Angular Custom Validator: Conditional Required Fields
Angular's built-in validation features are robust, but sometimes you need more control. This is where custom validators shine. They allow you to create validation logic tailored to your specific needs, including conditionally requiring fields.
When to Use Conditional Validators
Imagine a form where the requirement of a field depends on the selection in another field. For example, in an online store checkout form, you might need the customer's billing address only if they choose a different shipping address. This is a perfect scenario for a conditional validator.
Building Your Custom Validator
Let's dive into creating a custom validator for a conditional required field:
import { FormControl, FormGroup, ValidatorFn, AbstractControl } from '@angular/forms';
// Custom validator function
export function conditionalRequiredValidator(controlName: string, dependentControlName: string): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
// Get the dependent control
const dependentControl = control.parent?.get(dependentControlName) as FormControl;
// Check if the dependent control has a value
if (dependentControl?.value && !control.value) {
// If the dependent control has a value and the current control is empty, return an error
return { 'conditionalRequired': true };
}
// Otherwise, return no error
return null;
};
}
Explanation:
conditionalRequiredValidator
function: Takes two parameters:controlName
: The name of the control being validated.dependentControlName
: The name of the control that determines the requirement.
control.parent?.get(dependentControlName)
: Accesses the dependent control within the form group.dependentControl?.value && !control.value
: Checks if the dependent control has a value and the current control is empty.{ 'conditionalRequired': true }
: Returns an error object if the conditions are met.
Integrating with Your Form
-
Declare the Validator: In your component's
Validators
array, include the custom validator:import { Component } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { conditionalRequiredValidator } from './conditional-required-validator'; @Component({ selector: 'app-my-component', templateUrl: './my-component.html', styleUrls: ['./my-component.css'] }) export class MyComponent { myForm: FormGroup; constructor(private fb: FormBuilder) { this.myForm = this.fb.group({ shippingAddress: ['', Validators.required], billingAddress: ['', [Validators.required, conditionalRequiredValidator('billingAddress', 'shippingAddress')]] }); } }
-
Handle Validation Errors: In your template, use the
formControlName
directive and display validation errors:Billing address is required when a different shipping address is provided.
Conditional Logic with Observables
For more complex scenarios, you can use RxJS observables to observe changes in the dependent control:
import { FormControl, FormGroup, ValidatorFn, AbstractControl } from '@angular/forms';
import { Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
export function conditionalRequiredValidator(controlName: string, dependentControlName: string): ValidatorFn {
return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {
// Get the dependent control as an observable
const dependentControl$: Observable = control.parent?.get(dependentControlName)?.valueChanges as Observable;
// Combine latest values of dependent control and current control
return combineLatest([dependentControl$, control.valueChanges]).pipe(
map(([dependentValue, currentValue]) => {
// Apply conditional logic based on combined values
if (dependentValue && !currentValue) {
return { 'conditionalRequired': true };
}
return null;
})
);
};
}
Key Points:
- The validator now returns an observable that emits error objects based on the combined values of the dependent and current controls.
- The
combineLatest
operator combines the latest emissions from both observables. - The
map
operator transforms the combined values to either an error object or null.
Conclusion
Custom validators empower you to implement complex and dynamic validation logic within your Angular applications. Conditional validation, in particular, provides flexibility by making field requirements dependent on other values in your form. By mastering this technique, you can build more user-friendly and robust forms that meet the specific needs of your application.