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.
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.
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.
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.
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.
Make similar changes on experienceInYears and proficiency form controls.
All the skill form controls validation messages are in the template. So delete the validation messages from the validationMessages object in the component class.
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.
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.
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);
//
}
//
}
// }
});
}
I used your code above and I get the following error:
ReplyDeleteERROR 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
Hi sir,
ReplyDeleteWhy component level validation failing.
How to provide this validation in component level itself.
Can you please explain.
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
ReplyDeleteAfter adding this code getting below error
ReplyDeleteERROR 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.