Saturday 2 February 2019

Angular reactive forms edit example

Angular reactive forms edit example


In this topic we will discuss Implementing EDIT operation in a reactive form. We will use Create Employee Form for both creating a new employee as well as editing an existing employee details. 

Changes in app-routing.module.ts 
  • Include a new route for editing an existing employee details in app-routing.module.ts
  • We will use "create" route to create a new employee. 
  • The new "edit" route will be for editing an existing employee details. 
  • Notice to the "edit" route we are passing the id of the employee we want to edit.
const appRoutes: Routes = [
  { path: 'list', component: ListEmployeesComponent },
  { path: 'create', component: CreateEmployeeComponent },
  { path: 'edit/:id', component: CreateEmployeeComponent },
  { path: '', redirectTo: '/list', pathMatch: 'full' }
];


Changes in list-employees.component.html : Include click event binding on the Edit button. 

<td>
  <button class="btn btn-primary" (click)="editButtonClick(employee.id)">
    Edit
  </button>
</td>

Changes in list-employees.component.ts : Import Angular Router 

import Router from '@angular/router';

Inject it into the component class using the constructor 

constructor(private _employeeService: EmployeeService,
            private _router: Router) { }

Include editButtonClick() event handler method in the component class. When the Edit button is clicked, the user will be redirected to the "edit" route, passing it the id of the employee we want to edit. 

editButtonClick(employeeId: number) {
  this._router.navigate(['/edit', employeeId]);
}

Changes in create-employee.component.ts to support editing an existing employee 

Import ActivatedRoute, EmployeeService, IEmployee and ISkill types 

import { ActivatedRoute } from '@angular/router';
import { EmployeeService } from './employee.service';
import { IEmployee } from './IEmployee';
import { ISkill } from './ISkill';

Inject ActivatedRoute and EmployeeService into the component class using the constructor 

constructor(private fb: FormBuilder,
            private route: ActivatedRoute,
            private employeeService: EmployeeService) { }

In ngOnInit(), read the id route parameter value. If it is truthy i.e if the id value exists on the route, then call the geEmployee(id) method passing it the employee id.  

Once the employee data is retrieved and mapped to IEmployee type, it is then passed to editEmployee(employee: IEmployee) method.  

ngOnInit() {
  // Other existing code...

  this.route.paramMap.subscribe(params => {
    const empId = +params.get('id');
    if (empId) {
      this.getEmployee(empId);
    }
  });
}

getEmployee(id: number) method calls the EmployeeService and retrieves the existing employee details. 

getEmployee(id: number) {
  this.employeeService.getEmployee(id)
    .subscribe(
      (employee: IEmployee) => this.editEmployee(employee),
      (err: any) => console.log(err)
    );
}

The following editEmployee() method updates the form controls with the employee data, so the data is displayed on the form and the end user can edit it.

Notice we are using patchValue() method, to update the form controls with the employee data retrieved from the server. 

editEmployee(employee: IEmployee) {
  this.employeeForm.patchValue({
    fullName: employee.fullName,
    contactPreference: employee.contactPreference,
    emailGroup: {
      email: employee.email,
      confirmEmail: employee.email
    },
    phone: employee.phone
  });
}

At the moment, we have 3 minor bugs when editing an existing employee. 

Save button not disabled if the form is invalid : To disable the Save button bind disabled property of the button to the invalid property of the employeeForm 

<button class="btn btn-primary" type="submit" [disabled]="employeeForm.invalid">
  Save
</button>

We have a custom email validator attached to the email form control. This custom email validator fails validation if the email domain is not dell.com 

However, the validation error message is not displayed until the form control is touched or dirty. As we are editing the existing employee details, it makes more sense to display the validation errors if the data is invalid rather than waiting until the email form control is touched or dirty

To fix this, modify logValidationErrors() function as shown below. 

logValidationErrors(group: FormGroup = this.employeeForm): void {
  Object.keys(group.controls).forEach((key: string) => {
    const abstractControl = group.get(key);

    this.formErrors[key] = '';
    // abstractControl.value !== '' (This condition ensures if there is a value in the
    // form control and it is not valid, then display the validation error)
    if (abstractControl && !abstractControl.valid &&
        (abstractControl.touched || abstractControl.dirty || abstractControl.value !== '')) {
      const messages = this.validationMessages[key];

      for (const errorKey in abstractControl.errors) {
        if (errorKey) {
          this.formErrors[key] += messages[errorKey] + ' ';
        }
      }
    }

    if (abstractControl instanceof FormGroup) {
      this.logValidationErrors(abstractControl);
    }
  });
}

Similarly, if email and confirm email are not equal, the validation error is not displayed until the confirm email form control is dirty. To fix this include the following condition in matchEmail() function. 

confirmEmailControl.value === ''

Here is the matchEmail() function with the above condition included. 

function matchEmail(group: AbstractControl): { [key: string]: any } | null {
  const emailControl = group.get('email');
  const confirmEmailControl = group.get('confirmEmail');
  // If confirm email control value is not an empty string, and if the value
  // does not match with email control value, then the validation fails
  if (emailControl.value === confirmEmailControl.value
    || (confirmEmailControl.pristine && confirmEmailControl.value === '')) {
    return null;
  } else {
    return { 'emailMismatch'true };
  }
}

At the moment, the skills form array is not populated with the employees existing skills. We will discuss how to do this in our next topic. 


No comments:

Post a Comment