The StatusChanges
is an event raised by the Angular forms whenever the Angular calculates the validation status of the FormControl, FormGroup or FormArray. It returns an observable so that you can subscribe to it. The observable gets the latest status of the control. The Angular runs the validation check on every change made to the control. It also generates a list of validation errors in which case the status becomes INVALID
. If there are no errors, then the status becomes VALID
Table of Contents
How to use StatusChanges
The Angular Forms has three building blocks. FormControl, FormGroup & FormArray. All of these controls extend the AbstractControl base class. The AbstractControl base class implements StatusChanges
event
We can subscribe to StatusChanges
by getting the reference of the control and subscribing it as shown below
1 2 3 4 5 6 | this.reactiveForm.get("firstname").statusChanges.subscribe(newStaus => { console.log('firstname status changed') console.log(newStaus) }) |
You can also subscribe to the top-level form as shown below.
1 2 3 4 5 6 | this.reactiveForm.statusChanges.subscribe(newStaus => { console.log('form Status changed event') console.log(newStaus) }) |
StatusChanges Example
Create a reactive form as shown below
1 2 3 4 5 6 7 8 9 10 11 | reactiveForm = new FormGroup({ firstname: new FormControl('', [Validators.required]), lastname: new FormControl(), address: new FormGroup({ city: new FormControl(), street: new FormControl(), pincode: new FormControl() }) }) |
StatusChanges of FormControl
You can subscribe to StatusChanges
of a single FormControl
as shown below. Here in the newStatus variable, we will get the latest status of the firstname. You can also retreive the latest status of the firstname using this.reactiveForm.get("firstname").status
1 2 3 4 5 6 7 | this.reactiveForm.get("firstname").statusChanges.subscribe(newStatus => { console.log('firstname status changed') console.log(newStatus) //latest status console.log(this.reactiveForm.get("firstname").status) //latest status }) |
But, the top-level form is not yet updated at this point, hence this.reactiveForm.status
still shows the old status of the firstname and also the form.
The statusChanges
event for the firstname fires immediately after the new status is updated but before the change is bubbled up to its parent. Hence the this.reactiveForm.status
still shows the old status.
1 2 3 4 5 6 7 8 | this.reactiveForm.get("firstname").statusChanges.subscribe(newStatus=> { console.log('firstname status changed') console.log(newStatus) //latest status console.log(this.reactiveForm.get("firstname").status) //latest status console.log(this.reactiveForm.status) //Previous status }) |
You can work around this by waiting for the next tick using setTimeout
as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 | this.reactiveForm.get("firstname").statusChanges.subscribe(newStatus=> { console.log('firstname status changed') console.log(newStatus) //latest status console.log(this.reactiveForm.get("firstname").status) //latest status console.log(this.reactiveForm.status) //Previous status setTimeout(() => { console.log(this.reactiveForm.status) //latest status }) }) |
StatusChanges of FormGroup
The StatusChanges
event of FormGroup
or FormArray
is fired, whenever the status of any of its child controls are calculated. For Example, the following StatusChanges
will fire even whenever the status of the city, state & pincode are calculated.
1 2 3 4 5 6 | this.reactiveForm.get("address").statusChanges.subscribe(newStaus => { console.log('address status changed') console.log(newStaus) }) |
StatusChanges of Form
The following example show we can subscribe to the changes made to the entire form.
1 2 3 4 5 6 | this.reactiveForm.statusChanges.subscribe(newStaus => { console.log('form status changed') console.log(newStaus) }) |
emitEvent & StatusChanges
The statusChanges
event is fired even when the angular calculates the status of the control either via UI or programmatically. In some circumstances, you might not want to raise the statusChanges
event. To do that we can use the emitEvent: false
In the following example, the statusChanges
event is not fired at all, even though the value of the firstname is changed making it and the form INVALID
.
1 2 3 | this.reactiveForm.get("firstname").setValue("", { emitEvent: false }); |
You can use emitEvent: false
with the setValue
, patchValue
, markAsPending
, disable
, enable
, updateValueAndValidity
& setErrors
methods.
onlySelf & StatusChanges
When onlySelf: true
the changes will only affect only this FormControl
and change is not bubbled up to its parent. Hence the StatusChanges
event of the parent FormGroup
does not fire.
For Example, the following code will result in the StatusChanges
of the firstname. but not of its parent (i.e. top-level form)
1 2 3 | this.reactiveForm.get("firstname").setValue("", { onlySelf: true }); |
You can use the onlySelf: true
with the setValue
, patchValue
, markAsUntouched
, markAsDirty
, markAsPristine
, markAsPending
, disable
, enable
, and updateValueAndValidity
methods
Complete Source Code
[tabby title=”reactive.component.ts”]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms' import { timeout } from 'q'; @Component({ templateUrl: './reactive.component.html', }) export class ReactiveComponent implements OnInit { title = 'Reactive Forms'; reactiveForm = new FormGroup({ firstname: new FormControl('', [Validators.required]), lastname: new FormControl(), address: new FormGroup({ city: new FormControl(), street: new FormControl(), pincode: new FormControl() }) }) onSubmit() { //console.log(this.reactiveForm.value); } ngOnInit() { this.reactiveForm.get("firstname").statusChanges.subscribe(newStatus=> { console.log('firstname status changed') console.log(newStatus) console.log(this.reactiveForm.get("firstname").status) console.log(this.reactiveForm.status) setTimeout(() => { console.log(this.reactiveForm.status) }) }) this.reactiveForm.get("address").statusChanges.subscribe(newStatus=> { console.log('address status changed') console.log(newStatus) }) this.reactiveForm.statusChanges.subscribe(newStatus=> { console.log('form status changed') console.log(newStatus) }) } setValue() { let contact = { firstname: "Rahul", lastname: "Dravid", address: { city: "Bangalore", street: "Brigade Road", pincode: "600070" } }; this.reactiveForm.setValue(contact); } setAddress() { this.reactiveForm.get("address").setValue( { city: "Bangalore", street: "Brigade Road", pincode: "600070" } ); } setFirstname() { this.reactiveForm.get("firstname").setValue("Saurav") } withoutOnlySelf() { this.reactiveForm.get("firstname").setValue(""); } withOnlySelf() { this.reactiveForm.get("firstname").setValue("", { onlySelf: true }); } withEmitEvent() { this.reactiveForm.get("firstname").setValue("Sachin"); } withoutEmitEvent() { this.reactiveForm.get("firstname").setValue("", { emitEvent: false }); } reset() { this.reactiveForm.reset(); } } |
[tabby title=”reactive.component.html”]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | <h3>{{title}}</h3> <div style="float: left; width:50%;"> <form [formGroup]="reactiveForm" (ngSubmit)="onSubmit()" novalidate> <p> <label for="firstname">First Name </label> <input type="text" id="firstname" name="firstname" formControlName="firstname"> <label for="lastname">Last Name </label> <input type="text" id="lastname" name="lastname" formControlName="lastname"> </p> <div formGroupName="address"> <p> <label for="city">City</label> <input type="text" class="form-control" name="city" formControlName="city"> <label for="street">Street</label> <input type="text" class="form-control" name="street" formControlName="street"> <label for="pincode">Pin Code</label> <input type="text" class="form-control" name="pincode" formControlName="pincode"> </p> </div> <button>Submit</button> </form> <div> <button type="button" (click)="setValue()">SetValue</button> <button type="button" (click)="setAddress()">Address</button> <button type="button" (click)="setFirstname()">First Name</button> </div> <div> <button type="button" (click)="withoutOnlySelf()">Without Only Self</button> <button type="button" (click)="withOnlySelf()">With Only Self</button> </div> <div> <button type="button" (click)="withouEmitEvent()">Without EmitEvent</button> <button type="button" (click)="withEmitEvent()">With EmitEvent</button> </div> </div> <div style="float: right; width:50%;"> <h3>Form Status</h3> <b>status : </b>{{reactiveForm.status}} <b>valid : </b>{{reactiveForm.valid}} <b>invalid : </b>{{reactiveForm.invalid}} <b>touched : </b>{{reactiveForm.touched}} <b>untouched : </b>{{reactiveForm.untouched}} <b>pristine : </b>{{reactiveForm.pristine}} <b>dirty : </b>{{reactiveForm.dirty}} <b>disabled : </b>{{reactiveForm.disabled}} <b>enabled : </b>{{reactiveForm.enabled}} <h3>Form Value</h3> {{reactiveForm.value |json}} </div> |
[tabbyending]
[tabby title=”app.component.html”]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <h3>Angular StatusChanges Example</h3> <ul> <li> <a [routerLink]="['/template']" routerLinkActive="router-link-active" >Template</a> </li> <li> <a [routerLink]="['/reactive']" routerLinkActive="router-link-active" >Reactive</a> </li> </ul> <router-outlet></router-outlet> |
[tabby title=”app.component.ts”]
1 2 3 4 5 6 7 8 9 10 11 | import { Component} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { } |
[tabby title=”app.module.ts”]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { TemplateComponent } from './template-component'; import { ReactiveComponent } from './reactive.component'; @NgModule({ declarations: [ AppComponent,TemplateComponent,ReactiveComponent ], imports: [ BrowserModule, AppRoutingModule, FormsModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
[tabbyending]
StatusChanges in Template Driven Forms
StatusChanges
event can also be used in the template-driven forms. All you need to do is to get the reference to the Form Model in the component as shown below
1 2 3 | @ViewChild('templateForm',null) templateForm: NgForm; |
You can refer to the example code below
[tabby title=”template-component.ts”]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | import { Component, ViewChild, ElementRef, OnInit, OnDestroy } from '@angular/core'; import { NgForm } from '@angular/forms'; @Component({ templateUrl: './template.component.html', }) export class TemplateComponent implements OnInit { title = 'Template driven forms'; @ViewChild('templateForm',null) templateForm: NgForm; contact: contact; onSubmit() { console.log(this.templateForm.value); } ngOnInit() { setTimeout(() => { this.templateForm.control.get("firstname").statusChanges.subscribe(newStatus=> { console.log('firstname status changed') console.log(newStatus) console.log(this.templateForm.control.get("firstname").status) console.log(this.templateForm.control.status) setTimeout(() => { console.log(this.templateForm.control.status) }) }) this.templateForm.control.get("address").statusChanges.subscribe(newStatus => { console.log('address status changed') console.log(newStatus) }) this.templateForm.control.statusChanges.subscribe(newStatus=> { console.log('form status changed') console.log(newStatus) }) }); } setValue() { let contact = { firstname: "Rahul", lastname: "Dravid", address: { city: "Bangalore", street: "Brigade Road", pincode: "600070" } }; this.templateForm.setValue(contact); } setAddress() { let address= { city: "Bangalore", street: "Brigade Road", pincode: "600070" }; this.templateForm.control.get("address").setValue(address); }; setFirstname() { this.templateForm.control.get("firstname").setValue("Saurav") } withoutOnlySelf() { this.templateForm.control.get("firstname").setValue(""); } withOnlySelf() { this.templateForm.control.get("firstname").setValue("", { onlySelf: true }); } withouEmitEvent() { this.templateForm.control.get("firstname").setValue("Sachin"); } withEmitEvent() { this.templateForm.control.get("firstname").setValue("", { emitEvent: false }); } reset() { this.templateForm.reset(); } } export class contact { firstname:string; lastname:string; gender:string; email:string; isMarried:boolean; country:string; address: { city:string; street:string; pincode:string; } } |
[tabby title=”template-component.html”]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | <h3>{{title}}</h3> <div style="float: left; width:50%;"> <form #templateForm="ngForm" (ngSubmit)="onSubmit(templateForm)"> <p> <label for="firstname">First Name </label> <input type="text" id="firstname" name="firstname" #fname="ngModel" ngModel> </p> <p> <label for="lastname">Last Name </label> <input type="text" id="lastname" name="lastname" ngModel> </p> <div ngModelGroup="address"> <p> <label for="city">City</label> <input type="text" id="city" name="city" ngModel> <label for="street">Street</label> <input type="text" id="street" name="street" ngModel> <label for="pincode">Pin Code</label> <input type="text" id="pincode" name="pincode" ngModel> </p> </div> <button>Submit</button> </form> <div> <button type="button" (click)="setValue()">SetValue</button> <button type="button" (click)="setAddress()">Address</button> <button type="button" (click)="setFirstname()">First Name</button> </div> <div> <button type="button" (click)="withoutOnlySelf()">Without Only Self</button> <button type="button" (click)="withOnlySelf()">With Only Self</button> </div> <div> <button type="button" (click)="withouEmitEvent()">Without EmitEvent</button> <button type="button" (click)="withEmitEvent()">With EmitEvent</button> </div> </div> <div style="float: right; width:50%;"> <h3>Form Status</h3> <b>status : </b>{{templateForm.status}} <b>valid : </b>{{templateForm.valid}} <b>invalid : </b>{{templateForm.invalid}} <b>touched : </b>{{templateForm.touched}} <b>untouched : </b>{{templateForm.untouched}} <b>pristine : </b>{{templateForm.pristine}} <b>dirty : </b>{{templateForm.dirty}} <b>disabled : </b>{{templateForm.disabled}} <b>enabled : </b>{{templateForm.enabled}} <h3>Form Value</h3> {{templateForm.value | json }} </div> |
[tabbyending]
Summary
In this tutorial, we learned how to make use of StatusChanges
in Angular Forms. The StatusChanges
event is fired whenever the angular calculates the validity status of the FormControl, FormGroup or FormArray. It is an observable and we can subscribe to it. The StatusChanges
event does not fire depending on how we set emitEvent
or onlySelf
, when updating the value and validity of the form controls.
- Angular Forms Tutorial: Fundamental & Concepts
- Template Driven Forms in Angular
- Set Value in Template Driven forms in Angular
- Reactive Forms in Angular
- FormBuilder in Reactive Forms
- SetValue & PatchValue in Angular
- StatusChanges in Angular Forms
- ValueChanges in Angular Forms
- FormControl
- FormGroup
- FormArray Example
- Build Dynamic or Nested Forms using FormArray
- Validations in Reactive Forms in Angular
- Custom Validator in Reactive Forms
- Passing Parameter to Custom Validator in Reactive Forms
- Inject Service into Custom Validator
- Validation in Template Driven Forms
- Custom Validator in Template Driven Forms