如何正确实现Angular Material Chips Autocomplete复用组件的响应式表单逻辑?
最优实现Angular Material Chips+Autocomplete复用响应式表单组件
你的方向是对的:外部传入的FormControl只负责存储选中的Chips数组,输入框单独用内部控件处理自动补全逻辑。不建议混用响应式表单和ngModel,最好用内部独立FormControl处理输入,同时让组件实现ControlValueAccessor,完美适配formControlName/formControl的使用方式。
实现步骤
1. 组件类实现ControlValueAccessor
这一步让你的组件能像原生表单控件一样被响应式表单管理,自动同步值、状态(disabled)和验证。
import { Component, Input, OnInit } from '@angular/core'; import { FormControl, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, Validator, AbstractControl, ValidationErrors } from '@angular/forms'; import { Observable, map, startWith } from 'rxjs'; interface Option { id: number; label: string; name: string; } @Component({ selector: 'app-chips-autocomplete', templateUrl: './chips-autocomplete.component.html', styleUrls: ['./chips-autocomplete.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: ChipsAutocompleteComponent }, { provide: NG_VALIDATORS, multi: true, useExisting: ChipsAutocompleteComponent } ] }) export class ChipsAutocompleteComponent implements ControlValueAccessor, Validator, OnInit { @Input() label = 'Select items'; @Input() options: Option[] = []; @Input() type = 'text'; // 内部输入框控件,处理自动补全逻辑 inputControl = new FormControl(''); // 外部绑定的选中项数组 selectedItems: Option[] = []; // 过滤后的选项Observable filteredOptions$!: Observable<Option[]>; private onChange = (value: Option[]) => {}; private onTouched = () => {}; private disabled = false; ngOnInit(): void { // 监听输入框值变化,过滤选项 this.filteredOptions$ = this.inputControl.valueChanges.pipe( startWith(''), map(value => this.filterOptions(value)) ); } private filterOptions(value: string | Option): Option[] { const filterValue = typeof value === 'string' ? value.toLowerCase() : value.label.toLowerCase(); return this.options.filter(option => option.label.toLowerCase().includes(filterValue) && !this.selectedItems.some(item => item.id === option.id) ); } // 选中自动补全选项 onOptionSelect(option: Option): void { if (!this.selectedItems.some(item => item.id === option.id)) { this.selectedItems = [...this.selectedItems, option]; this.onChange(this.selectedItems); this.inputControl.reset(); } this.onTouched(); } // 删除Chip removeItem(item: Option): void { this.selectedItems = this.selectedItems.filter(i => i.id !== item.id); this.onChange(this.selectedItems); this.onTouched(); } // ControlValueAccessor 接口实现 writeValue(value: Option[]): void { this.selectedItems = value || []; } registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouched = fn; } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; isDisabled ? this.inputControl.disable() : this.inputControl.enable(); } // Validator 接口实现(可选,根据需求添加验证) validate(control: AbstractControl): ValidationErrors | null { if (this.selectedItems.length === 0) { return { required: true }; } return null; } }
2. 模板调整
输入框绑定内部inputControl,Chips列表绑定selectedItems,自动补全选项用过滤后的Observable:
<mat-chip-list [disabled]="disabled" aria-label="Selected items"> <mat-chip *ngFor="let item of selectedItems" [removable]="!disabled" (removed)="removeItem(item)"> {{ item.name }} <mat-icon matChipRemove *ngIf="!disabled">cancel</mat-icon> </mat-chip> </mat-chip-list> <mat-form-field> <mat-label>{{ label }}</mat-label> <input matInput [formControl]="inputControl" [type]="type" [matAutocomplete]="autocomplete" [disabled]="disabled" placeholder="Add item" /> <mat-autocomplete #autocomplete="matAutocomplete" (optionSelected)="onOptionSelect($event.option.value)"> <mat-option *ngIf="(filteredOptions$ | async)?.length === 0" disabled> No matching items </mat-option> <mat-option *ngFor="let option of filteredOptions$ | async" [value]="option"> {{ option.label }} </mat-option> </mat-autocomplete> </mat-form-field>
3. 组件使用方式
和你原来的用法完全一致,直接绑定formControlName:
<form [formGroup]="myForm"> <app-chips-autocomplete formControlName="fruits" [options]="fruitOptions" label="Fruits"> </app-chips-autocomplete> </form>
// 父组件中定义表单和选项 myForm = this.fb.group({ fruits: [[], Validators.required] }); fruitOptions: Option[] = [ { id: 1, label: 'Apple', name: 'Apple' }, { id: 2, label: 'Banana', name: 'Banana' }, { id: 3, label: 'Orange', name: 'Orange' } ];
关键优势
- 分离职责:外部FormControl只管理选中的Chips数组,内部输入框控件处理自动补全,避免值类型冲突
- 规范适配:实现
ControlValueAccessor,完全兼容Angular响应式表单体系,支持formControl/formControlName、状态同步、验证传递 - 无混用问题:不用ngModel,避免响应式表单和模板驱动表单混用导致的状态不一致
- 可复用性高:通过
@Input配置选项、标签等,轻松在不同场景复用
内容的提问来源于stack exchange,提问作者M. Izudinov
相关产品推荐
相关产品推荐

