Saturday 2 February 2019

Angular dynamic forms validation

Angular dynamic forms validation


In this topic we will discuss validating dynamically generated form controls

Modify the skillName form control HTML as shown below.  

<div formArrayName="skills"
    *ngFor="let skill of employeeForm.get('skills').controls; let i = index">

  <div [formGroupName]="i">

    <div class="form-group" [ngClass]="{'has-error':
          skill.get('skillName').invalid && skill.get('skillName').touched}">
      <label class="col-sm-2 control-label" [attr.for]="'skillName'+i">
        Skill
      </label>
      <div class="col-sm-4">
        <input type="text" class="form-control" [id]="'skillName'+i"
                formControlName="skillName" placeholder="C#, Java, Angular etc...">
        <span class="help-block" *ngIf="skill.get('skillName').errors?.required &&
                                              skill.get('skillName').touched">
          Skill Name is required
        </span>
      </div>
    </div>

  </div>
</div>

Notice, we are using the loop variable skill, to check if the dynamically generated skillName form control is invalid and touched. If so, the has-error bootstrap style class is applied. To get to the skillName form control in the skill FormGroup, we are using the get() method on the FormGroup and passing it the form control name. 

[ngClass]="{'has-error': skill.get('skillName').invalid &&
                         skill.get('skillName').touched}"

To get to the skillName form control in the skill FormGroup, we can also use controls property on the FormGroup and then the skillName form control. 

[ngClass]="{'has-error': skill.controls.skillName.invalid &&
                          skill.controls.skillName.touched}"

Even here, we are using the loop variable skill, to check if the dynamically generated skillName form control has failed required validation and touched. If so, the <span>element displays the validation error, otherwise hides it.  

<span class="help-block" *ngIf="skill.get('skillName').errors?.required &&
                                      skill.get('skillName').touched">
  Skill Name is required
</span>

Make sure to use the safe navigation operator between errors and required properties. This is because, when the skillName form control does not have any validation errors, the errors property will be null and trying to check for required key on a null object will result in Cannot read property 'required' of null error. 

skill.get('skillName').errors?.required

Make similar changes on experienceInYears and proficiency form controls. 

<div class="form-group" [ngClass]="{'has-error':
skill.get('experienceInYears').invalid && skill.get('experienceInYears').touched}">
  <label class="col-sm-2 control-label" [attr.for]="'experienceInYears'+i">
    Experience
  </label>
  <div class="col-sm-4">
    <input type="text" class="form-control" [id]="'experienceInYears'+i"
            formControlName="experienceInYears" placeholder="In Years">
    <span class="help-block" *ngIf="skill.get('experienceInYears').errors?.required &&
                                    skill.get('experienceInYears').touched">
      Experience is required
    </span>
  </div>
</div>

<div class="form-group" [ngClass]="{'has-error':
skill.get('proficiency').invalid && skill.get('proficiency').touched}">
  <label class="col-sm-2 control-label">Proficiency</label>
  <div class="col-sm-8">
    <label class="radio-inline">
      <input type="radio" value="beginner" formControlName="proficiency">Beginner
    </label>
    <label class="radio-inline">
      <input type="radio" value="intermediate"formControlName="proficiency">Intermediate
    </label>
    <label class="radio-inline">
      <input type="radio" value="advanced" formControlName="proficiency">Advanced
    </label>
    <span class="help-block" *ngIf="skill.get('proficiency').errors?.required &&
                                    skill.get('proficiency').touched">
      Proficiency is required
    </span>
  </div>
</div>

All the skill form controls validation messages are in the template. So delete the validation messages from the validationMessages object in the component class. 

validationMessages = {
  'fullName': {
    'required''Full Name is required.',
    'minlength''Full Name must be greater than 2 characters.',
    'maxlength''Full Name must be less than 10 characters.'
  },
  'email': {
    'required''Email is required.',
    'emailDomain''Email domian should be dell.com'
  },
  'confirmEmail': {
    'required''Confirm Email is required.',
  },
  'emailGroup': {
    'emailMismatch''Email and Confirm Email do not match',
  },
  'phone': {
    'required''Phone is required.'
  },
  // 'skillName': {
  //   'required': 'Skill Name is required.',
  // },
  // 'experienceInYears': {
  //   'required': 'Experience is required.',
  // },
  // 'proficiency': {
  //   'required': 'Proficiency is required.',
  // },
};

On the formErrors object also, delete the skill related properties. In fact, we do not need any of the properties on the formErrors object, as they will be dynamically added when the corresponding form control fails validation. Notice, I have commented all the properties on the formErrors object. 

formErrors = {
  // 'fullName': '',
  // 'email': '',
  // 'confirmEmail': '',
  // 'emailGroup': '',
  // 'phone': '',
  // 'skillName': '',
  // 'experienceInYears': '',
  // 'proficiency': ''
};

Since the validation messages for the skill form controls are in the template, we do not have to loop through the skill form groups in the form array. So I have commented the block of code that loops through the FormArray

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

    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] + ' ';
        }
      }
    }

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

    // if (abstractControl instanceof FormArray) {
    //   for (const control of abstractControl.controls) {
    //     if (control instanceof FormGroup) {
    //       this.logValidationErrors(control);
    //     }
    //   }
    // }
  });
}


No comments:

Post a Comment