Reputation: 14087
Does anyone know how to declare an ASYNC validation method ON THE CLASS of a custom field?
Right now I have a sync validator in the validate()
method:
@Component({
selector: 'my-field',
template: `<p>Some markup</p>`,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MyFieldComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => MyFieldComponent), multi: true }
]
})
export class MyFieldComponent implements ControlValueAccessor {
validate(c: FormControl) {
// return null or error
}
// Rest of the code omitted for brevity
// writeValue(), registerOnChange()...
}
But even if I add the NG_ASYNC_VALIDATORS
to the providers using the same syntax as above, it doesn't let me declare something like a validateAsync()
method.
Unless... both types of validator target the validate()
method and I need to do both my sync and async validations in this method and return one big observable (potentially wrapping multiple error keys)? I'm not too sure about this.
SIDE NOTE: What I could get to work is declaring the async validator directly in the providers, either inline with useValue
or as a separate class with useClass
. But I'd like to have it as a method on the component class with useExisting
.
Upvotes: 4
Views: 2266
Reputation: 5258
I followed the sample of the directive. But I a somehow different approach on the end since I haven't had a clue how to set up the Validator properly. It did cost me some effort to figure out how to properly set up the whole stuff. Since the question is hit many times I thought I'll post my conclusions.
I retrieve the component I'm interested in from the constructor like
constructor(@Self() private valueAccessor: RefURLcheckComponent) {}
and within in the validate
function I set the errors
from the custom component
this.valueAccessor.setErrors(ret);
The approach works well for sync and async validators at the same time. Here is a working Stackblitz
Upvotes: 0
Reputation: 214085
I don't completely understand what is your requirement but i can give you some idea that maybe will help you.
So let start with our custom field that implements ControlValueAccessor
and Validator
(sync):
@Component({
selector: 'my-field',
template: `<p>Some markup</p>`,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MyFieldComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => MyFieldComponent), multi: true }
]
})
export class MyFieldComponent implements ControlValueAccessor, Validator {
onChange = (_: any) => { };
onTouched = () => { };
constructor(private _renderer: Renderer, private _elementRef: ElementRef) { }
writeValue(value: any): void {
const normalizedValue = value == null ? '' : value;
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
}
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
validationResult: any;
validate(c: FormControl) {
this.validationResult = { 'sync': true };
return null;
}
}
After that declare directive that will implement AsyncValidator
:
@Directive({
selector: 'my-field[formControlName],my-field[ngModel]',
providers: [{
provide: NG_ASYNC_VALIDATORS,
useExisting: forwardRef(() => CustomAsyncValidator),
multi: true
}]
})
class CustomAsyncValidator implements AsyncValidator {
valueAccessor: MyFieldComponent;
constructor(@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
this.valueAccessor = valueAccessors.find(x => x.constructor === MyFieldComponent) as MyFieldComponent;
}
// the same as above. I would use it
// constructor(@Self() private valueAccessor: MyFieldComponent) {}
/**
* presents global validation result async + sync
*/
validate(control: AbstractControl): Observable<any> {
return Observable.fromPromise(fakeAsyncValidator(1000)(control)).map(asyncResult => {
return Object.assign({}, asyncResult, this.valueAccessor.validationResult);
});
}
}
function fakeAsyncValidator(timeout: number = 0) {
return (c: AbstractControl) => {
let resolve: (result: any) => void;
const promise = new Promise(res => { resolve = res; });
const res = { 'async': false };
setTimeout(() => resolve(res), timeout);
return promise;
};
}
In the directive above we use the same selector as our custom field (my-field
), provide NG_ASYNC_VALIDATORS
and also inject existing component in constructor(you can notice two options).
And finally we have validate
method in this directive that is trying to do something similar what you want.
Upvotes: 7