Angular valueChanges and statusChanges

By Arvind Rai, 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])
  ])
}); 
I am creating my demo application using Angular 17.

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> 
We can see that it returns 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);
  }
); 
If we create a getter property for our control as following
get username() {
 return this.userForm.get('username');
} 
Then we can call valueChanges as given below.
this.username.valueChanges.subscribe(
  uname => {
	console.log('Username changed:' + uname);
  }
); 
Above code will execute for every time whenever username control changes.

2. Find the sample code for valueChanges with FormArray
this.userForm.get('favoriteLocations').valueChanges.subscribe(
  data => {
	console.log('favoriteLocations: ' + data);
  }
); 
If we create a getter property for our control as following
get favoriteLocations() {
   return this.userForm.get('favoriteLocations');
} 
Then we can call valueChanges as given below.
this.favoriteLocations.valueChanges.subscribe(
  data => {
	console.log('favoriteLocations: ' + data);
  }
); 
Above code will execute for every time whenever 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);
}); 
Whenever there is change in value in any control of the 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> 
The return type of 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);
  }
); 
If we create a getter property for our control as following
get username() {
 return this.userForm.get('username');
} 
Then we can call statusChanges as given below.
this.username.statusChanges.subscribe(
  status => {
	 console.log('Username validation status: '+ status);
  }
); 
The above code will execute whenever the validation status of 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);
  }
); 
If we create a getter property for our control as following
get favoriteLocations() {
   return this.userForm.get('favoriteLocations');
} 
Then we can call statusChanges as given below.
this.favoriteLocations.statusChanges.subscribe(
  status => {
	console.log('favoriteLocations validation status: ' + status);
  }
); 
Above code will execute if validation status of favoriteLocations is recalculated.

3. Find the sample code for statusChanges with FormGroup
this.userForm.statusChanges.subscribe(status => {
   console.log('Form validation status: '+ status);
}); 
The above code executes whenever the userForm validation status is recalculated.

3. Conditional Validation using valueChanges

We can perform conditional validation using valueChanges 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 
Find the complete code.
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;
  }
} 
reactive-form.component.html
<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> 
user.ts
export interface User {
    username: string;
    password: string;
    notificationMode: string;
    email: string;
    mobileNumber: string;
    favoriteLocations: string[];
} 
confirm-password-validator.ts
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;
  };
}
pwd-match-username-validator.ts
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;
  };
} 
app.component.ts
import { Component } from '@angular/core';

@Component({
   selector: 'app-root',
   template: `
		<app-reactive-form></app-reactive-form>
             `
})
export class AppComponent {
} 
app.module.ts
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 { } 
styles.css
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
Angular valueChanges and statusChanges
In our demo application, whenever there is any change in value and validation status of form controls, we will see logs in console. We have created some conditional validations in our example and that are Username and password cannot be same, password and confirm password must match. We need to select notification type and accordingly we are required to enter data in email or mobile number or both fields. We need to enter favorite locations. Whenever we enter or delete data in form controls, we will see output in console for their current value and validation status.

6. References

AbstractControl
FormControl
FormArray
FormGroup

7. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI







©2024 concretepage.com | Privacy Policy | Contact Us