私も同じ問題を抱えていました。入力をデバウンスし、入力が変更されたときにのみバックエンドを要求するためのソリューションが必要でした。
バリデーターでタイマーを使用するすべての回避策には、キーストロークごとにバックエンドを要求するという問題があります。それらは検証応答をデバウンスするだけです。それは意図したことではありません。入力をデバウンスして区別し、その後でのみバックエンドをリクエストする必要があります。
そのための私の解決策は次のとおりです(リアクティブフォームとマテリアル2を使用):
コンポーネント
@Component({
selector: 'prefix-username',
templateUrl: './username.component.html',
styleUrls: ['./username.component.css']
})
export class UsernameComponent implements OnInit, OnDestroy {
usernameControl: FormControl;
destroyed$ = new Subject<void>(); // observes if component is destroyed
validated$: Subject<boolean>; // observes if validation responses
changed$: Subject<string>; // observes changes on username
constructor(
private fb: FormBuilder,
private service: UsernameService,
) {
this.createForm();
}
ngOnInit() {
this.changed$ = new Subject<string>();
this.changed$
// only take until component destroyed
.takeUntil(this.destroyed$)
// at this point the input gets debounced
.debounceTime(300)
// only request the backend if changed
.distinctUntilChanged()
.subscribe(username => {
this.service.isUsernameReserved(username)
.subscribe(reserved => this.validated$.next(reserved));
});
this.validated$ = new Subject<boolean>();
this.validated$.takeUntil(this.destroyed$); // only take until component not destroyed
}
ngOnDestroy(): void {
this.destroyed$.next(); // complete all listening observers
}
createForm(): void {
this.usernameControl = this.fb.control(
'',
[
Validators.required,
],
[
this.usernameValodator()
]);
}
usernameValodator(): AsyncValidatorFn {
return (c: AbstractControl) => {
const obs = this.validated$
// get a new observable
.asObservable()
// only take until component destroyed
.takeUntil(this.destroyed$)
// only take one item
.take(1)
// map the error
.map(reserved => reserved ? {reserved: true} : null);
// fire the changed value of control
this.changed$.next(c.value);
return obs;
}
}
}
テンプレート
<mat-form-field>
<input
type="text"
placeholder="Username"
matInput
formControlName="username"
required/>
<mat-hint align="end">Your username</mat-hint>
</mat-form-field>
<ng-template ngProjectAs="mat-error" bind-ngIf="usernameControl.invalid && (usernameControl.dirty || usernameControl.touched) && usernameControl.errors.reserved">
<mat-error>Sorry, you can't use this username</mat-error>
</ng-template>