Support us .Net Basics C# SQL ASP.NET Aarvi MVC Slides C# Programs Subscribe Download

Move validation logic to the component class in reactive form

Suggested Videos
Part 10 - Angular form control valuechanges | Text | Slides
Part 11 - Loop through all form controls in formgroup in reactive form | Text | Slides
Part 12 - Move validation messages to the component class in reactive form | Text | Slides

In this video we will discuss, how to move the logic to show and hide validation messages from the view template into the component class. This is continuation to Part 12 where we discussed moving validation messages.


create-employee.component.html : Consider the following HTML in CreateEmployeeComponent view template. This HTML is for the "Full Name" field. The logic to add or remove has-error class is in the template. Also the validation message and the logic to show and hide it is also in the template at the moment.


<div class="form-group"
      [ngClass]="{'has-error': ((employeeForm.get('fullName').touched ||
                                employeeForm.get('fullName').dirty) &&
                                employeeForm.get('fullName').errors)}">
  <label class="col-sm-2 control-label" for="fullName">Full Name</label>
  <div class="col-sm-8">
    <input id="fullName" type="text" class="form-control" formControlName="fullName">
    <span class="help-block" *ngIf="((employeeForm.get('fullName').touched ||
                                      employeeForm.get('fullName').dirty) &&
                                      employeeForm.get('fullName').errors)">
      <span *ngIf="employeeForm.get('fullName').errors.required">
        Full Name is required
      </span>
      <span *ngIf="employeeForm.get('fullName').errors.minlength ||
                    employeeForm.get('fullName').errors.maxlength">
        Full Name must be greater than 2 characters and less than 10 characters
      </span>
    </span>
  </div>
</div>

Now, let's move all of this into the component class. Modify the HTML as shown below. Notice, now we are binding to formErrors.fullName property. All the complex logic is moved to the component class. Notice the HTML here is much less than what we have had before.

<div class="form-group" [ngClass]="{'has-error': formErrors.fullName}">
  <label class="col-sm-2 control-label" for="fullName">Full Name</label>
  <div class="col-sm-8">
    <input id="fullName" type="text" class="form-control" formControlName="fullName">
    <span class="help-block" *ngIf="formErrors.fullName">
      {{formErrors.fullName}}
    </span>
  </div>
</div>

Changes in create-employee.component.ts file : The changes are commented and self-explanatory

formErrors = {
  'fullName': '',
  'email': '',
  'skillName': '',
  'experienceInYears': '',
  'proficiency': ''
};

validationMessages = {
  'fullName': {
    'required': 'Full Name is required.',
    'minlength': 'Full Name must be greater than 2 characters.',
    'maxlength': 'Full Name must be less than 2 characters.',
  },
  'email': {
    'required': 'Email is required.'
  },
  'skillName': {
    'required': 'Skill Name is required.',
  },
  'experienceInYears': {
    'required': 'Experience is required.',
  },
  'proficiency': {
    'required': 'Proficiency is required.',
  },
};

ngOnInit() {

  this.employeeForm = this.fb.group({
    fullName: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(10)]],
    email: ['', Validators.required],
    skills: this.fb.group({
      skillName: ['', Validators.required],
      experienceInYears: ['', Validators.required],
      proficiency: ['', Validators.required]
    }),
  });

  // When any of the form control value in employee form changes
  // our validation function logValidationErrors() is called
  this.employeeForm.valueChanges.subscribe((data) => {
    this.logValidationErrors(this.employeeForm);
  });

}

logValidationErrors(group: FormGroup = this.employeeForm): void {
  Object.keys(group.controls).forEach((key: string) => {
    const abstractControl = group.get(key);
    if (abstractControl instanceof FormGroup) {
      this.logValidationErrors(abstractControl);
    } else {
      this.formErrors[key] = '';
      if (abstractControl && !abstractControl.valid
          && (abstractControl.touched || abstractControl.dirty)) {
        const messages = this.validationMessages[key];
        for (const errorKey in abstractControl.errors) {
          if (errorKey) {
            this.formErrors[key] += messages[errorKey] + ' ';
          }
        }
      }
    }
  });
}

The only problem at the moment is that when a control loses focus, our validation is not triggered. This is because valueChanges observable does not emit an event when the control loses focus. It only emits an event when the value changes.

One work around for this is to bind to the blur event and call validation function (logValidationErrors()) manually.

<input id="fullName" type="text" class="form-control"
        formControlName="fullName" (blur)="logValidationErrors()">

Here is the HTML for Email, Skill Name, Experience and Proficiency input elements. 

<div class="form-group" [ngClass]="{'has-error': formErrors.email}">
  <label class="col-sm-2 control-label" for="email">Email</label>
  <div class="col-sm-8">
    <input id="email" type="text" class="form-control"
           formControlName="email" (blur)="logValidationErrors()">
    <span class="help-block" *ngIf="formErrors.email">
      {{formErrors.email}}
    </span>
  </div>
</div>

<div class="well">
  <div formGroupName="skills">

    <div class="form-group" [ngClass]="{'has-error': formErrors.skillName}">
      <label class="col-sm-2 control-label" for="skillName">
        Skill
      </label>
      <div class="col-sm-4">
        <input type="text" class="form-control" id="skillName" formControlName="skillName"
               (blur)="logValidationErrors()" placeholder="C#, Java, Angular etc...">
        <span class="help-block" *ngIf="formErrors.skillName">
          {{formErrors.skillName}}
        </span>
      </div>
    </div>

    <div class="form-group" [ngClass]="{'has-error': formErrors.experienceInYears}">
      <label class="col-sm-2 control-label" for="experienceInYears">
        Experience
      </label>
      <div class="col-sm-4">
        <input type="text" class="form-control" id="experienceInYears"
               formControlName="experienceInYears" placeholder="In Years"
              (blur)="logValidationErrors()">
        <span class="help-block" *ngIf="formErrors.experienceInYears">
          {{formErrors.experienceInYears}}
        </span>
      </div>
    </div>

    <div class="form-group" [ngClass]="{'has-error': formErrors.proficiency}">
      <label class="col-md-2 control-label">Proficiency</label>
      <div class="col-md-8">
        <label class="radio-inline">
          <input type="radio" value="beginner" formControlName="proficiency"
                 (blur)="logValidationErrors()">Beginner
        </label>
        <label class="radio-inline">
          <input type="radio" value="intermediate" formControlName="proficiency"
                 (blur)="logValidationErrors()">Intermediate
        </label>
        <label class="radio-inline">
          <input type="radio" value="advanced" formControlName="proficiency"
                 (blur)="logValidationErrors()">Advanced
        </label>
        <span class="help-block" *ngIf="formErrors.experienceInYears">
          {{formErrors.proficiency}}
        </span>
      </div>
    </div>
  </div>
</div>

angular 6 tutorial for beginners

5 comments:

  1. Can anyone help me with my requirement.

    if any of the input feilds are not valid in the form, Validations need to be fired after clicking submit button, it had to be achieved using formErrors of reactive forms.


    ReplyDelete
    Replies
    1. You can add Click Event to that button and call the validation method inside that event.Dont call validation method inside value changes observable.

      Delete
  2. if you are following the series then you can remove the value changes observable code and call the error logging function in your function submit. that should solve.

    ReplyDelete
  3. this.formErrors[key] = ''; in this portion error is comimng
    error - Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ fullName: string; email: string; skillName: string; experienceInYears: string; proficiency: string; }'.
    No index signature with a parameter of type 'string' was found on type '{ fullName: string; email: string; skillName: string; experienceInYears: string; proficiency

    ReplyDelete
    Replies
    1. you can solve this issue by adding @ts-ignore comment above the line that raises this error.
      Example based on the code:
      // @ts-ignore
      const messages = this.validationMessages[key];

      Delete

It would be great if you can help share these free resources