Angular 异步验证程序和mat自动完成不一起工作
在validate函数中,我向api发出一个请求,以检查数据是否进行了验证,并且工作正常 但是如果值是一个对象,我只返回null,但这会破坏mat autocomplete(面板永远不会关闭)Angular 异步验证程序和mat自动完成不一起工作,angular,angular-components,angular-validation,form-control,mat-autocomplete,Angular,Angular Components,Angular Validation,Form Control,Mat Autocomplete,在validate函数中,我向api发出一个请求,以检查数据是否进行了验证,并且工作正常 但是如果值是一个对象,我只返回null,但这会破坏mat autocomplete(面板永远不会关闭) import{ChangeDetectionStrategy,ChangeDetectorRef,Component,可选,EventEmitter,Output,Self}来自“@angular/core”; 从'@angular/forms'导入{AbstractControl,ControlValu
import{ChangeDetectionStrategy,ChangeDetectorRef,Component,可选,EventEmitter,Output,Self}来自“@angular/core”;
从'@angular/forms'导入{AbstractControl,ControlValueAccessor,NgControl,ValidationErrors};
从“@angular/material/autocomplete”导入{matautocompletelectedevent};
从“@angular/material/core”导入{ErrorStateMatcher};
从“rxjs”导入{Observable};
从“rxjs/operators”导入{finalize,map};
从'src/app/feature/address/postalea/portal area.model'导入{postalea};
从'src/app/feature/address/postal area/postal area.service'导入{postaleaservice};
@组成部分({
选择器:'邮政编码',
templateUrl:'./邮政编码.component.html',
styleUrls:['./邮政编码.component.scss'],
changeDetection:ChangeDetectionStrategy.OnPush
})
导出类PostalComponent实现ControlValueAccessor{
stateMatcher:ErrorStateMatcher=新CtrlErrorStateMatcher();
产后$:可观察到;
@PostalAreaselected上的输出():EventEmitter=新的EventEmitter();
建造师(
私人只读邮递服务:邮递服务,
私有只读\u changeDetectorRef:changeDetectorRef,
@可选()@Self()公共ngControl:ngControl,
) {
如果(this.ngControl!=null){
//直接设置值访问器(而不是使用提供程序)以避免运行循环导入。
this.ngControl.valueAccessor=此;
}
}
恩戈尼尼特(){
this.ngControl.control.setAsyncValidators(this.validate.bind(this));
this.ngControl.control.updateValueAndValidity();
}
onTouched=(_值?:any)=>{};
onChanged=(_值?:any)=>{};
writeValue(val:string):无效{
这个.ngControl.control?.setValue(val);
}
注册变更(fn:任何):无效{
this.onChanged=fn;
}
注册人(fn:任何):无效{
this.ontoched=fn;
}
验证(控件:AbstractControl):可观察{
if(typeof control.value!=='string'){
返回null;
}
this.postalea$=this.\u postaleaservice.getpostaleas(control.value);
返回此.u postaleaservice.getpostaleas(control.value).pipe(
地图(邮资=>{
if(postalea.length==0){
返回{invalidPostalCode:true};
}否则{
返回null;
}
}),
完成(()=>{
此._changedtectorref.markForCheck();
})
);
}
显示FN(后拉里亚?:后拉里亚){
返回postalArea?postalArea.zipCode:“”;
}
onSelectionChanged(事件:MatAutocompleteSelectedEvent){
this.onpostaleaselected.emit(event.option.value);
}
}
导出类CtrlErrorStateMatcher实现ErrorStateMatcher{
isErrorState(控件:AbstractControl):布尔值{
return!!(control&&control.invalid&&control.toucted);
}
}
模板:
<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>
{{postalea.zipCode}{{postalea.name}}
我对这个问题的想法进行了重构。。因此,这里有一个有效的解决方案:)
import{ChangeDetectionStrategy,ChangeDetectorRef,Component,EventEmitter,可选,Output,Self}来自“@angular/core”;
从'@angular/forms'导入{AbstractControl,ControlValueAccessor,NgControl,ValidationErrors};
从“@angular/material/autocomplete”导入{matautocompletelectedevent};
从“@angular/material/core”导入{ErrorStateMatcher};
从'rxjs'导入{可观察的};
从“rxjs/operators”导入{debounceTime、distinctUntilChanged、finalize、map、startWith、switchMap};
从'src/app/feature/address/postalea/portal area.model'导入{postalea};
从'src/app/feature/address/postal area/postal area.service'导入{postaleaservice};
@组成部分({
选择器:“ssp邮政编码”,
templateUrl:'./邮政编码.component.html',
styleUrls:['./邮政编码.component.scss'],
changeDetection:ChangeDetectionStrategy.OnPush
})
导出类PostalComponent实现ControlValueAccessor{
stateMatcher:ErrorStateMatcher=新CtrlErrorStateMatcher();
产后$:可观察到;
@PostalAreaselected上的输出():EventEmitter=新的EventEmitter();
建造师(
私人只读邮递服务:邮递服务,
@可选()@Self()公共ngControl:ngControl,
) {
如果(this.ngControl!=null){
//直接设置值访问器(而不是使用提供程序)以避免运行循环导入。
this.ngControl.valueAccessor=此;
}
}
恩戈尼尼特(){
this.ngControl.control.setAsyncValidators(this.validate.bind(this));
this.ngControl.control.updateValueAndValidity();
this.postalea$=this.ngControl.valueChanges
.烟斗(
去BounceTime(600),
distinctUntilChanged(),
开关映射(val=>{
如果(!this.ngControl.errors){
返回this.filter(val | |“”)//this.filter(val | |“”);
}
})
);
}
onTouched=(_值?:any)=>{};
onChanged=(_值?:any)=>{};
writeValue(val:string):无效{
这个.ngControl.control?.setValue(val);
}
注册变更(fn:任何):无效{
this.onChanged=fn;
}
注册人(fn:任何):无效{
this.ontoched=fn;
}
验证(控件:AbstractControl):可观察{
返回此.postalea$.pipe(
地图(邮资=>{
if(postalea.length==0){
this.ngControl.control.setErrors({invalidPostalCode:true});
}否则{
返回null;
}
})
);
}
显示FN(后拉里亚?:后拉里亚){
返回postalArea?postalArea.zipCode:“”;
}
onSelectionChanged(事件:MatAutocompleteSelectedEvent){
this.onChanged(event.option.value.zipCode);
这是一个很好的选择
<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);
}
}