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

Angular dynamic forms validation

Suggested Videos
Part 20 - Creating formarray of formgroup objects in Angular | Text | Slides
Part 21 - Angular dynamic forms tutorial | Text | Slides
Part 22 - Generate unique id value for dynamically created form controls in angular | Text | Slides

In this video 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);
    //     }
    //   }
    // }
  });
}

angular 6 tutorial for beginners

4 comments:

  1. I used your code above and I get the following error:
    ERROR TypeError: Cannot read property 'get' of undefined
    at Object.eval [as updateDirectives] (CreateEmployeeComponent.html:97)
    at Object.debugUpdateDirectives [as updateDirectives] (core.js:11054)
    at checkAndUpdateView (core.js:10451)
    at callViewAction (core.js:10692)
    at execComponentViewsAction (core.js:10634)
    at checkAndUpdateView (core.js:10457)
    at callViewAction (core.js:10692)
    at execEmbeddedViewsAction (core.js:10655)
    at checkAndUpdateView (core.js:10452)
    at callViewAction (core.js:10692)
    View_CreateEmployeeComponent_0 @ CreateEmployeeComponent.html:75
    proxyClass @ compiler.js:10173
    push../node_modules/@angular/core/fesm5/core.js.DebugContext_.logError @ core.js:11306
    push../node_modules/@angular/core/fesm5/core.js.ErrorHandler.handleError @ core.js:1719
    (anonymous) @ core.js:4578
    push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:388
    push../node_modules/zone.js/dist/zone.js.Zone.run @ zone.js:138
    push../node_modules/@angular/core/fesm5/core.js.NgZone.runOutsideAngular @ core.js:3779
    push../node_modules/@angular/core/fesm5/core.js.ApplicationRef.tick @ core.js:4578
    (anonymous) @ core.js:4462
    push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:388
    onInvoke @ core.js:3820
    push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:387
    push../node_modules/zone.js/dist/zone.js.Zone.run @ zone.js:138
    push../node_modules/@angular/core/fesm5/core.js.NgZone.run @ core.js:3734
    next @ core.js:4462
    schedulerFn @ core.js:3551
    push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub @ Subscriber.js:195
    push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next @ Subscriber.js:133
    push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next @ Subscriber.js:77
    push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next @ Subscriber.js:54
    push../node_modules/rxjs/_esm5/internal/Subject.js.Subject.next @ Subject.js:47
    push../node_modules/@angular/core/fesm5/core.js.EventEmitter.emit @ core.js:3535
    checkStable @ core.js:3789
    onHasTask @ core.js:3833
    push../node_modules/zone.js/dist/zone.js.ZoneDelegate.hasTask @ zone.js:441
    push../node_modules/zone.js/dist/zone.js.ZoneDelegate._updateTaskCount @ zone.js:461
    push../node_modules/zone.js/dist/zone.js.Zone._updateTaskCount @ zone.js:285
    push../node_modules/zone.js/dist/zone.js.Zone.runTask @ zone.js:205
    drainMicroTaskQueue @ zone.js:595
    Promise.then (async)
    scheduleMicroTask @ zone.js:578
    push../node_modules/zone.js/dist/zone.js.ZoneDelegate.scheduleTask @ zone.js:410
    push../node_modules/zone.js/dist/zone.js.Zone.scheduleTask @ zone.js:232
    push../node_modules/zone.js/dist/zone.js.Zone.scheduleMicroTask @ zone.js:252
    scheduleResolveOrReject @ zone.js:862
    ZoneAwarePromise.then @ zone.js:962
    push../node_modules/@angular/core/fesm5/core.js.PlatformRef.bootstrapModule @ core.js:4345
    ./src/main.ts @ main.ts:11
    __webpack_require__ @ bootstrap:78
    0 @ custom.validators.ts:15
    __webpack_require__ @ bootstrap:78
    checkDeferredModules @ bootstrap:45
    webpackJsonpCallback @ bootstrap:32
    (anonymous) @ main.js:1
    CreateEmployeeComponent.html:75

    ReplyDelete
  2. Hi sir,

    Why component level validation failing.
    How to provide this validation in component level itself.

    Can you please explain.

    ReplyDelete
  3. Hi Venkat, how to create error component and get label dynamically when field is dirty then pass error message to current field..? like this (name) is required.. please help me Boss

    ReplyDelete
  4. After adding this code getting below error


    ERROR in src/app/employee/create-employee.component.html:78:85 - error TS2339: Property 'controls' does not exist on type 'AbstractControl'.

    ~~~~~~~~

    src/app/employee/create-employee.component.ts:8:16
    8 templateUrl: './create-employee.component.html',
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component CreateEmployeeComponent.

    ReplyDelete

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