angularangularjstypescriptfrontend

Angular Front-end editing picture problem


So i am currently working on a web application that i can add user which has name last name age born date and a picture .And after i create a user i can delete and edit it . My problem is when i create a user and then after i try to edit it and replace the picture of the user and then save it it creates a new user with the same info but with the picture i tried to change on the first place.

here is the stackblitz link to it : https://stackblitz.com/~/github.com/simeonskiq/Angular-project

edit-user.component.ts :

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UserService } from '../user.service';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

declare var bootstrap: any;

@Component({
  selector: 'app-edit-user',
  templateUrl: './edit-user.component.html',
  styleUrls: ['./edit-user.component.css']
})
export class EditUserComponent implements OnInit {
  userForm: FormGroup;
  userId!: string;
  notificationMessage: string = '';
  private confirmModalInstance: any;
  photoUrl?: string | ArrayBuffer | null = '';

  constructor(
    private fb: FormBuilder,
    private userService: UserService,
    private route: ActivatedRoute,
    private router: Router
  ) {
    this.userForm = this.fb.group({
      id: [''],
      firstName: ['', [Validators.required, this.noNumbers]],
      lastName: ['', [Validators.required, this.noNumbers]],
      occupation: ['', [Validators.required, this.noNumbers]],
      gender: ['', Validators.required],
      dateOfBirth: ['', Validators.required],
      photo: ['']
    });
  }

  noNumbers = (control: any): { [key: string]: boolean } | null => {
    if (control.value && /\d/.test(control.value)) {
      return { 'containsNumber': true };
    }
    return null;
  }

  ngOnInit(): void {
    this.userId = this.route.snapshot.params['id'];
    this.userService.getUser(Number(this.userId)).subscribe(user => {
      if (user) {
        this.userForm.patchValue(user);
        this.photoUrl = user.photo;
      }
    });
  
    const modalElement = document.getElementById('confirmSaveUserModal');
    if (modalElement) {
      this.confirmModalInstance = new bootstrap.Modal(modalElement);
    }
  }

  showNotification(message: string): void {
    this.notificationMessage = ''; 
    setTimeout(() => {
      this.notificationMessage = message;  
    }, 0);
  }

  handleSave(event: Event): void {
    event.preventDefault(); 
    if (this.userForm.invalid || this.hasErrors()) {
      this.showNotification('Please ensure all fields are correctly filled and do not contain numbers.');
    } else {
      this.showConfirmationModal(); 
    }
  }

  hasErrors(): boolean {
    const controls = this.userForm.controls;
    return Object.keys(controls).some(key => controls[key].hasError('containsNumber'));
  }

  showConfirmationModal(): void {
    if (this.confirmModalInstance) {
      this.confirmModalInstance.show();
    }
  }

  confirmSaveUser(): void {
    if (this.userForm.valid) {
      const updatedUser = { ...this.userForm.value };
      updatedUser.id = this.userId; // Explicitly assign the ID to avoid duplications
  
      this.userService.updateUser(updatedUser).subscribe(() => {
        this.confirmModalInstance.hide();
        this.router.navigate(['/home']);
      }, (error) => {
        console.error('Error updating user:', error);
      });
    }
  }

  cancel(): void {
    this.router.navigate(['/home']);
  }

  onFileChange(event: any): void {
    const file = event.target.files[0];
    if (file) {
      const reader = new FileReader();
      reader.onload = () => {
        this.photoUrl = reader.result as string; // Ensure it's a string
        this.userForm.patchValue({ photo: this.photoUrl }); 
      };
      reader.readAsDataURL(file);
    }
  }
}
user.service.ts :

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { User } from './user.model';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private users: User[] = [];

  constructor() { }

  getUsers(): Observable<User[]> {
    return of(this.users);
  }

  getUser(id: number): Observable<User | undefined> {
    const user = this.users.find(user => user.id === id.toString());
    return of(user);
  }

  addUser(user: User): Observable<void> {
    if (!user.id) {
      user.id = (this.users.length + 1).toString();  
    }
    user.createdDate = new Date(); 
    if (user.dateOfBirth) {
      user.age = this.calculateAge(user.dateOfBirth);
    }
    this.users.push({ ...user });  
    return of();
  }
  
  updateUser(updatedUser: User): Observable<void> {
    const index = this.users.findIndex(user => user.id === updatedUser.id);
    if (index !== -1) {
      updatedUser.age = this.calculateAge(updatedUser.dateOfBirth);
      this.users[index] = updatedUser;
    }
    return of();
  }

  updateUserPhoto(id: string, photoUrl: string | ArrayBuffer | null): Observable<void> {
    const index = this.users.findIndex(user => user.id === id);
    if (index !== -1 && photoUrl) {
      if (typeof photoUrl !== 'string') {
        // Convert ArrayBuffer to base64 string
        photoUrl = this.arrayBufferToBase64(photoUrl);
      }
      this.users[index].photo = photoUrl;
    }
    return of();
  }

  deleteUser(id: number): Observable<void> {
    this.users = this.users.filter(user => user.id !== id.toString());
    return of();
  }

  private calculateAge(dateOfBirth: string): number {
    const dob = new Date(dateOfBirth);
    const ageDifMs = Date.now() - dob.getTime();
    const ageDate = new Date(ageDifMs);
    return Math.abs(ageDate.getUTCFullYear() - 1970);
  }

  private arrayBufferToBase64(buffer: ArrayBuffer): string {
    let binary = '';
    const bytes = new Uint8Array(buffer);
    const len = bytes.byteLength;
    for (let i = 0; i < len; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return window.btoa(binary);
  }
}

I tried everything and it isnt working. I want when i edit it and place a new picture and save it the picture to change and not a new user to add.


Solution

  • Inside your add-user component you have some code that is binding an event listener to the confirmation modal button to fire off this.confirmAddUserAndNavigate():

      ngAfterViewInit(): void {
        const confirmModalElement = document.getElementById('confirmModal');
        if (confirmModalElement) {
          const saveButton = confirmModalElement.querySelector('.btn-primary');
          if (saveButton) {
            saveButton.addEventListener('click', () => {
              this.confirmAddUserAndNavigate();
            });
          }
        }
      }
    

    but there is already a function binded to the click event inside the HTML template. So it's firing off two calls to addUser() whenever the button is pressed.

    <div class="modal-footer">
       <button type="button" class="btn btn-secondary" (click)="cancel()">Cancel</button>
       <button type="button" class="btn btn-primary" (click)="confirmAddUserAndNavigate()">Save</button>
    </div>