angular - 异步验证器和 mat-autocomplete 不能一起工作
问题描述
在验证函数中,我向 api 发出请求以检查数据是否验证并且工作正常。
但是,如果该值是一个对象,我只返回 null 但这会破坏 mat-autocomplete(面板永远不会关闭)。
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Optional, EventEmitter, Output, Self } from '@angular/core';
import { AbstractControl, ControlValueAccessor, NgControl, ValidationErrors } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { ErrorStateMatcher } from '@angular/material/core';
import { Observable } from 'rxjs';
import { finalize, map } from 'rxjs/operators';
import { PostalArea } from 'src/app/feature/address/postal-area/portal-area.model';
import { PostalAreaService } from 'src/app/feature/address/postal-area/postal-area.service';
@Component({
selector: 'postal-code',
templateUrl: './postal-code.component.html',
styleUrls: ['./postal-code.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PostalCodeComponent implements ControlValueAccessor {
stateMatcher: ErrorStateMatcher = new CtrlErrorStateMatcher();
postalArea$: Observable<PostalArea[]>;
@Output() onPostalAreaSelected: EventEmitter<PostalArea> = new EventEmitter();
constructor(
private readonly _postalAreaService: PostalAreaService,
private readonly _changeDetectorRef: ChangeDetectorRef,
@Optional() @Self() public ngControl: NgControl,
) {
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
ngOnInit() {
this.ngControl.control.setAsyncValidators(this.validate.bind(this));
this.ngControl.control.updateValueAndValidity();
}
onTouched = (_value?: any) => { };
onChanged = (_value?: any) => { };
writeValue(val: string): void {
this.ngControl.control?.setValue(val);
}
registerOnChange(fn: any): void {
this.onChanged = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
validate(control: AbstractControl): Observable<ValidationErrors | null> {
if (typeof control.value !== 'string') {
return null;
}
this.postalArea$ = this._postalAreaService.getPostalAreas(control.value);
return this._postalAreaService.getPostalAreas(control.value).pipe(
map(postalArea => {
if (postalArea.length === 0) {
return { invalidPostalCode: true };
} else {
return null;
}
}),
finalize(() => {
this._changeDetectorRef.markForCheck();
})
);
}
displayFn(postalArea?: PostalArea) {
return postalArea ? postalArea.zipCode : '';
}
onSelectionChanged(event: MatAutocompleteSelectedEvent) {
this.onPostalAreaSelected.emit(event.option.value);
}
}
export class CtrlErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: AbstractControl): boolean {
return !!(control && control.invalid && control.touched);
}
}
模板:
<input matInput [errorStateMatcher]="stateMatcher" [formControl]="ngControl?.control"
[matAutocomplete]="postalCodeAutoComplete" (input)="onChanged($event.target.value)" (blur)="onTouched()"
name="postal-code" />
<mat-autocomplete #postalCodeAutoComplete="matAutocomplete" [displayWith]="displayFn.bind(this)"
(optionSelected)="onSelectionChanged($event)">
<mat-option *ngFor="let postalArea of (postalArea$ | async)" [value]="postalArea">
{{ postalArea.zipCode }} {{ postalArea.name }}
</mat-option>
</mat-autocomplete>
解决方案
我对我对这个问题的看法进行了重构..所以这是一个可行的解决方案:)
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Optional, Output, Self } from '@angular/core';
import { AbstractControl, ControlValueAccessor, NgControl, ValidationErrors } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { ErrorStateMatcher } from '@angular/material/core';
import { Observable, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, finalize, map, startWith, switchMap } from 'rxjs/operators';
import { PostalArea } from 'src/app/feature/address/postal-area/portal-area.model';
import { PostalAreaService } from 'src/app/feature/address/postal-area/postal-area.service';
@Component({
selector: 'ssp-postal-code',
templateUrl: './postal-code.component.html',
styleUrls: ['./postal-code.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PostalCodeComponent implements ControlValueAccessor {
stateMatcher: ErrorStateMatcher = new CtrlErrorStateMatcher();
postalArea$: Observable<PostalArea[]>;
@Output() onPostalAreaSelected: EventEmitter<PostalArea> = new EventEmitter();
constructor(
private readonly _postalAreaService: PostalAreaService,
@Optional() @Self() public ngControl: NgControl,
) {
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
ngOnInit() {
this.ngControl.control.setAsyncValidators(this.validate.bind(this));
this.ngControl.control.updateValueAndValidity();
this.postalArea$ = this.ngControl.valueChanges
.pipe(
debounceTime(600),
distinctUntilChanged(),
switchMap(val => {
if (!this.ngControl.errors) {
return this.filter(val || ''); // this.filter(val || '');
}
})
);
}
onTouched = (_value?: any) => { };
onChanged = (_value?: any) => { };
writeValue(val: string): void {
this.ngControl.control?.setValue(val);
}
registerOnChange(fn: any): void {
this.onChanged = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
validate(control: AbstractControl): Observable<ValidationErrors | null> {
return this.postalArea$.pipe(
map(postalArea => {
if (postalArea.length === 0) {
this.ngControl.control.setErrors({ invalidPostalCode: true });
} else {
return null;
}
})
);
}
displayFn(postalArea?: PostalArea) {
return postalArea ? postalArea.zipCode : '';
}
onSelectionChanged(event: MatAutocompleteSelectedEvent) {
this.onChanged(event.option.value.zipCode);
this.onPostalAreaSelected.emit(event.option.value);
}
filter(val: string): Observable<any[]> {
return this._postalAreaService.getPostalAreas(val)
.pipe(
map(response => response.filter(option => {
return option;
}))
);
}
}
export class CtrlErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: AbstractControl): boolean {
return !!(control && control.invalid && control.touched);
}
}
推荐阅读
- python - IndexError:目标 11 超出范围。交叉熵
- python - 我在这里没有看到我的错误 - 尝试学习 Python
- javascript - 如何收集用户提交的照片?
- javascript - chart.js how to make x-axis labels position top
- r - 如何在闪亮中循环观察事件?单击多边形时更改传单中的样式
- c# - 提升表单事件
- c++ - 目标函数 CPLEX C++ 中的 CEIL
- python - 无法删除列表中的所有最大元素
- python - 在一行中使用 2 个输入将值相加
- javascript - 如何使用 multer 将文件正确上传到 fs