Angular Reactive Forms Cannot Read Property 'invalid' of Undefined
- Learning Objectives
- Validators
- Form Control State
- Dirty & Pristine
- Touched & Untouched
- Valid & Invalid
- Validation Styling
- Writing Shorter Validation Expressions
- Validation Letters
- Summary
- Listing
Learning Objectives
-
How to add validation checks to our class via the class model.
-
How tostyle our form in social club to give visual feedback to the user then they know when the fields don't pass the validation checks.
-
How to add validation error messages to the class to give hints to the user about why the field isn't passing a validation check.
Validators
Carrying on from the model-driven form we started in the previous lecture.
Our form is valid all the time, regardless of what input the user types into the controls.
Validators are rules which an input control has to follow. If the input doesn't friction match the rule and so the control is said to be invalid.
Since it's a signup form most of the fields should exist required and I would want to specify some more complex validators on the password field to brand sure the user is entering a good strong password.
We can apply validators either past calculation attributes to the template or by defining them on our FormControls
in our model.
To stick to the theme of being model-driven we are going to add validators to the form model direct.
Angular comes with a small set of pre-congenital validators to lucifer the ones nosotros can define via standard HTMLfive attributes, namely required
, minlegth
, maxlength
andpattern
which nosotros can access from theValidators
module.
The start parameter of a FormControl
constructor is the initial value of the command, we'll exit that as empty string. The 2d parameter contains either a unmarried validator if we only want to utilise one, or a list of validators if we desire to use multiple validators to a unmarried command.
Our model then looks something like this:
import { FormGroup, FormControl, Validators } from '@athwart/forms'; . . . class ModelFormComponent implements OnInit { myform: FormGroup; ngOnInit() { myform = new FormGroup({ proper noun: new FormGroup({ firstName: new FormControl('', Validators.required), (1) lastName: new FormControl('', Validators.required), }), email: new FormControl('', [ (2) Validators.required, Validators.pattern("[^ @]*@[^ @]*") (three) ]), countersign: new FormControl('', [ Validators.minLength(8), (4) Validators.required ]), linguistic communication: new FormControl() (5) }); } }
ane | We add a single required validator to marking this control as required. |
2 | We can as well provide an array of validators. |
3 | We specify a blueprint validator which checks whether the email contains a@ graphic symbol. |
4 | Theminlength validator checks to see if the countersign is a minimum of 8 characters long. |
5 | We don't add any validators to the language select box. |
Form Control State
The form control instance on our model encapsulates country near the control itself, such as if it is currently valid or if information technology'southward been touched.
Muddy & Pristine
We can become a reference to these form control instances in our template through thecontrols
property of our myform
model, for instance we can print out the the muddy state of the electronic mail field like so:
<pre>Dirty? {{ myform.controls.email.muddied }}</pre>
dirty
is true
if the user has inverse the value of the control.
The opposite ofdirty
is pristine
so if we wrote:
<pre>Pristine? {{ myform.controls.e-mail.pristine }}</pre>
This would exist true
if the user hasn't changed the value, andimitation
if the user has.
Touched & Untouched
A controls is said to be touched if the the user focused on the control and and then focused on something else. For example by clicking into the control and then pressing tab or clicking on another command in the form.
The difference between touched
anddirty
is that with touched the user doesn't demand to really change the value of the input control.
<pre>Touched? {{ myform.controls.e-mail.touched }}</pre>
touched
is true
of the field has been touched past the user, otherwise it'southward fake.
The opposite oftouched
is the holding untouched
.
Valid & Invalid
We tin can besides check thevalid
land of the control with:
<pre>Valid? {{ myform.controls.email.valid }}</pre>
valid
is true
of the field doesn't have whatever validators or if all the validators are passing.
Once more the opposite ofvalid
is invalid
, so we could write:
<pre>Invalid? {{ myform.controls.email.invalid }}</pre>
This would be true
if the command was invalid andfalse
if information technology was valid.
Validation Styling
Bootstrap has classes for showing visual feedback for course controls when they are invalid.
For instance if we add thehas-danger
form to the parent div
of the input command with the class ofform-grouping
it adds ared border.
Conversely if we add thehas-success
class it adds agreen border.
Figure 1. Valid Form Control
Figure 2. Invalid Course Control
We can combine Bootstrap classes with dirty
andinvalid
FormControl
properties and the ngClass
directive to give the user some nice visual feedback, like and so:
<div class="class-grouping" [ngClass]="{ 'has-danger': myform.controls.electronic mail.invalid && myform.controls.email.dirty, (1) 'has-success': myform.controls.email.valid && myform.controls.electronic mail.dirty (2) }">
1 | If the email is invalid and it'due south been touched by the user then we add thehas-danger class giving the control a red border. |
2 | If the email is valid and it's been touched past the user and so nosotros add thehas-success course giving the control a green border. |
Tip
The reason we check for the dirty
property existence true is then nosotros don't show the user visual feedback when the form is first displayed. Instead we only show the user feedback when they have had a chance to edit the field.
Now the input control shows thegreenish edge when it's valid
anddirty
andred if it's invalid
anddingy
.
Writing Shorter Validation Expressions
The higher up can quickly become cumbersome to use in our templates, especially for things like the nested firstName
andlastName
controls.
Since thefirstName
andlastName
FormControls
exist nether thename
FormGroup
to access those from the template we demand to use syntax like this:
<div class="form-group" [ngClass]="{ 'has-danger': myform.controls.proper name.controls.firstName.invalid && myform.controls.name.controls.firstName.dingy, 'has-success': myform.controls.proper noun.controls.firstName.valid && myform.controls.proper noun.controls.firstName.dirty }">
The length of the expression quickly becomes unwieldy.
We can help ourselves here past creating local backdrop on our component to reverberate the private FormControls
and bounden directly to them in our template, like so:
Listing 1. script.ts
class ModelFormComponent implements OnInit { langs: string[] = [ 'English', 'French', 'German', ]; myform: FormGroup; firstName: FormControl; (ane) lastName: FormControl; email: FormControl; password: FormControl; language: FormControl; ngOnInit() { this.createFormControls(); this.createForm(); } createFormControls() { (ii) this.firstName = new FormControl('', Validators.required); this.lastName = new FormControl('', Validators.required); this.email = new FormControl('', [ Validators.required, Validators.pattern("[^ @]*@[^ @]*") ]); this.password = new FormControl('', [ Validators.required, Validators.minLength(8) ]); this.language = new FormControl('', Validators.required); } createForm() { (iii) this.myform = new FormGroup({ name: new FormGroup({ firstName: this.firstName, lastName: this.lastName, }), email: this.electronic mail, password: this.password, language: this.language }); } }
ane | We declare theFormControls equally backdrop of our component. And then we tin can bind to them directly in our tempalte without having to go through the superlative-level myform model. |
two | We first create theFormControls . |
3 | Nosotros and then construct themyform model from the form controls we created previously and stored as properties on our component. |
At present we tin can bind directly to our individual grade controls in our template without having to traverse the tree from themyform instance.
Nosotros tin therefore ngClass
expression to something much more succinct, like so:
<div grade="form-group" [ngClass]="{ 'has-danger': firstName.invalid && firstName.muddy, 'has-success': firstName.valid && firstName.dirty }">
Validation Letters
Equally well equally styling a course when information technology'due south invalid it's as well useful to show the user error messages with helpful hints about how they tin can make the form valid again.
Taking what we take learnt about form validation styling we can apply the aforementioned method to conditionally show or hide an fault bulletin.
Bootstrap conveniently has some markup and classes for form controls which nosotros tin can employ to bear witness these error messages, let's add them to our password grade command, like so:
<div class="grade-grouping"> <characterization>Password</characterization> <input type="password" class="form-control" formControlName="countersign"> <div class="course-control-feedback" (1) *ngIf="password.invalid && password.dingy"> (two) Field is invalid </div> </div>
-
The form
grade-command-feedback
shows a message in cherry if the parentform-grouping
div also has thehas-danger
class, i.e. when the field is invalid any text under this div will show as ruby. -
We only bear witness the message when the password field is both
invalid
anddirty
.
Now when the input control is both dirty
andinvalid
we show the validation error message "Field is invalid"
.
However this field has two validators associated with it, the required validator and the minlength validator simply with the to a higher place solution we only testify ane generic validation fault bulletin. We can't tell the user what they demand to exercise in order to make the field valid.
How to do nosotros show a separate validation mistake message for each of the validators?
Nosotros can practise that by checking another property on our class control chosen errors
.
This is an object which has one entry per validator, the key is the proper name of the validator and if the value is not null then the validator is failing.
<div grade="form-command-feedback" *ngIf="countersign.errors && (password.dirty || password.touched)"> <p *ngIf="countersign.errors.required">Countersign is required</p> <p *ngIf="password.errors.minlength">Password must be 8 characters long</p> </div>
Note
If theerrors
object has a key ofrequired
it means the command is failing because it's required
and the user hasn't entered any value into the input field.
Digging a flake deeper into theerrors
belongings. The value tin can contain useful bits of information which nosotros can show the user, for instance theminlength
validator gives u.s. therequiredLength
andactualLength
properties.
{ "minlength": { "requiredLength": 8, "actualLength": one } }
We can employ this in our validation mistake message to requite the user a bit more help in resolving the issue, like and so:
<div class="form-command-feedback" *ngIf="countersign.errors && (password.dirty || password.touched)"> <p *ngIf="password.errors.required">Password is required</p> <p *ngIf="password.errors.minlength">Password must be 8 characters long, we need another {{password.errors.minlength.requiredLength - password.errors.minlength.actualLength}} characters </p> </div>
Figure 3. Form Validation Letters
Summary
Nosotros can add validators to our model form which check each field for validity.
Can render the controls with styling to show the user when fields are invalid.
Finally, we can add validation error messages then the user knows how to make the form valid again.
Next up we'll look at how to submit and reset a model-driven grade.
List
Listing ii. main.ts
import { NgModule, Component, Pipe, OnInit } from '@angular/core'; import { ReactiveFormsModule, FormsModule, FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; @Component({ selector: 'model-class', template: ` <div class="container"> <form novalidate [formGroup]="myform"> <fieldset formGroupName="proper name"> <div class="grade-grouping" [ngClass]="{ 'has-danger': firstName.invalid && (firstName.dirty || firstName.touched), 'has-success': firstName.valid && (firstName.dirty || firstName.touched) }"> <label>Start Name</label> <input blazon="text" class="form-command" formControlName="firstName" required> <div class="class-control-feedback" *ngIf="firstName.errors && (firstName.muddied || firstName.touched)"> <p *ngIf="firstName.errors.required">Commencement Proper name is required</p> </div> <!-- <pre>Valid? {{ myform.controls.name.controls.firstName.valid }}</pre> <pre>Muddy? {{ myform.controls.name.controls.firstName.dirty }}</pre> --> </div> <div course="grade-grouping" [ngClass]="{ 'has-danger': lastName.invalid && (lastName.dirty || lastName.touched), 'has-success': lastName.valid && (lastName.dingy || lastName.touched) }"> <label>Terminal Proper name</label> <input blazon="text" grade="form-control" formControlName="lastName" required> <div class="form-control-feedback" *ngIf="lastName.errors && (lastName.muddied || lastName.touched)"> <p *ngIf="lastName.errors.required">Final Name is required</p> </div> </div> </fieldset> <div grade="form-group" [ngClass]="{ 'has-danger': email.invalid && (email.dirty || email.touched), 'has-success': email.valid && (email.dingy || email.touched) }"> <characterization>Email</label> <input type="email" course="form-control" formControlName="email" required> <div class="course-control-feedback" *ngIf="email.errors && (e-mail.muddy || email.touched)"> <p *ngIf="email.errors.required">Email is required</p> <p *ngIf="password.errors.pattern">The email address must incorporate at least the @ character</p> </div> <!-- <pre>Valid? {{ myform.controls.electronic mail.valid }}</pre> <pre>Dingy? {{ myform.controls.electronic mail.dirty }}</pre> --> </div> <div class="form-group" [ngClass]="{ 'has-danger': countersign.invalid && (countersign.dirty || password.touched), 'has-success': password.valid && (countersign.muddy || password.touched) }"> <label>Countersign</label> <input type="password" class="form-command" formControlName="password" required> <div class="form-command-feedback" *ngIf="password.errors && (password.dirty || password.touched)"> <p *ngIf="countersign.errors.required">Password is required</p> <p *ngIf="password.errors.minlength">Password must be 8 characters long, we need another {{password.errors.minlength.requiredLength - password.errors.minlength.actualLength}} characters </p> </div> </div> <!-- <pre>{{ countersign.errors | json }}</pre> --> <div class="form-group" [ngClass]="{ 'has-danger': language.invalid && (language.muddy || language.touched), 'has-success': language.valid && (language.dirty || language.touched) }"> <label>Linguistic communication</label> <select class="form-command" formControlName="linguistic communication"> <choice value="">Please select a language</choice> <option *ngFor="permit lang of langs" [value]="lang">{{lang}} </pick> </select> </div> <pre>{{myform.value | json}}</pre> </course> </div>` }) class ModelFormComponent implements OnInit { langs: string[] = [ 'English language', 'French', 'German language', ]; myform: FormGroup; firstName: FormControl; lastName: FormControl; email: FormControl; password: FormControl; linguistic communication: FormControl; ngOnInit() { this.createFormControls(); this.createForm(); } createFormControls() { this.firstName = new FormControl('', Validators.required); this.lastName = new FormControl('', Validators.required); this.electronic mail = new FormControl('', [ Validators.required, Validators.blueprint("[^ @]*@[^ @]*") ]); this.password = new FormControl('', [ Validators.required, Validators.minLength(8) ]); this.language = new FormControl(''); } createForm() { this.myform = new FormGroup({ name: new FormGroup({ firstName: this.firstName, lastName: this.lastName, }), email: this.email, password: this.countersign, language: this.language }); } } @Component({ selector: 'app', template: `<model-form></model-form>` }) class AppComponent { } @NgModule({ imports: [ BrowserModule, FormsModule, ReactiveFormsModule], declarations: [ AppComponent, ModelFormComponent ], bootstrap: [ AppComponent ], }) class AppModule { } platformBrowserDynamic().bootstrapModule(AppModule);
Source: https://codecraft.tv/courses/angular/forms/model-driven-validation/