Angular Material 自动完成组件 MatAutocomplete 完全指南:API 解析、源码原理与实战示例
【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components
自动完成(Autocomplete)是 Angular Material 组件库中最常用的交互组件之一:它由一个普通文本输入框与一个承载候选选项的下拉面板组合而成。本文以本仓库(Component infrastructure and Material Design components for Angular)中@angular/material/autocomplete的官方 API 报告 goldens/material/autocomplete/index.api.md 为主线骨架,结合 组件使用文档 与 核心源码、触发器源码,系统讲解该组件的公开 API 面、底层实现机制、完整配置项与可运行的实战代码。读完本文,你将能够从零搭建带过滤、带分组、可定制挂载位置的自动完成输入框,并理解其键盘交互、无障碍支持与事件模型背后的实现原理。
一、组件家族总览:从 API 报告看整体架构
根据 API 报告,@angular/material/autocomplete包对外暴露的公开 API 由一个 NgModule 和若干组件、指令、注入令牌、事件类与函数组成,它们协同工作构成了完整的自动完成能力:
| API 类型 | 名称 | 职责 |
|---|---|---|
| NgModule | MatAutocompleteModule | 聚合模块,同时依赖并再导出OverlayModule、MatOptionModule、BidiModule、CdkScrollableModule |
| 组件 | MatAutocomplete | 选项面板本身,选择器mat-autocomplete,可导出matAutocomplete |
| 指令 | MatAutocompleteTrigger | 绑定在input[matAutocomplete]/textarea[matAutocomplete]上的触发器 |
| 指令 | MatAutocompleteOrigin | 指定面板的连接锚点元素,选择器[matAutocompleteOrigin] |
| 组件 | MatOption | 单个选项,来自../core(由 public-api.ts 再导出) |
| 组件 | MatOptgroup | 选项分组,同样来自../core |
| 注入令牌 | MAT_AUTOCOMPLETE_DEFAULT_OPTIONS | 覆盖全局默认配置 |
| 注入令牌 | MAT_AUTOCOMPLETE_SCROLL_STRATEGY | 面板打开期间的滚动策略 |
| 注入令牌 | MAT_AUTOCOMPLETE_VALUE_ACCESSOR | 将触发器注册为ControlValueAccessor的 provider |
| 事件类 | MatAutocompleteSelectedEvent | 选项被选中时触发,携带source与option |
| 事件接口 | MatAutocompleteActivatedEvent | 活动选项变化时触发,携带source与option(可为null) |
| 配置接口 | MatAutocompleteDefaultOptions | 全局默认选项的类型定义 |
| 函数 | getMatAutocompleteMissingPanelError() | 生成“找不到面板实例”错误 |
从依赖关系看,该组件深度复用 CDK 基础设施:@angular/cdk/overlay负责面板浮层定位、@angular/cdk/a11y的ActiveDescendantKeyManager负责键盘焦点管理、@angular/cdk/scrolling负责滚动容器、@angular/cdk/bidi负责双向文本方向;同时通过ControlValueAccessor与@angular/forms的表单体系无缝集成(见 autocomplete-module.ts 与 autocomplete-trigger.ts 中的模块声明)。
二、快速上手:搭建第一个自动完成输入框
使用前先在模块或独立组件中引入MatAutocompleteModule。以下为官方autocomplete-simple示例(源码位于 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', styleUrl: 'autocomplete-simple-example.css', imports: [ FormsModule, MatFormFieldModule, MatInputModule, MatAutocompleteModule, ReactiveFormsModule, ], }) export class AutocompleteSimpleExample { myControl = new FormControl(''); options: string[] = ['One', 'Two', 'Three']; }模板:
<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>两个关键步骤:
- 定义面板:用
mat-autocomplete标签创建面板,内部用mat-option定义每个选项,[value]决定该选项被选中后写入输入框/表单的值; - 绑定触发器:通过
exportAs把面板实例导出到局部模板变量(此处为#auto),再绑定到输入框的matAutocomplete输入属性上。该属性的对应实现是MatAutocompleteTrigger.autocomplete(@Input('matAutocomplete')),源码位于 autocomplete-trigger.ts。
组件类中用ReactiveFormsModule的FormControl跟踪输入值。文档也提示:如果偏好模板驱动表单同样可行,只是响应式表单更便于订阅值变化。
三、实现过滤:基于 valueChanges 的自定义过滤器
面板默认在聚焦时可开合、选项可选中,但“边输入边过滤”需要自行实现。官方autocomplete-filter示例(见 autocomplete-filter-example.ts)演示了标准做法:
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-option列表:
<mat-autocomplete #auto="matAutocomplete"> @for (option of filteredOptions | async; track option) { <mat-option [value]="option">{{option}}</mat-option> } </mat-autocomplete>要点解析:
startWith('')的作用:用空字符串“预热”值变化流,使组件在初始化时(尚未发生任何输入)就按空串执行一次过滤,从而保证面板在聚焦时即可显示全部选项;- 过滤器完全自定义:只要返回
MatOption的候选值数组即可,不限于字符串前缀匹配——可以是对象、数组或任意可比较结构; - 无障碍提醒:官方文档特别建议,若使用非标准过滤规则(不限于从字符串开头匹配),应在页面上补充说明过滤条件的文字提示,这对使用屏幕阅读器的用户尤其重要。
四、分离控件值与显示值:displayWith 的妙用
默认情况下,mat-option的[value]既作为表单保存的控件值,也作为输入框中显示的文本。当两者需要不同时(典型场景:表单保存对象,而输入框只展示其中一个字符串属性),使用MatAutocomplete的displayWith输入属性。
官方autocomplete-display示例(见 autocomplete-display-example.ts):
export interface User { name: string; } 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): string[] { const filterValue = name.toLowerCase(); return this.options.filter(option => option.name.toLowerCase().includes(filterValue)); } }模板中绑定[displayWith]="displayFn":
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn"> @for (option of filteredOptions | async; track option) { <mat-option [value]="option">{{option.name}}</mat-option> } </mat-autocomplete>实现细节:displayWith在MatAutocomplete上定义为@Input() displayWith: ((value: any) => string) | null = null(见 autocomplete.ts)。过滤器中的typeof value === 'string' ? value : value?.name判断是必须的——因为当用户回删选中值、输入框里只剩纯字符串时,valueChanges发出的就是字符串而非User对象。
五、强制选择:requireSelection 与全局默认配置
默认情况下,自动完成接受用户随意输入的任何文本。若业务要求“必须从候选中选中一项”,可开启requireSelection输入。其行为(源码注释见 autocomplete.ts)有两方面:
- 用户打开面板、改变了输入值但未选择任何选项便离开时,值会被重置为
null; - 用户打开面板又关闭、且未改动值,则保留旧值。
<mat-autocomplete #auto="matAutocomplete" requireSelection> ... </mat-autocomplete>该行为可以全局统一配置。MAT_AUTOCOMPLETE_DEFAULT_OPTIONS注入令牌在源码中providedIn: 'root'并带有默认工厂(见 autocomplete.ts):
export const MAT_AUTOCOMPLETE_DEFAULT_OPTIONS = new InjectionToken<MatAutocompleteDefaultOptions>( 'mat-autocomplete-default-options', { providedIn: 'root', factory: () => ({ autoActiveFirstOption: false, autoSelectActiveOption: false, hideSingleSelectionIndicator: false, requireSelection: false, hasBackdrop: false, }), }, );MatAutocompleteDefaultOptions接口(同样见 autocomplete.ts)支持的全部键为:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
autoActiveFirstOption | boolean | false | 面板打开时是否高亮第一个选项 |
autoSelectActiveOption | boolean | false | 键盘导航过程中是否自动选中当前活动选项 |
requireSelection | boolean | false | 是否强制用户必须做出选择 |
backdropClass | string | — | 应用到遮罩层(backdrop)的 CSS 类 |
hasBackdrop | boolean | false | 面板打开时是否显示遮罩层 |
overlayPanelClass | string \| string[] | — | 应用到浮层面板的 CSS 类(可多个) |
hideSingleSelectionIndicator | boolean | false | 单选时是否隐藏选中指示图标 |
应用级覆盖方式,例如在 providers 中:
providers: [ { provide: MAT_AUTOCOMPLETE_DEFAULT_OPTIONS, useValue: {autoActiveFirstOption: true, requireSelection: true}, }, ],从源码可以看到,组件构造器中正是用这些默认值初始化各输入属性:this.autoActiveFirstOption = !!this._defaults.autoActiveFirstOption,因此组件级输入可以逐项覆盖全局配置(见 autocomplete.ts 构造函数部分)。
六、键盘交互与焦点管理
官方文档给出完整的键盘交互约定:
| 快捷键 | 行为 |
|---|---|
| ↓Down Arrow | 移动到下一个选项 |
| ↑Up Arrow | 移动到上一个选项 |
| Enter | 选中当前活动选项 |
| Escape | 关闭面板 |
| Alt+↑ | 关闭面板 |
| Alt+↓ | 存在匹配选项时打开面板 |
这些行为在底层由ActiveDescendantKeyManager实现。面板组件在ngAfterContentInit中创建键盘管理器(见 autocomplete.ts):
this._keyManager = new ActiveDescendantKeyManager<MatOption>(this.options) .withWrap() // 支持首尾循环 .skipPredicate(this._skipPredicate);值得注意的实现细节:_skipPredicate恒返回false,即不跳过禁用选项。源码注释引用了 WAI-ARIA APG 键盘接口规范——对于 listbox 这类复合控件,禁用的选项仍应可被键盘聚焦(但不可点击),这与普通规则“禁用元素移出 Tab 序”形成例外(见 autocomplete.ts 的_skipPredicate注释)。
与之配套的两个输入属性:
autoActiveFirstOption:打开面板即高亮第一个选项,适用于候选较少、希望用户直接回车选中的场景;autoSelectActiveOption:在键盘上下导航过程中,活动选项的值被“自动选中”但暂不写入模型,直到面板关闭才落库;触发器端通过_pendingAutoselectedOption与_valueBeforeAutoSelection跟踪此状态(见 autocomplete-trigger.ts)。
七、触发器深入:位置、禁用、滚动策略与常见错误
MatAutocompleteTrigger是连接输入框与面板的枢纽,其选择器为input[matAutocomplete], textarea[matAutocomplete],并通过MAT_AUTOCOMPLETE_VALUE_ACCESSOR(基于NG_VALUE_ACCESSOR)实现ControlValueAccessor(见 autocomplete-trigger.ts),因此可无缝配合FormControl、[(ngModel)]及formControlName。
它的主要输入与公开成员:
| 成员 | 绑定别名 | 类型/默认值 | 说明 |
|---|---|---|---|
autocomplete | matAutocomplete | MatAutocomplete | 关联的面板实例 |
position | matAutocompletePosition | 'auto' \| 'above' \| 'below',默认'auto' | auto优先在下方展开,视口空间不足时自动翻转到上方;above/below强制固定方向 |
connectedTo | matAutocompleteConnectedTo | MatAutocompleteOrigin | 面板的定位锚点,默认是触发器本身 |
autocompleteAttribute | autocomplete | 'off' | 透传给原生输入框的autocomplete属性 |
autocompleteDisabled | matAutocompleteDisabled | boolean,默认false | 禁用后输入框退化为普通输入框,无法打开面板 |
panelOpen | —(getter) | boolean | 面板当前是否打开 |
activeOption | —(getter) | MatOption \| null | 当前活动选项 |
optionSelections | — | Observable<MatOptionSelectionChange> | 选项选中流 |
panelClosingActions | — | Observable<MatOptionSelectionChange \| null> | 面板关闭动作流 |
openPanel()/closePanel()/updatePosition() | — | 方法 | 编程式控制面板开关与重定位 |
几点实现层面的细节:
- 宿主 ARIA 属性自动管理:触发器通过 host 绑定动态维护
role="combobox"、aria-autocomplete="list"、aria-expanded、aria-controls、aria-haspopup="listbox"、aria-activedescendant等属性,且全部在autocompleteDisabled时置空(见 autocomplete-trigger.ts 的host定义); - 滚动策略可替换:
MAT_AUTOCOMPLETE_SCROLL_STRATEGY令牌默认返回createRepositionScrollStrategy(面板随页面滚动重新定位);若需要固定面板或自定义行为,可注入该令牌提供自己的() => ScrollStrategy; - 常见错误:
getMatAutocompleteMissingPanelError()会在“尝试打开一个不存在的mat-autocomplete实例”时被抛出,例如matAutocomplete绑定写错、或试图在ngAfterContentInit钩子之前打开面板——错误信息会提示核对传入的 id 与打开时机。
八、灵活挂载:自定义输入元素与改变面板锚点
8.1 脱离 mat-form-field 的自定义输入
matAutocomplete并不强制要求宿主是mat-form-field。任何input/textarea元素都可以直接挂载触发器,从而完全自定义输入框外观:
<input type="text" [matAutocomplete]="auto" placeholder="Type here"> <mat-autocomplete #auto="matAutocomplete"> @for (option of options; track option) { <mat-option [value]="option">{{option}}</mat-option> } </mat-autocomplete>官方autocomplete-plain-input示例演示了该用法,适合不想引入mat-form-field全部能力、只想使用自动完成交互的场景。
8.2 把面板挂到其他元素:matAutocompleteOrigin + matAutocompleteConnectedTo
默认面板以输入框为锚点。如需挂载到容器元素(例如自定义包装 div),用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<HTMLElement>作为连接点(见 autocomplete-origin.ts),connectedTo输入则指向该指令实例。
8.3 面板宽度与外观控制
MatAutocomplete还提供以下与外观相关的输入:
panelWidth:任意 CSS 宽度值(如'300px'或50),未设置时面板与宿主同宽;class(输入别名):将宿主元素上的类透传到浮层面板内部,便于直接为面板写样式;disableRipple:禁用面板内选项的水波纹反馈;hideSingleSelectionIndicator:隐藏单选时的对勾指示(详见下节无障碍说明)。
九、选项分组:mat-optgroup
当选项数量多、需要分层展示时,用mat-optgroup对mat-option分组(官方autocomplete-optgroup示例,位于 src/components-examples/material/autocomplete/autocomplete-optgroup/):
<mat-autocomplete #auto="matAutocomplete"> <mat-optgroup label="Group name"> <mat-option value="item">Item</mat-option> </mat-optgroup> </mat-autocomplete>MatOptgroup支持label(组标题)与disabled(整组禁用)。面板组件通过@ContentChildren(MAT_OPTGROUP, {descendants: true}) optionGroups收集分组,通过@ContentChildren(MatOption, {descendants: true}) options收集全部选项(见 autocomplete.ts)。源码中还包含一个平台特判:在 Safari 上inertGroups会被置为true(见构造函数),用以规避 VoiceOver 朗读分组时的已知缺陷。
十、无障碍(Accessibility)设计
MatAutocomplete实现了 ARIA combobox 交互模式,这是其无障碍设计的核心:
- 输入触发器承担
role="combobox",弹出内容承担role="listbox"(见面板模板 autocomplete.html); - 不要在选项内嵌套交互控件:由于 listbox 模式,选项内部不应再放按钮、复选框等其他可交互元素,否则会干扰绝大多数辅助技术;
- 必须提供可访问标签:可通过
<mat-form-field>内的<mat-label>、原生<label>、aria-label或aria-labelledby任一方式给出; - 焦点保持在输入框:面板打开时焦点始终留在触发输入框,通过
aria-activedescendant指向当前活动选项的 id 来支持选项间导航(面板 id 由_IdGenerator生成mat-autocomplete-前缀的唯一值,见 autocomplete.ts); - 选中指示:默认面板用对勾标识已选项。官方文档明确指出,虽然可用
hideSingleSelectionIndicator隐藏对勾,但这会降低无障碍性——视觉用户更难(甚至无法)分辨当前选中项,建议仅在确有设计需求时使用。
十一、事件模型与编程控制
MatAutocomplete对外暴露四个事件(定义见 autocomplete.ts):
| 事件 | 载荷 | 触发时机 |
|---|---|---|
optionSelected | MatAutocompleteSelectedEvent(含source与option) | 用户选定一个选项 |
opened | void | 面板打开 |
closed | void | 面板关闭 |
optionActivated | MatAutocompleteActivatedEvent(含source与option,可为null) | 活动选项变化(面板打开期间) |
典型用法:
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="onSelected($event)" (opened)="onOpened()" (closed)="onClosed()"> ... </mat-autocomplete>onSelected(event: MatAutocompleteSelectedEvent) { // event.source 为面板实例,event.option 为被选中的 MatOption console.log('selected:', event.option.value); }与此同时,触发器提供optionSelectionsObservable 与panelClosingActionsObservable 用于更底层的流式监听;openPanel()、closePanel()、updatePosition()三个方法让业务代码可以精确控制面板的开关与重定位(例如在窗口尺寸变化时手动updatePosition(),不过触发器本身已订阅ViewportRuler与BreakpointObserver自动处理了大部分场景,见 autocomplete-trigger.ts)。
附录:核心 API 速查(源自 API 报告)
以下速查依据 goldens/material/autocomplete/index.api.md 整理,供快速引用:
MatAutocompleteModule:导入该模块即可使用mat-autocomplete、matAutocomplete触发器、matAutocompleteOrigin,并自动获得MatOptionModule与 CDK overlay/bidi/scrolling 能力;MatAutocomplete:选择器mat-autocomplete,导出名matAutocomplete;输入含aria-label、aria-labelledby、displayWith、autoActiveFirstOption、autoSelectActiveOption、requireSelection、panelWidth、disableRipple、class、hideSingleSelectionIndicator;输出含optionSelected、opened、closed、optionActivated;另有只读成员isOpen、panel、options、optionGroups、template;MatAutocompleteTrigger:选择器input[matAutocomplete], textarea[matAutocomplete];输入matAutocomplete、matAutocompletePosition、matAutocompleteConnectedTo、autocomplete(原生属性透传)、matAutocompleteDisabled;MatAutocompleteOrigin:选择器[matAutocompleteOrigin],导出名matAutocompleteOrigin;- 令牌:
MAT_AUTOCOMPLETE_DEFAULT_OPTIONS、MAT_AUTOCOMPLETE_SCROLL_STRATEGY、MAT_AUTOCOMPLETE_VALUE_ACCESSOR; - 相关测试与更多示例:组件行为可参考 autocomplete.spec.ts,其余示例分布在 src/components-examples/material/autocomplete/ 下的
autocomplete-require-selection、autocomplete-auto-active-first-option、autocomplete-plain-input、autocomplete-optgroup、autocomplete-harness等目录中,可作为从简单到进阶的完整学习序列。
【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考