Angular自定义下拉搜索Value Accessor设置FormControl值不选中问题
解决Angular Material自定义下拉搜索控件无法选中初始值的问题
核心问题分析
- 值同步不完整:父组件设置FormControl值时,
writeValue仅更新了selectedValue,没把对应选项的文本同步到输入框,也没关联MatAutocomplete的选中状态。 - 值传递不统一:模板里
mat-option的[value]绑的是选项文本(option.Value),但选择时传的是option.Id,父组件设置的也是Id,导致值类型不匹配,无法关联正确选项。 - FormControl与ngModel混用冲突:输入框同时绑定
[formControl]和[(ngModel)],Angular中这两种绑定不能同时用,会导致值同步混乱。 - ngDoCheck操作不当:频繁触发的
ngDoCheck里直接修改searchText,可能覆盖初始值的同步逻辑。
具体修复步骤
1. 完善writeValue的同步逻辑
当父组件传入值时,不仅要更新selectedValue,还要匹配对应的选项文本,同步到输入框和内部FormControl:
writeValue(value: any): void { this.selectedValue = value; // 根据Id找到对应选项,同步文本到输入框 const selectedItem = this.itemSource.find(item => item.Id === value); if (selectedItem) { this.searchText = selectedItem.Value; this.searchTextSubject.next(selectedItem.Value); // 同步到内部FormControl,避免事件循环 if (this.formControl) { this.formControl.setValue(selectedItem.Value, { emitEvent: false }); } } else { this.searchText = ''; this.searchTextSubject.next(''); if (this.formControl) { this.formControl.setValue('', { emitEvent: false }); } } }
2. 修正模板的绑定逻辑
把mat-option的[value]改为绑定option.Id,添加displayWith函数处理选中值的显示,同时移除ngModel避免冲突:
<mat-form-field appearance="outline" class="form-field"> <mat-label>Employee</mat-label> <input matInput [placeholder]="'Search and Select...'" [formControl]="formControl" [value]="searchText" [matAutocomplete]="auto" [class.invalid]="formControl.invalid && formControl.touched" (input)="onSearchTextChange($event.target.value)" /> <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayWith"> <mat-option *ngFor="let option of filteredOptions$ | async" [value]="option.Id" (click)="selectOption(option)" > {{ option.Value }} </mat-option> </mat-autocomplete> <mat-error *ngIf="formControl.hasError('required')">Required</mat-error> </mat-form-field>
3. 添加displayWith函数
在组件中定义displayWith,让MatAutocomplete根据选中的Id显示对应的文本:
displayWith = (id: number) => { const item = this.itemSource.find(item => item.Id === id); return item ? item.Value : ''; }
4. 调整selectOption方法
确保选中选项时同步更新所有状态:
selectOption(option: any): void { this.selectedValue = option.Id; this.searchText = option.Value; this.onChange(option.Id); this.onTouched(); this.autocomplete.closePanel(); if (this.formControl) { this.formControl.setValue(option.Value, { emitEvent: false }); } }
5. 优化ngDoCheck逻辑
避免频繁修改输入值,只在选中值为null时清空文本:
ngDoCheck(): void { if (this.selectedValue == null && this.searchText !== '') { this.searchText = ''; this.searchTextSubject.next(''); } }
6. 同步初始值到内部FormControl
在setFormControl方法中,同步父组件的初始值到内部控件:
setFormControl() { const ngControl = this.injector.get(NgControl); if (ngControl instanceof FormControlName) { this.formControl = this.injector .get(FormGroupDirective) .getControl(ngControl); } else { this.formControl = (ngControl as FormControlDirective) .form as FormControl; } // 同步父组件的初始值 if (this.formControl.value != null) { this.writeValue(this.formControl.value); } }
修复后关键代码片段(组件)
@Component({ selector: 'app-dropdown-search', templateUrl: './dropdown-search.component.html', styleUrls: ['./dropdown-search.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: DropdownSearchComponent, multi: true, }, ], }) export class DropdownSearchComponent implements OnInit, ControlValueAccessor, DoCheck { @Input() itemSource: any[] = [ { Id: 1, Value: 'Tom Cruise' }, { Id: 2, Value: 'Maria Sharapova' }, { Id: 3, Value: 'James Bond' }, ]; selectedValue: any; searchText: string = ''; isDisabled: boolean = false; onChange: any = () => {}; onTouched: any = () => {}; @ViewChild(MatAutocompleteTrigger) autocomplete!: MatAutocompleteTrigger; filteredOptions$!: Observable<any[]>; private searchTextSubject = new BehaviorSubject<string>(''); formControl!: FormControl; constructor( private injector: Injector, private errorStateMatcher: ErrorStateMatcher ) {} ngOnInit(): void { this.setFormControl(); this.filteredOptions$ = this.searchTextSubject.pipe( debounceTime(100), distinctUntilChanged(), map((text) => this.filterOptions(text)) ); } ngDoCheck(): void { if (this.selectedValue == null && this.searchText !== '') { this.searchText = ''; this.searchTextSubject.next(''); } } setFormControl() { const ngControl = this.injector.get(NgControl); if (ngControl instanceof FormControlName) { this.formControl = this.injector .get(FormGroupDirective) .getControl(ngControl); } else { this.formControl = (ngControl as FormControlDirective) .form as FormControl; } if (this.formControl.value != null) { this.writeValue(this.formControl.value); } } writeValue(value: any): void { this.selectedValue = value; const selectedItem = this.itemSource.find(item => item.Id === value); if (selectedItem) { this.searchText = selectedItem.Value; this.searchTextSubject.next(selectedItem.Value); if (this.formControl) { this.formControl.setValue(selectedItem.Value, { emitEvent: false }); } } else { this.searchText = ''; this.searchTextSubject.next(''); if (this.formControl) { this.formControl.setValue('', { emitEvent: false }); } } } selectOption(option: any): void { this.selectedValue = option.Id; this.searchText = option.Value; this.onChange(option.Id); this.onTouched(); this.autocomplete.closePanel(); if (this.formControl) { this.formControl.setValue(option.Value, { emitEvent: false }); } } onSearchTextChange(text: string): void { this.searchText = text; this.searchTextSubject.next(text); } filterOptions(text: string): any[] { if (text.trim() === '') { return []; } return this.itemSource.filter((item) => item.Value.toLowerCase().includes(text.toLowerCase()) ); } displayWith = (id: number) => { const item = this.itemSource.find(item => item.Id === id); return item ? item.Value : ''; } }
内容的提问来源于stack exchange,提问作者lowzz z
相关产品推荐
相关产品推荐

