Have been using Angular 13 in my application and have a scenario where I have to remove a required field validation on value changes. Find this stackblitz for a demo of what I am trying to achieve.
In the demo atleast one field is required. If I add firstname, validation on nickname should be removed or vice versa.
Have tried using the updateValueandValidity method but that doesn't seem to work as well. If I do not pass any opts on the updateValueandValidity method, Maximum Call stack error appears as valuechanges is called infinitely. Below is code from stackblitz.
HTML:
<h1>Mandatory to add atleast one name</h1>
<form [formGroup]="registerForm">
<div >
<mat-form-field>
<mat-label>First Name</mat-label>
<input matInput formControlName="firstName" />
</mat-form-field>
</div>
<div >
<mat-form-field>
<mat-label>Nick Name</mat-label>
<input matInput formControlName="nickName" />
</mat-form-field>
</div>
</form>
TS:
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
public registerForm: FormGroup;
ngOnInit() {
this.registerForm = new FormGroup({
firstName: new FormControl('', Validators.required),
nickName: new FormControl('', Validators.required),
});
this.toggleRequiredValidationOnInput();
}
toggleRequiredValidationOnInput() {
Object.keys(this.registerForm.controls).forEach((field)=>{
this.registerForm.get(field)?.valueChanges.subscribe(()=> {
let isMandatory = false;
isMandatory = !Object.keys(this.registerForm.controls).some((field) => {
return this.registerForm.get(field)?.value;
});
this.updateFieldWithMandatoryCheck(isMandatory);
});
});
}
updateFieldWithMandatoryCheck(isMandatory) {
Object.keys(this.registerForm.controls).forEach((field) => {
if (isMandatory) { this.registerForm.get(field).addValidators(Validators.required);
} else {
this.registerForm.get(field).removeValidators(Validators.required);
}
this.registerForm
.get(field)
.updateValueAndValidity({ onlySelf: true, emitEvent: false });
});
}
}
CodePudding user response:
Instead of adding/removing the validators manually, you could put a custom cross-field-validator function onto the FormGroup
, like this:
private checkAtLeastOneRequired(form: FormGroup): ValidationErrors {
const ffFirstName: AbstractControl = form.get('firstName');
const ffNickName: AbstractControl = form.get('nickName');
let errors: ValidationErrors = null;
if (!ffFirstName.value && !ffNickName.value) {
errors = { atLeastOneRequired: true };
}
ffFirstName.setErrors(errors);
ffNickName.setErrors(errors);
return errors;
}
ngOnInit(): void {
this.registerForm = new FormGroup({
firstName: new FormControl(''),
nickName: new FormControl(''),
}, this.checkAtLeastOneRequired);
}
CodePudding user response:
Ok, here is what I did. The trick to breaking the loop was using the distinctUntilChanged
pipe. See the demo here
ngOnInit() {
this.registerForm = new FormGroup({
firstName: new FormControl('', Validators.required),
nickName: new FormControl('', Validators.required),
});
this.registerForm
.get('firstName')
.valueChanges.pipe(takeUntil(this.destroy$), distinctUntilChanged())
.subscribe({
next: (val) => {
if (val) {
this.registerForm.get('nickName').clearValidators();
this.registerForm.get('nickName').updateValueAndValidity();
} else {
this.registerForm
.get('nickName')
.setValidators([Validators.required]);
this.registerForm.get('nickName').updateValueAndValidity();
}
},
});
this.registerForm
.get('nickName')
.valueChanges.pipe(takeUntil(this.destroy$), distinctUntilChanged())
.subscribe({
next: (val) => {
if (val) {
this.registerForm.get('firstName').clearValidators();
this.registerForm.get('firstName').updateValueAndValidity();
} else {
this.registerForm
.get('firstName')
.setValidators([Validators.required]);
this.registerForm.get('firstName').updateValueAndValidity();
}
},
});
}