Angular valueChanges and statusChanges
July 24, 2018
This page will walk through Angular valueChanges
and statusChanges
properties of FormControl
, FormArray
and FormGroup
classes. valueChanges
and statusChanges
both return Observable
instance and we can subscribe them to get data. valueChanges
emits an event every time when the value of control changes either using UI or programmatically. statusChanges
emits an event every time when the validation status of the control is recalculated. statusChanges
gives current validation status of a control. valueChanges
gives current value of a control. valueChanges
can be used for conditional validation in reactive form. Here on this page we will create a reactive user form and provide examples for valueChanges
and statusChanges
using FormControl
, FormArray
and FormGroup
classes.
Contents
Technologies Used
Find the technologies being used in our example.1. Angular 6.0.3
2. Angular CLI 6.0.3
3. TypeScript 2.7.2
4. Node.js 10.3.0
5. NPM 6.1.0
valueChanges
valueChanges
is a property of AbstractControl
that emits an event every time when the value of control changes either using UI or programmatically. valueChanges
property is available in FormControl
, FormArray
and FormGroup
classes because they inherit AbstractControl
class. valueChanges
property has been declared as following.
get valueChanges: Observable<any>
Observable
of any
type. We can subscribe valueChanges
to get data.
1. If we subscribe
valueChanges
of a FormControl
instance, we get latest value of that control whenever there is any change.
2. If we subscribe
valueChanges
of a FormArray
instance, we get latest value of those array controls whenever there is any change.
3. If we subscribe
valueChanges
of a FormGroup
instance, we get latest value of the form controls whenever there is a change in any control of the form.
statusChanges
statusChanges
is a property of AbstractControl
that emits an event every time when the validation status of the control is recalculated. statusChanges
property is available in FormControl
, FormArray
and FormGroup
classes because they inherit AbstractControl
class. statusChanges
property has been declared as following.
get statusChanges: Observable<any>
statusChanges
is Observable
of any
type.
1. If we subscribe
statusChanges
of a FormControl
instance, we get latest validation status of that control whenever validation status is recalculated for that control.
2. If we subscribe
statusChanges
of a FormArray
instance, we get latest validation status of those array controls whenever validation status is recalculated for those array controls.
3. If we subscribe
statusChanges
of a FormGroup
instance, we get latest validation status of the form controls whenever validation status is recalculated in any control of the form.
Create a Reactive Form
We will create a reactive user form for our demo usingFormBuilder
.
userForm: FormGroup; constructor(private formBuilder:FormBuilder) { }
this.userForm = this.formBuilder.group({ username: ['', [ Validators.required ]], password: ['', [ Validators.required ]], confirmPassword: ['', [ Validators.required ]], notificationMode: ['', [ Validators.required ]], email: '', mobileNumber: '', favoriteLocations: this.formBuilder.array([ this.formBuilder.control('', [Validators.required]), this.formBuilder.control('', [Validators.required]) ]) });
valueChanges of FormControl, FormArray and FormGroup
1. Find the sample code forvalueChanges
with FormControl
this.userForm.get('username').valueChanges.subscribe( uname => { console.log('Username changed:' + uname); } );
get username() { return this.userForm.get('username'); }
valueChanges
as given below.
this.username.valueChanges.subscribe( uname => { console.log('Username changed:' + uname); } );
username
control changes.
2. Find the sample code for
valueChanges
with FormArray
this.userForm.get('favoriteLocations').valueChanges.subscribe( data => { console.log('favoriteLocations: ' + data); } );
get favoriteLocations() { return this.userForm.get('favoriteLocations'); }
valueChanges
as given below.
this.favoriteLocations.valueChanges.subscribe( data => { console.log('favoriteLocations: ' + data); } );
favoriteLocations
control changes.
3. Find the sample code for
valueChanges
with FormGroup
this.userForm.valueChanges.subscribe((user: User) => { console.log('username: '+ user.username); console.log('Password: '+ user.password); });
userForm
, above code will execute.
statusChanges of FormControl, FormArray and FormGroup
1. Find the sample code forstatusChanges
with FormControl
this.userForm.get('username').statusChanges.subscribe( status => { console.log('Username validation status: '+ status); } );
get username() { return this.userForm.get('username'); }
statusChanges
as given below.
this.username.statusChanges.subscribe( status => { console.log('Username validation status: '+ status); } );
username
is recalculated.
2. Find the sample code for
statusChanges
with FormArray
this.userForm.get('favoriteLocations').statusChanges.subscribe( status => { console.log('favoriteLocations validation status: ' + status); } );
get favoriteLocations() { return this.userForm.get('favoriteLocations'); }
statusChanges
as given below.
this.favoriteLocations.statusChanges.subscribe( status => { console.log('favoriteLocations validation status: ' + status); } );
favoriteLocations
is recalculated.
3. Find the sample code for
statusChanges
with FormGroup
this.userForm.statusChanges.subscribe(status => { console.log('Form validation status: '+ status); });
userForm
validation status is recalculated.
Conditional Validation using valueChanges
We can perform conditional validation usingvalueChanges
property in reactive form. For the demo, suppose we have password and confirm password fields in our user form. The confirm password must match with password and if the value in password field changes, the confirm password must be recalculated.
this.password.valueChanges.subscribe( pwd => { this.confirmPassword.setValidators([Validators.required, confirmPasswordValidator(pwd)]); this.confirmPassword.updateValueAndValidity(); } ); this.confirmPassword.valueChanges.subscribe( () => { const pwd = this.password.value; this.confirmPassword.setValidators([Validators.required, confirmPasswordValidator(pwd)]); } );
confirmPasswordValidator
is a custom validator that validates if password and confirm password are matched or not.
Complete Example
Find the project structure of our demo application.my-app | |--src | | | |--app | | | | | |--custom-validators | | | | | | | |--confirm-password-validator.ts | | | |--pwd-match-username-validator.ts | | | | | |--reactive-form.component.ts | | |--reactive-form.component.html | | |--user.ts | | | | | |--app.component.ts | | |--app.module.ts | | | |--main.ts | |--index.html | |--styles.css | |--node_modules |--package.json
reactive-form.component.ts
import { Component, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { Observable } from 'rxjs'; import { User } from './user'; import { pwdMatchUsernameValidator } from './custom-validators/pwd-match-username-validator'; import { confirmPasswordValidator } from './custom-validators/confirm-password-validator'; @Component({ selector: 'app-reactive-form', templateUrl: './reactive-form.component.html' }) export class ReactiveFormComponent implements OnInit { userForm: FormGroup; constructor(private formBuilder:FormBuilder) { } ngOnInit() { this.userForm = this.formBuilder.group({ username: ['', [ Validators.required ]], password: ['', [ Validators.required ]], confirmPassword: ['', [ Validators.required ]], notificationMode: ['', [ Validators.required ]], email: '', mobileNumber: '', favoriteLocations: this.formBuilder.array([ this.formBuilder.control('', [Validators.required]), this.formBuilder.control('', [Validators.required]) ]) }); this.handleFormChanges(); } handleFormChanges() { this.userForm.valueChanges.subscribe((user: User) => { console.log('----Form Data---'); console.log('username: '+ user.username); console.log('password: '+ user.password); console.log('notificationMode: '+ user.notificationMode); console.log('email: '+ user.email); console.log('mobileNumber: '+ user.mobileNumber); console.log('favoriteLocations: '+ user.favoriteLocations); console.log('----End Form Data---'); }); this.userForm.statusChanges.subscribe(status => { console.log('Form validation status: '+ status); }); this.username.statusChanges.subscribe( status => { console.log('Username validation status: '+ status); } ); this.username.valueChanges.subscribe( uname => { this.password.setValidators([Validators.required, pwdMatchUsernameValidator(uname)]); this.password.updateValueAndValidity(); } ); this.password.valueChanges.subscribe( pwd => { const uname = this.username.value; this.password.setValidators([Validators.required, pwdMatchUsernameValidator(uname)]); this.confirmPassword.setValidators([Validators.required, confirmPasswordValidator(pwd)]); this.confirmPassword.updateValueAndValidity(); } ); this.confirmPassword.valueChanges.subscribe( () => { const pwd = this.password.value; this.confirmPassword.setValidators([Validators.required, confirmPasswordValidator(pwd)]); } ); this.notificationMode.valueChanges.subscribe( mode => { console.log('NotificationMode Mode:'+ mode); if (mode==='email') { this.email.setValidators([Validators.required, Validators.email]); this.mobileNumber.clearValidators(); } else if (mode === 'mobile') { this.mobileNumber.setValidators([Validators.required]); this.email.clearValidators(); } else if (mode==='both') { this.email.setValidators([Validators.required, Validators.email]); this.mobileNumber.setValidators([Validators.required]); } this.email.updateValueAndValidity(); this.mobileNumber.updateValueAndValidity(); } ); this.favoriteLocations.valueChanges.subscribe( data => { console.log('favoriteLocations: ' + data); } ); this.favoriteLocations.statusChanges.subscribe( status => { console.log('favoriteLocations validation status: ' + status); } ); } onFormSubmit() { console.log(this.userForm.value); this.userForm.reset(); } get username() { return this.userForm.get('username'); } get password() { return this.userForm.get('password'); } get confirmPassword() { return this.userForm.get('confirmPassword'); } get email() { return this.userForm.get('email'); } get mobileNumber() { return this.userForm.get('mobileNumber'); } get notificationMode() { return this.userForm.get('notificationMode'); } get favoriteLocations() { return this.userForm.get('favoriteLocations'); } }
<h3>User Form</h3> <form [formGroup]="userForm" (ngSubmit)="onFormSubmit()"> <table> <tr> <td>Username: *</td> <td> <input formControlName="username"> <div *ngIf="username.dirty && username.errors" class = "error"> <div *ngIf="username.errors.required"> Username required. </div> </div> </td> </tr> <tr> <td>Password: *</td> <td> <input formControlName="password" type="password"> <div *ngIf="password.dirty && password.errors" class = "error"> <div *ngIf="password.errors.required"> Password required. </div> <div *ngIf="password.errors.matchForUsername"> Password can not be same as Username. </div> </div> </td> </tr> <tr> <td>Confirm Password: *</td> <td> <input formControlName="confirmPassword" type="password"> <div *ngIf="confirmPassword.dirty && confirmPassword.errors" class = "error"> <div *ngIf="confirmPassword.errors.required"> Confirm Password required. </div> <div *ngIf="confirmPassword.errors.cnfPassword"> Confirm Password not matched. </div> </div> </td> </tr> <tr> <td>Notification Mode: *</td> <td> <input type="radio" value="email" formControlName="notificationMode"> Email <input type="radio" value="mobile" formControlName="notificationMode"> Mobile <input type="radio" value="both" formControlName="notificationMode"> Both </td> </tr> <tr> <td>Email: </td> <td> <input formControlName="email"> <div *ngIf="email.dirty && email.errors" class = "error"> <div *ngIf="email.errors.required"> Email required. </div> <div *ngIf="email.errors.email"> Email not valid. </div> </div> </td> </tr> <tr> <td>Mobile Number: </td> <td> <input formControlName="mobileNumber"> <div *ngIf="mobileNumber.dirty && mobileNumber.errors" class = "error"> <div *ngIf="mobileNumber.errors.required"> Mobile number required. </div> </div> </td> </tr> <tr> <td>Favorite Locations: *</td> <td> <div formArrayName="favoriteLocations"> <div *ngFor="let loc of favoriteLocations.controls; index as i"> <input [formControlName]="i"> </div> </div> </td> </tr> <tr> <td colspan="2"> <button [disabled]="userForm.invalid">Submit</button> </td> </tr> </table> </form>
export class User { username: string; password: string; notificationMode: string; email: string; mobileNumber: string; favoriteLocations: string[]; }
import { Directive } from '@angular/core'; import { AbstractControl, ValidatorFn } from '@angular/forms'; export function confirmPasswordValidator(confirmPassword: String): ValidatorFn { return (control: AbstractControl): {[key: string]: any} | null => { let password: string = control.value; let isInValid = (password !== confirmPassword) ? true : false; return isInValid ? {'cnfPassword': {value: 'Invalid'}} : null; }; }
import { Directive } from '@angular/core'; import { AbstractControl, ValidatorFn } from '@angular/forms'; export function pwdMatchUsernameValidator(username: String): ValidatorFn { return (control: AbstractControl): {[key: string]: any} | null => { let password: string = control.value; let isInValid = (password === username) ? true : false; return isInValid ? {'matchForUsername': {value: 'Invalid'}} : null; }; }
import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-reactive-form></app-reactive-form> ` }) export class AppComponent { }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { ReactiveFormComponent } from './reactive-form.component'; @NgModule({ imports: [ BrowserModule, ReactiveFormsModule, ], declarations: [ AppComponent, ReactiveFormComponent ], providers: [ ], bootstrap: [ AppComponent ] }) export class AppModule { }
table { border-collapse: collapse; } table, th, td { border: 1px solid black; } .error { color: red; } .success { color: green; }
Run Application
To run the application, find the steps.1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Run ng serve using command prompt.
4. Access the URL http://localhost:4200

References
AbstractControlFormControl
FormArray
FormGroup