You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确实现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' }
];

关键优势

  1. 分离职责:外部FormControl只管理选中的Chips数组,内部输入框控件处理自动补全,避免值类型冲突
  2. 规范适配:实现ControlValueAccessor,完全兼容Angular响应式表单体系,支持formControl/formControlName、状态同步、验证传递
  3. 无混用问题:不用ngModel,避免响应式表单和模板驱动表单混用导致的状态不一致
  4. 可复用性高:通过@Input配置选项、标签等,轻松在不同场景复用

内容的提问来源于stack exchange,提问作者M. Izudinov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 21:46:06