Angular valueChanges and statusChanges
January 03, 2024
This page will walk through Angular valueChanges
and statusChanges
properties of FormControl
, FormArray
and FormGroup
classes.
1.
valueChanges
and statusChanges
both return Observable
instance and we can subscribe them to get respective data.
2.
valueChanges
emits an event every time when the value of control changes either using UI or programmatically. valueChanges
gives current value of a control. It can be used for conditional validation in reactive form.
3.
statusChanges
emits an event every time when the validation status of the control is recalculated. statusChanges
gives current validation status of a control.
Here on this page we will create a reactive form and provide examples for
valueChanges
and statusChanges
using FormControl
, FormArray
and FormGroup
classes.
I will use following reactive form used in our example.
constructor(private formBuilder:FormBuilder) {} 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]) ]) });
Contents
1. valueChanges using FormControl, FormArray and FormGroup
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.
valueChanges
example :
1. Find the sample code for
valueChanges
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.
2. statusChanges using FormControl, FormArray and FormGroup
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.
statusChanges
example :
1. Find the sample code for
statusChanges
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.
3. 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.
4. 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, ReactiveFormsModule, FormArray } from '@angular/forms'; import { User } from './user'; import { pwdMatchUsernameValidator } from './custom-validators/pwd-match-username-validator'; import { confirmPasswordValidator } from './custom-validators/confirm-password-validator'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-reactive', standalone: true, imports: [CommonModule, ReactiveFormsModule], 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.controls['favoriteLocations'] as FormArray; } }
<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 interface 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; }
5. 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
6. References
AbstractControlFormControl
FormArray
FormGroup