Angular Form Validation
- 在 form.js 中参考这个方法,validator/asyncValidator 都被封装了一层。把多个 validator 方法合并成一个,然后通过forkjoin,将多个异步合并成一个,这个地方有个坑,asyncValidator 返回的虽然是
AsyncValidatorFn|AsyncValidatorFn[]
,但是它是假设这个函数返回的是 Promise/Observable,Promise 比较简单,Observable 则必须是 compeleted 状态。jorkjoin 相当于 Promise.all(), 如果你的 Observables, 是来自于一个 Subject(Rxjs),记住一定要把它变成 completed 状态。如何变,可以采用 pipe(take(1)), 或者直接 subject.complete(), 否则,asyncValidator 不会拿到 errors. 切记切记。
export declare interface AsyncValidatorFn {
(control: AbstractControl):
| Promise<ValidationErrors | null>
| Observable<ValidationErrors | null>;
}
下面的函数该怎么看呢,首先是 setUpControl, 它会将 validator/asyncValidator 封装,可以递归方法下去,方法都在下面清单里面。
当我们的 formControl 的值更新了,那么会首先调用 updateValueAndValidity()
,
- 这个方法里面先取消之前未完成的异步,
this._cancelExistingSubscription();
, - 然后执行同步的 validaiton,
this._runValidator()
,这个里面的this.validator
就是static compose(validators)
的返回值,this
就是 control - 然后计算 validaiton status, 只有同步的 validation 通过了,或者 status===PENDING,才会进行异步的 validaiton 验证。节省资源。
if (this.status === VALID || this.status === PENDING) {
this._runAsyncValidator(opts.emitEvent);
}
function setUpControl(control, dir) {
//xxx 省略若干
control.validator = Validators.compose([
/** @type {?} */ (control.validator),
dir.validator,
]);
control.asyncValidator = Validators.composeAsync([
/** @type {?} */ (control.asyncValidator),
dir.asyncValidator,
]);
//xxx 省略若干
}
static compose(validators) {
if (!validators)
return null;
/** @type {?} */
const presentValidators = (/** @type {?} */ (validators.filter(isPresent)));
if (presentValidators.length == 0)
return null;
return (/**
* @param {?} control
* @return {?}
*/
function (control) {
return _mergeErrors(_executeValidators(control, presentValidators));
});
}
static composeAsync(validators) {
if (!validators)
return null;
/** @type {?} */
const presentValidators = (/** @type {?} */ (validators.filter(isPresent)));
if (presentValidators.length == 0)
return null;
return (/**
* @param {?} control
* @return {?}
*/
function (control) {
/** @type {?} */
const observables = _executeAsyncValidators(control, presentValidators).map(toObservable);
return forkJoin(observables).pipe(map(_mergeErrors));
});
}
/**
* @param {?} control
* @param {?} validators
* @return {?}
*/
function _executeValidators(control, validators) {
return validators.map((/**
* @param {?} v
* @return {?}
*/
v => v(control)));
}
/**
* @param {?} control
* @param {?} validators
* @return {?}
*/
function _executeAsyncValidators(control, validators) {
return validators.map((/**
* @param {?} v
* @return {?}
*/
v => v(control)));
}
/**
* @param {?} arrayOfErrors
* @return {?}
*/
function _mergeErrors(arrayOfErrors) {
/** @type {?} */
let res = {};
// Not using Array.reduce here due to a Chrome 80 bug
// https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
arrayOfErrors.forEach((/**
* @param {?} errors
* @return {?}
*/
(errors) => {
res = errors != null ? Object.assign(Object.assign({}, (/** @type {?} */ (res))), errors) : (/** @type {?} */ (res));
}));
return Object.keys(res).length === 0 ? null : res;
}
_runValidator() {
return this.validator ? this.validator(this) : null;
}
/**
* @private
* @param {?=} emitEvent
* @return {?}
*/
_runAsyncValidator(emitEvent) {
if (this.asyncValidator) {
((/** @type {?} */ (this))).status = PENDING;
/** @type {?} */
const obs = toObservable(this.asyncValidator(this));
this._asyncValidationSubscription =
obs.subscribe((/**
* @param {?} errors
* @return {?}
*/
(errors) => this.setErrors(errors, { emitEvent })));
}
}
/**
* @private
* @return {?}
*/
_cancelExistingSubscription() {
if (this._asyncValidationSubscription) {
this._asyncValidationSubscription.unsubscribe();
}
}
setErrors(errors, opts = {}) {
((/** @type {?} */ (this))).errors = errors;
this._updateControlsErrors(opts.emitEvent !== false);
}
updateValueAndValidity(opts = {}) {
this._setInitialStatus();
this._updateValue();
if (this.enabled) {
this._cancelExistingSubscription();
((/** @type {?} */ (this))).errors = this._runValidator();
((/** @type {?} */ (this))).status = this._calculateStatus();
if (this.status === VALID || this.status === PENDING) {
this._runAsyncValidator(opts.emitEvent);
}
}
if (opts.emitEvent !== false) {
((/** @type {?} */ (this.valueChanges))).emit(this.value);
((/** @type {?} */ (this.statusChanges))).emit(this.status);
}
if (this._parent && !opts.onlySelf) {
this._parent.updateValueAndValidity(opts);
}
}