Reputation: 8451
I have a reactive form and a form control that must unique:
this.form = new FormGroup({
name: new FormControl(this.initialValue, [
Validators.required,
], this._uniqueNameValidator.bind(this)),
});
I've wrote an async validator that calls a method from my app service:
private _uniqueNameValidator(control: FormControl) {
const value = control.value.trim();
if (value === '' || value === this.initialValue) {
return of(null);
}
control.markAsTouched();
return timer(500).pipe(
switchMap(() => {
return this._appService.validateName(value, this.selectedGroupId);
}),
map(response => {
return response ? {nameTaken: true} : null;
}),
catchError(() => {
return of(null);
})
);
}
The service method is a simple http GET
that returns a boolean
value:
validateName(name: string, groupId: number): Observable<boolean> {
// ...
return this._http.get<boolean>(url);
}
Working example: https://stackblitz.com/edit/angular-8kedup
Now, in my app a have many another forms with controls that must be unique. The uniqueness is checked async, but in other services methods and in other routes. I don't want to copy the code for the validator in every component that needs an async validator for uniqueness.
The problem: I want to write a generic validator, that takes as parameters a service method that must be called in order to determine if the value is unique or not, and the initial form value.
I wrote the following function:
export function UniqueAsyncValidator(fn: any, initialValue: string): AsyncValidatorFn {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
const value = control.value.trim();
if (value === '' || value === initialValue) {
return of(null);
}
control.markAsTouched();
return timer(500).pipe(
switchMap(() => {
return fn(value);
}),
map(response => {
return response ? {nameTaken: true} : null;
}),
catchError(() => {
return of(null);
})
);
};
}
And form definition:
this.form = new FormGroup({
name: new FormControl(this.initialValue, [
Validators.required,
], UniqueAsyncValidator(this._appService.validateName, this.initialValue).bind(this)),
});
It seems that the service method is called, but the context (this
) in the service method is undefined:
validateName(name: string, groupId: number): Observable<boolean> {
// ....
console.log( 'this: ', this ); // -> returns undefined
return this._http.get<boolean>(url);
}
Example: https://stackblitz.com/edit/angular-sbbgc8
Thanks!
Upvotes: 2
Views: 733
Reputation: 13515
You're not making the same function call in both versions. Your service function expects groupId
. You should call the service with this arg in your callback.
const uniqueValidator = UniqueAsyncValidator(
value => this._appService.validateName(value, this.selectedGroupId),
this.initialValue).bind(this);
this.form = new FormGroup({
name: new FormControl(this.initialValue, [
Validators.required,
], uniqueValidator)
});
When you create the instance of UniqueAsyncValidator
, you are passing in a callback. This allows you to reuse the logic inside the validator, and inject the function that will make the call.
value => this._appService.validateName(value, this.selectedGroupId)
This is arrow function syntax. It is simply declaring an anonymous function that accepts one parameter - value
(this is being passed in by your validator) - and returns this._appService.validateName(value, this.selectedGroupId)
.
DEMO: https://stackblitz.com/edit/angular-ynmzyu
Upvotes: 2