Angular Material Autocomplete 完整实战指南:从简单建议列表到高级过滤与无障碍设计
2026/9/12 10:45:12 网站建设 项目流程

Angular Material Autocomplete 完整实战指南:从简单建议列表到高级过滤与无障碍设计

【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components

本篇指南以当前仓库中 Angular Material 的 autocomplete 组件文档(autocomplete.md)为核心骨架,结合组件源码(autocomplete.ts、autocomplete-trigger.ts)与官方示例代码,系统讲解如何在 Angular 项目中实现"文本输入 + 建议面板"的自动补全交互:你将掌握从零搭建一个mat-autocomplete、接入响应式表单做自定义过滤、使用displayWith分离控件值与显示值、开启requireSelection强制选项选中、设置首项高亮、把面板挂到自定义容器,以及完整键盘交互与无障碍(ARIA)实现。所有结论均可在仓库源码中直接验证,示例代码可直接复制运行。

1. 认识 MatAutocomplete:一个"被增强的普通文本输入框"

Autocomplete(自动补全)在 Angular Material 中的定位非常朴素而清晰:它是一个由建议选项面板增强的普通文本输入框。组件本身不内置过滤逻辑、不内置数据源,只负责"输入框 ↔ 面板 ↔ 选项"三者之间的联动,过滤与数据组织完全交给开发者。这种设计让它可以服务于国家选择、人员搜索、标签录入等任意需要"输入即联想"的场景。

在仓库中,autocomplete 的功能由三个紧密协作的文件构成:

  • autocomplete.ts:MatAutocomplete组件本身,对应<mat-autocomplete>标签,管理选项集合、面板显隐与键盘焦点;
  • autocomplete-trigger.ts:MatAutocompleteTrigger指令,对应matAutocomplete属性,负责把输入框与面板"绑"在一起,并通过@angular/cdk/overlay把面板渲染到浮层中;
  • autocomplete-origin.ts:MatAutocompleteOrigin指令,对应matAutocompleteOrigin属性,允许把面板的挂载锚点从输入框改到其他元素。

其中触发器是一个ControlValueAccessor(见 autocomplete-trigger.ts 中的MAT_AUTOCOMPLETE_VALUE_ACCESSOR提供者),因此它既能与响应式表单(ReactiveFormsModule)协同,也能与模板驱动表单协同。

2. 最简单的 autocomplete:面板 + 选项 + 输入框绑定

2.1 创建面板与选项

第一步是创建自动补全面板以及面板中要展示的选项。每个选项使用mat-option标签定义,其value属性决定"该选项被选中后,文本输入框的值是什么":

<mat-autocomplete #auto="matAutocomplete"> @for (option of options; track option) { <mat-option [value]="option">{{option}}</mat-option> } </mat-autocomplete>

对应的组件类(来自官方示例 autocomplete-simple-example.ts):

import {Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatAutocompleteModule} from '@angular/material/autocomplete'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @Component({ selector: 'autocomplete-simple-example', templateUrl: 'autocomplete-simple-example.html', imports: [ FormsModule, MatFormFieldModule, MatInputModule, MatAutocompleteModule, ReactiveFormsModule, ], }) export class AutocompleteSimpleExample { myControl = new FormControl(''); options: string[] = ['One', 'Two', 'Three']; }

注意这里使用了 Angular 新式的独立组件(standalone)写法,直接在imports中引入MatAutocompleteModuleMatInputModuleMatFormFieldModuleReactiveFormsModule。如果使用模块式(NgModule)应用,则在模块的imports数组中做同样声明即可。

2.2 创建输入框并绑定面板

接下来创建输入框,并把matAutocomplete输入属性指向面板的模板引用变量。这里假设用ReactiveFormsModule提供的formControl指令来跟踪输入框的值:

<form class="example-form"> <mat-form-field class="example-full-width"> <mat-label>Number</mat-label> <input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto"> <mat-autocomplete #auto="matAutocomplete"> @for (option of options; track option) { <mat-option [value]="option">{{option}}</mat-option> } </mat-autocomplete> </mat-form-field> </form>

完整模板见 autocomplete-simple-example.html。

关于响应式 vs 模板驱动表单:文档明确说明,如果你更喜欢,完全可以使用模板驱动表单(ngModel)。示例选择响应式表单,是因为订阅输入值变化(valueChanges)非常方便。若使用响应式表单,请确保从@angular/forms导入ReactiveFormsModule。对响应式表单不熟悉的读者可以参考 Angular 官方文档。

核心联动是通过把面板实例导出为局部模板变量(此处叫auto),再将该变量绑定到输入框的matAutocomplete属性完成的。此时"输入框获得焦点即弹出面板、选项可被点击选中"的基础交互就已经可用了。

3. 自定义过滤:用valueChanges驱动选项筛选

基础版面板在聚焦时即可弹出,但不会随着输入自动收窄选项。要实现"边输入边过滤",需要自定义一个过滤函数——组件不内置任何过滤策略,过滤逻辑完全由你掌控。

推荐的写法是利用FormControl上内置的valueChangesObservable:把输入值映射为过滤后的建议选项列表,得到新的 ObservablefilteredOptions,再在模板中用async管道替换原来的静态options

import {Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {Observable} from 'rxjs'; import {map, startWith} from 'rxjs/operators'; import {AsyncPipe} from '@angular/common'; import {MatAutocompleteModule} from '@angular/material/autocomplete'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @Component({ selector: 'autocomplete-filter-example', templateUrl: 'autocomplete-filter-example.html', imports: [ FormsModule, MatFormFieldModule, MatInputModule, MatAutocompleteModule, ReactiveFormsModule, AsyncPipe, ], }) export class AutocompleteFilterExample { myControl = new FormControl(''); options: string[] = ['One', 'Two', 'Three']; filteredOptions: Observable<string[]>; constructor() { this.filteredOptions = this.myControl.valueChanges.pipe( startWith(''), map(value => this._filter(value || '')), ); } private _filter(value: string): string[] { const filterValue = value.toLowerCase(); return this.options.filter(option => option.toLowerCase().includes(filterValue)); } }

模板中把选项列表换成filteredOptions | async

<mat-autocomplete #auto="matAutocomplete"> @for (option of filteredOptions | async; track option) { <mat-option [value]="option">{{option}}</mat-option> } </mat-autocomplete>

完整实现见 autocomplete-filter-example.ts。

两点关键细节:

  1. startWith('')预热数据流:用空字符串给valueChanges数据流"预填充"一个初值,确保面板在初始化时(尚未发生任何输入变化之前)就用该值过滤一次、立即展示全量选项。这是文档明确强调的实践。
  2. 过滤匹配方式完全自由:文档特别提示,为了获得最佳无障碍体验,若你使用的是"非标准"过滤(即不限制为从字符串开头匹配,而是如本例的includes任意位置匹配),建议在页面上附加一段文字说明过滤规则,这对屏幕阅读器用户尤其有帮助。本例的_filter只是对选项值做一次简单的大小写不敏感包含测试。

4.displayWith:让控件值与显示值各司其职

默认情况下,面板选项的value就是选中后回填到输入框的文字。但实际业务中常遇到这种情况:表单要保存一个对象,而输入框只想显示对象的某个字符串属性(例如保存User对象,只显示name)。

解决之道是在mat-autocomplete上设置displayWith属性:在组件类中定义一个"把控件值映射为期望显示值"的函数,再绑定到displayWith上:

export interface User { name: string; } @Component({ selector: 'autocomplete-display-example', templateUrl: 'autocomplete-display-example.html', imports: [/* FormsModule, MatFormFieldModule, MatInputModule, MatAutocompleteModule, ReactiveFormsModule, AsyncPipe */], }) export class AutocompleteDisplayExample { myControl = new FormControl<string | User>(''); options: User[] = [{name: 'Mary'}, {name: 'Shelley'}, {name: 'Igor'}]; filteredOptions: Observable<User[]>; constructor() { this.filteredOptions = this.myControl.valueChanges.pipe( startWith(''), map(value => { const name = typeof value === 'string' ? value : value?.name; return name ? this._filter(name as string) : this.options.slice(); }), ); } displayFn(user: User): string { return user && user.name ? user.name : ''; } private _filter(name: string): User[] { const filterValue = name.toLowerCase(); return this.options.filter(option => option.name.toLowerCase().includes(filterValue)); } }

模板中把displayWith绑定到displayFnmat-optionvalue则直接绑定User对象:

<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn"> @for (option of filteredOptions | async; track option) { <mat-option [value]="option">{{option.name}}</mat-option> } </mat-autocomplete>

完整代码见 autocomplete-display-example.ts 与其 HTML 模板。

从源码看,displayWith在组件上被定义为((value: any) => string) | null类型的输入属性(见 autocomplete.ts),由触发器在渲染回填文本时调用,实现"控件值 → 显示文本"的转换。注意过滤函数里要兼容"值可能还是字符串"的情况(用户尚未选择任何对象时,输入框里的原始文字),示例中用typeof value === 'string'做了分支处理。

5.requireSelection:强制用户从选项中选择

默认行为下,autocomplete 会接受用户在输入框里随意输入的任何文本。如果你的业务要求"必须从面板中选中一个合法选项"(例如服务端只认固定枚举值),可以开启mat-autocomplete上的requireSelection输入属性。开启后行为发生如下变化(文档原文语义):

  1. 用户打开面板、修改了值,但没有选中任何选项——autocomplete 的值会被重置回null
  2. 用户打开面板又直接关闭、未改动值——原有的旧值会被保留。

官方示例 autocomplete-require-selection-example.ts 展示了配合signalviewChild的现代写法:通过输入事件触发filter()更新filteredOptions信号。

从源码看,requireSelectionbooleanAttribute转换器解析(见 autocomplete.ts),并且默认值来自全局配置(见下文第 8 节),默认false。因此需要在开启该选项时重新审视表单校验策略——用户一旦输入了"未命中任何选项"的文本,值会变为null,这本身就构成一种隐式的必选约束。

6.autoActiveFirstOption:打开面板即高亮首项

如果你的交互要求"面板打开时第一个选项自动处于高亮(active)状态",以便用户直接按回车选中,可以设置autoActiveFirstOption输入属性:

<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption> ... </mat-autocomplete>

官方示例见 autocomplete-auto-active-first-option-example.ts,其过滤逻辑与第 3 节的_filter完全一致,差别仅在模板上多出该布尔属性。

需要说明的是,源码中还存在一个相邻但独立的选项autoSelectActiveOption(见 autocomplete.ts):它控制"用户导航过程中是否直接选中当前高亮项"。autoActiveFirstOption只负责"打开时高亮第一个",两者可以组合使用,按需在组件上单独或同时开启。

7. 扩展应用:裸输入框、自定义挂载锚点与选项分组

7.1 在自定义输入元素上使用(不依赖mat-form-field

mat-autocomplete不仅限于mat-form-field内部,它同样可以挂到任意普通input元素上,只需要matAutocomplete属性即可。这样你可以在不引入mat-form-field额外功能的前提下完全自定义输入框外观:

<form class="example-form"> <input type="text" placeholder="Search for a street" [formControl]="control" [matAutocomplete]="auto" class="example-input"> <mat-autocomplete #auto="matAutocomplete"> @for (street of filteredStreets | async; track street) { <mat-option [value]="street">{{street}}</mat-option> } </mat-autocomplete> </form>

示例见 autocomplete-plain-input-example.html。从源码看,触发器的选择器是input[matAutocomplete], textarea[matAutocomplete](见 autocomplete-trigger.ts),意味着该指令对普通inputtextarea都适用。当它处于mat-form-field内部时,会通过MAT_FORM_FIELD令牌获得表单字段上下文;脱离表单字段时则独立工作。

7.2 把面板挂到其他元素:matAutocompleteOrigin+matAutocompleteConnectedTo

默认情况下,自动补全面板会附着(attach)在输入框元素上。某些场景(如输入框与建议列表视觉上分离、自定义容器布局)下,你可能希望面板附着到另一个容器元素。这时组合使用matAutocompleteOrigin指令与matAutocompleteConnectedTo输入属性即可(此代码块完整来自原文档):

<div class="custom-wrapper-example" matAutocompleteOrigin #origin="matAutocompleteOrigin"> <input matInput [formControl]="myControl" [matAutocomplete]="auto" [matAutocompleteConnectedTo]="origin"> </div> <mat-autocomplete #auto="matAutocomplete"> @for (option of options; track option) { <mat-option [value]="option">{{option}}</mat-option> } </mat-autocomplete>

matAutocompleteOrigin在源码中是一个极简指令,仅暴露自身的ElementRef作为连接点(见 autocomplete-origin.ts),exportAs: 'matAutocompleteOrigin'使其可以被模板变量捕获;matAutocompleteConnectedTo则把该元素交给触发器的浮层定位策略,作为面板的锚点。

7.3 选项分组:mat-optgroup

当选项数量较多、需要按类别组织时,可以用mat-optgroup把若干mat-option收集成组,并通过label属性展示分组标题:

<mat-autocomplete #autoGroup="matAutocomplete"> @for (group of stateGroupOptions | async; track group) { <mat-optgroup [label]="group.letter"> @for (name of group.names; track name) { <mat-option [value]="name">{{name}}</mat-option> } </mat-optgroup> } </mat-autocomplete>

完整示例见 autocomplete-optgroup-example.html。组件源码通过@ContentChildren(MAT_OPTGROUP, {descendants: true})收集所有分组(见 autocomplete.ts),分组后的键盘导航、高亮逻辑依然由内部的ActiveDescendantKeyManager统一管理。

8. 全局默认配置:MAT_AUTOCOMPLETE_DEFAULT_OPTIONS

无论是requireSelection还是autoActiveFirstOption,文档都指出可以通过MAT_AUTOCOMPLETE_DEFAULT_OPTIONS注入令牌进行全局配置,从而免去在每个mat-autocomplete上重复设置。

该令牌定义在 autocomplete.ts,其默认工厂返回以下默认值:

配置项默认值含义
autoActiveFirstOptionfalse打开面板时是否高亮第一个选项
autoSelectActiveOptionfalse导航过程中是否自动选中当前高亮项
hideSingleSelectionIndicatorfalse单选时是否隐藏选中标记(勾选图标)
requireSelectionfalse是否强制要求从选项中选择
hasBackdropfalse面板是否带背景遮罩

此外,MatAutocompleteDefaultOptions接口(autocomplete.ts)还支持backdropClass(遮罩的自定义样式类)与overlayPanelClass(应用到浮层面板的样式类,可传字符串或字符串数组)。全局覆盖方式是在providers中重新提供该令牌:

import {MAT_AUTOCOMPLETE_DEFAULT_OPTIONS} from '@angular/material/autocomplete'; providers: [ { provide: MAT_AUTOCOMPLETE_DEFAULT_OPTIONS, useValue: {autoActiveFirstOption: true, requireSelection: true}, }, ]

组件构造时会读取该令牌的默认值来初始化对应属性(见 autocomplete.ts)。其他值得了解的输入属性还包括:panelWidth(面板宽度,任意 CSS 尺寸值,缺省时匹配宿主宽度)、disableRipple(禁用面板内涟漪效果)、class(把宿主上的类透传到浮层内的面板以便样式化),以及事件输出optionSelectedopenedclosedoptionActivated(见 autocomplete.ts)。

9. 键盘交互一览

mat-autocomplete的键盘导航由ActiveDescendantKeyManager驱动(见 autocomplete.ts),它基于方向键维护"活跃选项",并通过aria-activedescendant通知辅助技术。完整快捷键如下(原文档表格完整继承):

键盘快捷键行为
Down Arrow导航到下一个选项
Up Arrow导航到上一个选项
Enter选中当前活跃(高亮)选项
Escape关闭自动补全面板
Alt+Up Arrow关闭自动补全面板
Alt+Down Arrow打开自动补全面板(若存在匹配选项)

其中Enter与方向键的处理位于触发器(autocomplete-trigger.ts 中导入了DOWN_ARROWENTERESCAPETABUP_ARROW等键码常量)。值得留意的是,键盘管理器允许键盘聚焦到被禁用的选项(以对齐 WAI-ARIA 对复合组件中 listbox 选项的建议),但被禁用的选项无法被点击选中(见 autocomplete.ts 的_skipPredicate注释与实现)。

10. 无障碍(Accessibility)设计要点

MatAutocomplete实现了ARIA combobox 交互模式:文本输入触发器上设置role="combobox",弹出的面板内容应用role="listbox"(面板内含的每个选项自然具备 listbox option 语义)。由于采用了 listbox 模式,不要在 autocomplete 选项内部再放置按钮、复选框等其他交互控件——这种嵌套交互控件会干扰绝大多数辅助技术的解析。触发器的 host 绑定(见 autocomplete-trigger.ts)会自动维护aria-autocomplete="list"aria-expandedaria-controlsaria-haspopup="listbox"aria-activedescendant等属性。

使用时的无障碍要点(继承自原文档):

  • 始终提供可访问标签:可以通过<mat-form-field>内的<mat-label>、原生<label>元素、aria-label属性或aria-labelledby属性中的任意一种方式为 autocomplete 提供标签。
  • 焦点始终保留在输入框上MatAutocomplete通过aria-activedescendant支持在选项间导航,焦点不会移入面板,屏幕阅读器因此能持续获得活跃选项的上下文。aria-activedescendant的值指向当前活跃选项的 id(见 autocomplete-trigger.ts)。
  • 选中指示器与可访问性的权衡:默认情况下,MatAutocomplete会显示一个勾选标记(checkmark)来标识当前已选中的项。虽然可以通过hideSingleSelectionIndicator隐藏该指示器,但组件源码注释明确指出这会降低可访问性——用户(尤其是视觉障碍用户)将更难甚至无法直观识别已选中的项,因此除非有明确的设计理由,否则不建议隐藏。

11. 进一步探索

  • 组件完整 API 文档:src/material/autocomplete/autocomplete.md
  • 组件实现源码:autocomplete.ts、autocomplete-trigger.ts、autocomplete-origin.ts
  • 官方示例目录:src/components-examples/material/autocomplete,包含本文涉及的 simple、filter、display、require-selection、auto-active-first-option、plain-input、optgroup 全部可运行示例
  • 测试用例:autocomplete.spec.ts、autocomplete.zone.spec.ts(组件行为)、testing/autocomplete-harness.spec.ts(测试 Harness)
  • 组件测试 Harness 公共 API:testing/autocomplete-harness.ts,可用于编写不依赖 DOM 细节的组件测试

【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询