I've created a custom asynchronous postal code validator that can be used with Template Driven forms.
@Directive({
selector: '[appAsyncPostalCode]',
providers: [
{ provide: NG_ASYNC_VALIDATORS, useExisting: AsyncPostalCodeValidatorDirective, multi: true }
]
})
export class AsyncPostalCodeValidatorDirective implements AsyncValidator {
public validate(
control: AbstractControl
): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return this.postalCodeValidator()(control);
}
public postalCodeValidator(): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
const postalCodePattern = /^\d{5}$/;
if (control.value && postalCodePattern.test(control.value)) {
return checkPostalCodeXHR(control.value).pipe(
map(name => null),
catchError(() => {
if (control.value) {
control.markAsTouched();
}
return of({ postalCodeInvalid: true });
})
);
} else {
return of(control.value ? { postalCode: true } : null);
}
};
}
}
Additionally, I have implemented a custom solution to utilize Form Array in Template Driven forms.
@Component({
selector: 'app-array-input',
templateUrl: './array-input.component.html',
styleUrls: ['./array-input.component.scss'],
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: ArrayInputComponent, multi: true },
{ provide: NG_ASYNC_VALIDATORS, useExisting: ArrayInputComponent, multi: true }
]
})
export class ArrayInputComponent implements ControlValueAccessor, AsyncValidator {
@Input() public name!: string;
@ContentChild(ArrayItemDirective, { static: true })
public itemTemplate!: ArrayItemDirective;
// Remaining code for ArrayInputComponent implementation...
The issue arises when validate
is called upon value changes but the control's statusChanges
doesn't trigger even though the status changes. This causes the array control to remain in a pending state and renders the entire form invalid.
To workaround this issue, a temporary solution involves manually checking the validity of the parent component after the asynchronous validation completes in the next tick.
...
return checkPostalCodeXHR(control.value).pipe(
map(name => {
setTimeout(() => control.parent?.updateValueAndValidity());
return null;
}),
catchError(() => {
if (control.value) {
control.markAsTouched();
}
setTimeout(() => control.parent?.updateValueAndValidity());
return of({ postalCodeInvalid: true });
})
);
...
I'm aware of cases where Angular sets the emitEvent
to false
for updates, but some adjustments were made to address this specifically for async validators (https://github.com/profanis/angular/commit/bae96682e7d5ea1942c72583e7d68a24507c1a5a).
If anyone has a more permanent solution or insight into why this issue occurs, please share your thoughts.
It could be related to potential errors in my declarations or an alternative method to validate the array's status?
Thank you for any assistance provided.
EDIT: A reproducible example of the problem has been generated on Stackblitz. https://stackblitz.com/edit/angular-template-form-array-jf9h5v.
Please refer to the browser console for a better understanding of the issue rather than relying solely on the Stackblitz console. It showcases how the first control's VALID status isn't emitted, although it displays as valid in the evaluated log.