如何解决对 Angular 的自定义表单数组的验证对于异步验证器保持为 PENDING
我有一个自定义邮政编码异步验证器,它也适用于模板驱动的表单。
@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);
}
};
}
}
以及在模板驱动表单中使用表单数组的自定义实现。
@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,multi: true }
]
})
export class ArrayInputComponent implements ControlValueAccessor,AsyncValidator {
@Input() public name!: string;
@ContentChild(ArrayItemDirective,{ static: true })
public itemTemplate!: ArrayItemDirective;
public get value() {
return this.array.value;
}
private array = new FormArray([]);
public controlIds: symbol[] = [];
constructor(
@Optional() @Host() parent: ControlContainer,private changeDetector: ChangeDetectorRef
) {
if (parent) {
this.array.setParent(parent.control as any);
}
this.array.valueChanges.subscribe(value => {
this.onChange(value);
this.onTouch();
});
}
public validate() {
return combineLatest(this.array.controls.map(c => c.statusChanges)).pipe(
startWith(this.array.controls.map(c => c.status)),filter(() => !this.array.controls.some(c => c.pending)),map(() => (this.array.valid ? null : { array: true })),take(1)
);
}
private onChange: (val?: any) => void = () => {};
private onTouch: () => void = () => {};
public registerOnChange(fn: any): void {
this.onChange = fn;
}
public registerOnTouched(fn: any): void {
this.onTouch = fn;
}
public add() {
this.controlIds.push(Symbol());
this.changeDetector.detectChanges();
}
public removeAt(index: number) {
this.controlIds.splice(index,1);
this.array.removeAt(index);
this.changeDetector.detectChanges();
}
public bindItem(item: ArrayValueDirective) {
const isNew = item.index >= this.array.length;
if (!isNew) {
const value = this.array.at(item.index).value;
item.setValue(value);
}
this.array.setControl(item.index,item.control,{ emitEvent: isNew });
}
public writeValue(newArray: any[] | null): void {
if (!equal(this.value,newArray)) {
this.array.clear({ emitEvent: false });
this.controlIds.splice(0);
if (newArray) {
for (const val of newArray) {
this.array.push(new FormControl(val),{ emitEvent: false });
this.controlIds.push(Symbol());
}
}
}
this.changeDetector.detectChanges();
}
}
问题是当值改变时 ArrayInputComponent > validate
被调用,但控件的 statusChanges
从不触发,即使状态从 PENDING
变为 VALID
。因此,数组控件保留在 PENDING
中,整个表单无效。
目前我能找到的唯一解决方法是在异步验证器完成后的下一个滴答中检查父组件的有效性
...
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 });
})
);
...
我知道在某些情况下,Angular 会将 emitEvent
设置为 false
以进行更新,但也进行了一些更改以解决异步验证器 (https://github.com/profanis/angular/commit/bae96682e7d5ea1942c72583e7d68a24507c1a5a) 的问题。
有人对此有解决方案吗?
可能是我声明错误,或者我应该以其他方式检查数组的有效性?
提前致谢。
编辑:我针对这个问题做了一个 Stackblitz。 https://stackblitz.com/edit/angular-template-form-array-jf9h5v。
检查浏览器的控制台比 Stackblitz 更好。您可以看到第一个控件的 VALID 状态如何没有发出,但是如果您检查控制台中的控制日志(在扩展中进行评估),则状态为 VALID。
解决方法
添加的 Stackblitz 的这个分支解决了这个问题。
https://stackblitz.com/edit/angular-template-form-array-jf9h5v-uh3t4x
似乎对于 FormArray 的控件,statusChanges
不会发出新状态。但是数组的 statusChanges
可以。这可用于正确验证数组。
public validate() {
return this.array.statusChanges.pipe(
startWith(this.array.status),filter(() => !this.array.pending),map(() => (this.array.valid ? null : { array: true })),take(1)
);
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。