如何将验证器添加到订阅的自定义输入?
How to add Validator to Custom Input from Subscribe?
我有一个用户表单,其中必须检查两个输入 'Username' 和 'Email' 是否重复。 ▼
this.userForm = this.formBuilder.group({
username: [
this.isEdit ? this.data.user?.nameIdentifier : null,
Validators.required
],
email: [
this.isEdit ? this.data.user?.email : null,
[Validators.required, Validators.email]
]
});
我有自定义验证器,它需要加载异步数据,然后使用该数据检查重复项。因此,我将该验证器置于订阅中,并在异步数据加载时将该验证设置为表单控件。 ▼
this.userService
.getUsers({ pageSize: 5000 })
.pipe(takeUntil(this.destroy$))
.subscribe((users: IGetUsersRes) => {
this.userForm
.get('username')
?.setValidators(
duplicateNameValidator(
users.list, // this one
['nameIdentifier'],
this.data.user
)
);
this.userForm
.get('email')
?.setValidators(
duplicateNameValidator(
users.list, // this one
['email'],
this.data.user
)
);
});
这在当时有效,但现在当我切换到我的自定义输入时,来自订阅的验证不会分配给自定义输入(可能是因为它处于异步状态)。我的自定义输入以这种方式接受父表单控件验证 ▼
export class InputComponent implements OnInit, OnDestroy, ControlValueAccessor {
inputControl = new FormControl();
isDisabled!: boolean;
@Input() debounce = false;
onChange: any = () => {
// any
};
onTouched: any = () => {
// any
};
constructor(@Self() @Optional() public ngControl: NgControl) {
this.ngControl && (this.ngControl.valueAccessor = this);
}
ngOnInit(): void {
this.updateInputValue();
this.setValidators();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
setValidators(): void {
const control = this.ngControl.control;
const validators: ValidatorFn[] = control?.validator
? [control?.validator]
: [];
this.inputControl.setValidators(validators);
}
updateInputValue(): void {
this.inputControl.valueChanges
.pipe(
takeUntil(this.destroy$),
debounceTime(this.debounce ? 500 : 0),
distinctUntilChanged()
)
.subscribe((res) => {
this.onChange(res);
});
}
clearInput(): void {
this.inputControl.reset(null, { emitEvent: false });
this.onChange();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
writeValue(value: any): void {
this.inputControl.setValue(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(disabled: boolean): void {
this.isDisabled = disabled;
}
}
我不确定我做的每件事都是正确的,所以有人知道如何解决这个问题吗?我认为父控件验证是在 ngOnInit 上分配的,但由于我的 duplicateNameValidator() 处于异步状态,因此需要额外处理,而且我不知道如何在我的自定义输入中查看它。在 Google 中搜索结果不佳,google 被误导。
如果你想用响应式表单做一些异步的事情,我们有专门的异步验证器!所以首先我会使用它,所以我建议以下...
创建验证函数。我在这里传递表单控件名称和 http 请求结果的可观察值作为参数。为了获取我们在组件中分配的可观察对象,附加一个 shareReplay
以便只发出一次请求。
在这里,我直接从我的组件中懒惰地使用 http,像您已经在做的那样使用服务 ;)
users$ = this.http.get('https://jsonplaceholder.typicode.com/users').pipe(
shareReplay(1)
);
然后我们将 user$
传递给异步验证器。然后我们以与自定义同步验证器类似的方式使用此验证器,但异步验证器需要是第三个参数:
this.reactiveForm = this.fb.group({
username: ['', [], [validate('username', this.users$)]],
email: ['', [], [validate('email', this.users$)]],
});
验证函数:
export function validate(ctrlName: string, result$): ValidatorFn {
return (ctrl: AbstractControl): ValidationErrors | null => {
if (ctrl) {
return result$.pipe(
map((values: any) => {
const found = values.find((x) => x[ctrlName].toLowerCase() === ctrl.value.toLowerCase());
if (found) {
return { notValid: true };
}
return null;
}),
catchError(() => of(null))
);
}
return of(null);
};
}
因此,当我们将控件名称作为字符串传递时,这个验证器将同时适用于您的用户名和电子邮件。
然后您可以在自定义组件中像任何其他同步验证器一样显示此错误:
<input type="text" [formControl]="$any(control)">
<small *ngIf="control.hasError('notValid')">Value already taken!</small>
这是一个 STACKBLITZ 上面的代码 - 也使用自定义输入。
我有一个用户表单,其中必须检查两个输入 'Username' 和 'Email' 是否重复。 ▼
this.userForm = this.formBuilder.group({
username: [
this.isEdit ? this.data.user?.nameIdentifier : null,
Validators.required
],
email: [
this.isEdit ? this.data.user?.email : null,
[Validators.required, Validators.email]
]
});
我有自定义验证器,它需要加载异步数据,然后使用该数据检查重复项。因此,我将该验证器置于订阅中,并在异步数据加载时将该验证设置为表单控件。 ▼
this.userService
.getUsers({ pageSize: 5000 })
.pipe(takeUntil(this.destroy$))
.subscribe((users: IGetUsersRes) => {
this.userForm
.get('username')
?.setValidators(
duplicateNameValidator(
users.list, // this one
['nameIdentifier'],
this.data.user
)
);
this.userForm
.get('email')
?.setValidators(
duplicateNameValidator(
users.list, // this one
['email'],
this.data.user
)
);
});
这在当时有效,但现在当我切换到我的自定义输入时,来自订阅的验证不会分配给自定义输入(可能是因为它处于异步状态)。我的自定义输入以这种方式接受父表单控件验证 ▼
export class InputComponent implements OnInit, OnDestroy, ControlValueAccessor {
inputControl = new FormControl();
isDisabled!: boolean;
@Input() debounce = false;
onChange: any = () => {
// any
};
onTouched: any = () => {
// any
};
constructor(@Self() @Optional() public ngControl: NgControl) {
this.ngControl && (this.ngControl.valueAccessor = this);
}
ngOnInit(): void {
this.updateInputValue();
this.setValidators();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
setValidators(): void {
const control = this.ngControl.control;
const validators: ValidatorFn[] = control?.validator
? [control?.validator]
: [];
this.inputControl.setValidators(validators);
}
updateInputValue(): void {
this.inputControl.valueChanges
.pipe(
takeUntil(this.destroy$),
debounceTime(this.debounce ? 500 : 0),
distinctUntilChanged()
)
.subscribe((res) => {
this.onChange(res);
});
}
clearInput(): void {
this.inputControl.reset(null, { emitEvent: false });
this.onChange();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
writeValue(value: any): void {
this.inputControl.setValue(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(disabled: boolean): void {
this.isDisabled = disabled;
}
}
我不确定我做的每件事都是正确的,所以有人知道如何解决这个问题吗?我认为父控件验证是在 ngOnInit 上分配的,但由于我的 duplicateNameValidator() 处于异步状态,因此需要额外处理,而且我不知道如何在我的自定义输入中查看它。在 Google 中搜索结果不佳,google 被误导。
如果你想用响应式表单做一些异步的事情,我们有专门的异步验证器!所以首先我会使用它,所以我建议以下...
创建验证函数。我在这里传递表单控件名称和 http 请求结果的可观察值作为参数。为了获取我们在组件中分配的可观察对象,附加一个 shareReplay
以便只发出一次请求。
在这里,我直接从我的组件中懒惰地使用 http,像您已经在做的那样使用服务 ;)
users$ = this.http.get('https://jsonplaceholder.typicode.com/users').pipe(
shareReplay(1)
);
然后我们将 user$
传递给异步验证器。然后我们以与自定义同步验证器类似的方式使用此验证器,但异步验证器需要是第三个参数:
this.reactiveForm = this.fb.group({
username: ['', [], [validate('username', this.users$)]],
email: ['', [], [validate('email', this.users$)]],
});
验证函数:
export function validate(ctrlName: string, result$): ValidatorFn {
return (ctrl: AbstractControl): ValidationErrors | null => {
if (ctrl) {
return result$.pipe(
map((values: any) => {
const found = values.find((x) => x[ctrlName].toLowerCase() === ctrl.value.toLowerCase());
if (found) {
return { notValid: true };
}
return null;
}),
catchError(() => of(null))
);
}
return of(null);
};
}
因此,当我们将控件名称作为字符串传递时,这个验证器将同时适用于您的用户名和电子邮件。
然后您可以在自定义组件中像任何其他同步验证器一样显示此错误:
<input type="text" [formControl]="$any(control)">
<small *ngIf="control.hasError('notValid')">Value already taken!</small>
这是一个 STACKBLITZ 上面的代码 - 也使用自定义输入。