Angular Material Timepicker 完整实战指南:从基础用法到表单集成、校验与国际化
【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components
导读
本文围绕 Angular Material 官方组件库中的 Timepicker(时间选择器)展开,系统讲解如何通过matTimepicker将文本输入框与下拉选项列表组合,让用户既可以手动输入时间,也可以从预置的选项列表中快速选择,且值直接落在 Date 对象的"时间部分"。读完本文,你将掌握 Timepicker 与mat-form-field、@angular/forms、MatDatepicker的组合用法,能够配置校验规则(matTimepickerMin/matTimepickerMax)、自定义下拉选项间隔、定制图标与多语言环境,并理解其底层基于DateAdapter与MAT_DATE_FORMATS的实现机制,足以在实际项目中独立落地一个完整的日期时间选择方案。
Timepicker 是什么
Angular Material 的 Timepicker 允许用户设置 Date 对象的时间部分——既可以手动键入时间,也可以从一组预定义的时间选项中点选。官方文档(timepicker.md)开篇即点明它的定位:它只负责"时间",不负责"日期",因此天然适合与mat-datepicker协作构成完整的日期时间选择器。
从源码看,MatTimepicker组件(timepicker.ts)通过selector: 'mat-timepicker'暴露,底层复用了 CDK 的 Overlay、MatOption列表与ActiveDescendantKeyManager键盘管理,渲染为一个 listbox 样式的下拉面板,并通过opened/closed/selected三个输出事件与外界通信。它由三部分协作组成:
mat-timepicker:下拉面板本体,负责生成与渲染时间选项;mat-timepicker-toggle:可选的开合按钮(默认渲染一个时钟图标);- 输入框上的
matTimepicker绑定:把<input>与 timepicker 面板连接起来。
将 Timepicker 连接到输入框
一个 timepicker 由文本输入框和下拉菜单组成,二者通过输入框上的matTimepicker绑定连接。官方最小化示例(timepicker-overview-example.html)如下:
<mat-form-field> <mat-label>Pick a time</mat-label> <input matInput [matTimepicker]="picker"> <mat-timepicker-toggle matIconSuffix [for]="picker"/> <mat-timepicker #picker/> </mat-form-field>对应的组件类(timepicker-overview-example.ts)需要显式引入三个模块,并提供一个日期适配器:
import {Component} from '@angular/core'; import {MatTimepickerModule} from '@angular/material/timepicker'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; import {provideNativeDateAdapter} from '@angular/material/core'; @Component({ selector: 'timepicker-overview-example', templateUrl: 'timepicker-overview-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatTimepickerModule], }) export class TimepickerOverviewExample {}需要说明的是,mat-timepicker-toggle上的matIconSuffix指令负责把开关按钮作为后缀嵌入表单字段中,[for]="picker"指明它控制的是哪个 timepicker。官方强调:输入框与开关可以独立使用,也可以作为mat-form-field的一部分——上述示例演示了最常规的 form-field 组合方式。
输入框的行为细节
从 timepicker-input.ts 的源码可以看到输入框的几处关键交互逻辑:
- 点击输入框打开面板:
matTimepickerOpenOnClick输入控制"点击输入框是否打开面板",默认开启(input(true, ...),见 timepicker-input.ts)。若关闭,需要自行实现打开逻辑; - 键盘快捷操作:输入框聚焦时按
Esc可清空当前值,按↓/↑方向键直接打开面板(timepicker-input.ts); - 失焦时格式化:
blur时按当前显示格式把值重新格式化,保证用户交互过程中文本不会被意外改写(timepicker-input.ts)。
Timepicker 的表单集成
Timepicker 输入框完整集成了@angular/forms模块:它自行充当ControlValueAccessor和Validator(类声明implements MatTimepickerConnectedInput<D>, ControlValueAccessor, Validator, OnDestroy,见 timepicker-input.ts)。
其行为模型是:当用户在输入框中键入新时间或从下拉列表中选择一项时,该时间会被设置到当前表单控件值所对应的 Date 对象上;如果表单控件当前没有值,timepicker 会用"今天日期 + 所选时间"创建一个新的 Date 对象作为控件值。
官方示例(timepicker-forms-example.html)展示了 Reactive Forms 的用法:
<mat-form-field> <mat-label>Pick a time</mat-label> <input matInput [formControl]="formControl" [matTimepicker]="picker"> <mat-timepicker-toggle matIconSuffix [for]="picker"/> <mat-timepicker #picker/> </mat-form-field> <p>Value: {{formControl.value}}</p> <p>Touched: {{formControl.touched}}</p> <p>Dirty: {{formControl.dirty}}</p>组件侧(timepicker-forms-example.ts)初始值设为当天 12:30:
export class TimepickerFormsExample { formControl: FormControl<Date | null>; constructor() { const initialValue = new Date(); initialValue.setHours(12, 30, 0); this.formControl = new FormControl(initialValue); } }touched、dirty、value等状态都能被正常追踪,说明 timepicker 输入框与模板驱动表单(ngModel)和响应式表单(formControl)均兼容。
与 MatDatepicker 集成:组合日期时间选择器
Material 的 datepicker 与 timepicker 可以作用在同一个值上,从而组合成一个完整的日期时间选择器。绑定同一个值时:datepicker 负责设置整个 Date 对象,timepicker 只修改其中的时间部分。
官方集成示例(timepicker-datepicker-integration-example.html):
<mat-form-field> <mat-label>Meeting date</mat-label> <input matInput [matDatepicker]="datepicker" [(ngModel)]="value"> <mat-datepicker #datepicker/> <mat-datepicker-toggle [for]="datepicker" matSuffix/> </mat-form-field> <mat-form-field> <mat-label>Meeting time</mat-label> <input matInput [matTimepicker]="timepicker" [(ngModel)]="value" [ngModelOptions]="{updateOn: 'blur'}"> <mat-timepicker #timepicker/> <mat-timepicker-toggle [for]="timepicker" matSuffix/> </mat-form-field> <p>Value: {{value()}}</p>这里两个输入框通过[(ngModel)]="value"共享同一个 Date 对象:日期字段改日期、时间字段改时间,互不覆盖对方的部分。时间输入框使用{updateOn: 'blur'}让模型在失焦时才更新,避免键入过程中因中间态值(如只输入了"12:")触发不必要的变化检测。
输入校验(Input validation)
timepicker 输入框会检查两件事:用户键入的值是否为合法时间字符串,以及是否落在设定的上下界内。
解析错误:matTimepickerParse
如果用户键入非法时间字符串(例如abc或24:67),输入框会报告matTimepickerParse错误。字符串解析由当前日期实现(DateAdapter)的parseTime方法完成,例如 timepicker-input.ts 中的this._dateAdapter.parseTime(value, this._dateFormats.parse.timeInput)。
范围错误:matTimepickerMin / matTimepickerMax
输入框通过matTimepickerMin与matTimepickerMax两个输入限定上下界(timepicker-input.ts)。它们既接受携带具体时间的 Date 对象,也接受时间字符串。这两个输入同时还会控制下拉菜单里显示哪些时间选项:选项只会生成在最小值与最大值之间。
例如,设置matTimepickerMin="12:30"与matTimepickerMax="21:25",用户只能选择下午 12:30 到晚上 9:25 之间的时间。一旦值超出范围,输入框会向 value accessor 报告matTimepickerMin(太早)或matTimepickerMax(太晚)错误。
校验器的实现见 timepicker-input.ts:它用Validators.compose组合了三个校验函数,分别产出matTimepickerParse、matTimepickerMin和matTimepickerMax错误对象,且错误对象中带有text、min、max、actual等便于排错的明细字段。
官方校验示例(timepicker-validation-example.html)演示了如何展示错误:
<mat-form-field> <mat-label>Pick a time</mat-label> <input matInput [formControl]="formControl" [matTimepicker]="picker" matTimepickerMin="12:30" matTimepickerMax="17:30"> <mat-timepicker-toggle matIconSuffix [for]="picker"/> <mat-timepicker #picker/> @if (formControl.errors?.['matTimepickerParse']) { <mat-error>Value isn't a valid time</mat-error> } @if (formControl.errors?.['matTimepickerMin']) { <mat-error>Value is too early</mat-error> } @if (formControl.errors?.['matTimepickerMax']) { <mat-error>Value is too late</mat-error> } </mat-form-field> <p>Enter a value before 12:30 PM or after 5:30 PM to see the errors</p> <p>Errors: {{formControl.errors | json}}</p>自定义下拉选项
默认情况下,mat-timepicker的下拉列表以30 分钟为间隔生成选项。官方提供两种自定义方式:设置interval(间隔),或传入自定义options数组。
方式一:interval 间隔
给mat-timepicker传入interval输入即可改变选项生成间隔,选项会从最小时间开始、到最大时间结束。例如<mat-timepicker interval="90m"/>表示按 90 分钟间隔生成选项。
合法的间隔字符串包括:
| 写法 | 含义 | 示例 |
|---|---|---|
| 纯数字(按分钟解释) | 50 表示 50 分钟 | interval="50" |
| 数字 + 短单位 | 30m表示 30 分钟,5h表示 5 小时;支持h/H(时)、m/M(分)、s/S(秒) | interval="30m"、interval="5h" |
| 数字 + 长单位 | 75 min表示 75 分钟,1.5 hours表示 1.5 小时;支持min/minute/minutes(分)、hour/hours(时)、second/seconds(秒) | interval="75 min"、interval="1.5 hours" |
间隔字符串的解析逻辑在 util.ts 的parseInterval中:正则INTERVAL_PATTERN(/^(\d*\.?\d+)\s*(h|hour|hours|m|min|minute|minutes|s|second|seconds)?$/i)负责拆出数值与单位,再统一换算为秒;小时 ×3600、分钟 ×60、秒 ×1。数字形式则被直接视为秒数。MatTimepicker.interval输入本身也以parseInterval作为 transform(见 timepicker.ts)。
选项的生成则由generateOptions(util.ts)完成:从 min 开始按间隔用adapter.addSeconds累加,直到超过 max 为止,每个选项的标签用formats.display.timeOptionLabel格式化。源码特意对间隔做了Math.max(interval, 1)的下限保护,避免小于 1 秒的间隔"冻结"浏览器。生成结果还有一层缓存:只有当"间隔 + min + max"的组合变化时才重新生成(timepicker.ts)。
全局默认间隔:可以通过MAT_TIMEPICKER_CONFIG注入令牌为整个应用设置默认间隔,例如把所有 timepicker 默认改为 90 分钟间隔:
import {MAT_TIMEPICKER_CONFIG} from '@angular/material/timepicker'; { provide: MAT_TIMEPICKER_CONFIG, useValue: {interval: '90 minutes'}, }MatTimepickerConfig接口(util.ts)目前支持interval(默认间隔)与disableRipple(默认关闭波纹效果)两个可选字段。
方式二:自定义 options 数组
如果应用需要更细粒度的控制,可以直接向mat-timepicker传入options数组。注意数组元素必须符合MatTimepickerOption接口:
export interface MatTimepickerOption<D = unknown> { /** Date value of the option. */ value: D; /** Label to show to the user. */ label: string; }官方示例(timepicker-options-example.ts)同时演示了 interval 与自定义 options 两种用法:
export class TimepickerOptionsExample { customOptions: MatTimepickerOption<Date>[] = [ {label: 'Morning', value: new Date(2024, 0, 1, 9, 0, 0)}, {label: 'Noon', value: new Date(2024, 0, 1, 12, 0, 0)}, {label: 'Evening', value: new Date(2024, 0, 1, 22, 0, 0)}, ]; }对应模板(timepicker-options-example.html):
<mat-form-field> <mat-label>Every 45 minutes</mat-label> <input matInput [matTimepicker]="minutesPicker"> <mat-timepicker-toggle matIconSuffix [for]="minutesPicker"/> <mat-timepicker interval="45min" #minutesPicker/> </mat-form-field> <mat-form-field> <mat-label>Every 3.5 hours</mat-label> <input matInput [matTimepicker]="hoursPicker"> <mat-timepicker-toggle matIconSuffix [for]="hoursPicker"/> <mat-timepicker interval="3.5h" #hoursPicker/> </mat-form-field> <mat-form-field> <mat-label>Pick a time of day</mat-label> <input matInput [matTimepicker]="customPicker"> <mat-timepicker-toggle matIconSuffix [for]="customPicker"/> <mat-timepicker [options]="customOptions" #customPicker/> </mat-form-field>自定义开关图标
mat-timepicker-toggle默认渲染一个时钟图标。你可以通过向开关内投影一个带有matTimepickerToggleIcon属性的元素来替换它。官方示例(timepicker-custom-icon-example.html):
<mat-form-field> <mat-label>Pick a time</mat-label> <input matInput [matTimepicker]="picker"> <mat-timepicker-toggle matIconSuffix [for]="picker"> <mat-icon matTimepickerToggleIcon>keyboard_arrow_down</mat-icon> </mat-timepicker-toggle> <mat-timepicker #picker/> </mat-form-field>开关组件(timepicker-toggle.ts)本身还提供for(绑定的 timepicker)、disabled、disableRipple(默认读取MAT_TIMEPICKER_CONFIG中的全局配置)等输入,以及ariaLabel/ariaLabelledby等无障碍相关配置。
国际化(Internationalization)
timepicker 的国际化机制与mat-datepicker共用同一个日期适配器,由三个层面共同配置:
- 日期 locale(地区代码);
- timepicker 接受的日期实现(DateAdapter 实现);
- timepicker 使用的显示与解析格式(MAT_DATE_FORMATS)。
设置 locale 代码
默认情况下,MAT_DATE_LOCALE注入令牌会使用@angular/core提供的LOCALE_ID地区代码。如需覆盖,可以显式提供新的值:
bootstrapApplication(MyApp, { providers: [{provide: MAT_DATE_LOCALE, useValue: 'en-GB'}], });也可以在运行时通过DateAdapter的setLocale方法动态切换地区。官方 locale 示例(timepicker-locale-example.ts)演示了这一点——点击按钮即调用this._adapter.setLocale('bg-BG')切到保加利亚语:
export class TimepickerLocaleExample { private readonly _adapter = inject<DateAdapter<unknown, unknown>>(DateAdapter); value = signal(new Date(2024, 0, 1, 13, 45, 0)); protected switchLocale() { this._adapter.setLocale('bg-BG'); } }注意:如果使用的是provideDateFnsAdapter,则必须向MAT_DATE_LOCALE提供对应 locale 的 data 对象(而非 locale 代码),同时还要向MAT_DATE_FORMATS提供与date-fns兼容的配置;date-fns的 locale 数据可从date-fns/locale导入。
选择日期实现与格式设置
timepicker 被设计为实现无关(implementation-agnostic),可与mat-datepicker互操作,因此支持多种日期实现——但也意味着开发者需要为所选实现提供配套的适配器。最省事的做法是直接导入官方提供的日期适配器之一。
provideNativeDateAdapter或MatNativeDateModule
| 项目 | 说明 |
|---|---|
| Date 类型 | Date |
| 支持的 locales | 使用 AM/PM 或 24 小时制格式化的 locale |
| 依赖 | 无 |
| 导入来源 | @angular/material/core |
provideDateFnsAdapter或MatDateFnsModule(通过ng add @angular/material-date-fns-adapter安装)
| 项目 | 说明 |
|---|---|
| Date 类型 | Date |
| 支持的 locales | 以date-fns项目为准 |
| 依赖 | date-fns |
| 导入来源 | @angular/material-date-fns-adapter |
provideLuxonDateAdapter或MatLuxonDateModule(通过ng add @angular/material-luxon-adapter安装)
| 项目 | 说明 |
|---|---|
| Date 类型 | DateTime |
| 支持的 locales | 以 Luxon 项目为准 |
| 依赖 | Luxon |
| 导入来源 | @angular/material-luxon-adapter |
provideMomentDateAdapter或MatMomentDateModule(通过ng add @angular/material-moment-adapter安装)
| 项目 | 说明 |
|---|---|
| Date 类型 | Moment |
| 支持的 locales | 以 Moment.js 项目为准 |
| 依赖 | Moment.js |
| 导入来源 | @angular/material-moment-adapter |
重要限制:provideNativeDateAdapter的时间解析基于正则实现,只支持 AM/PM 格式(如1:45 PM)或 24 小时格式(如22:45、22.45),因此无法适配使用其他格式的 locale。官方建议:要么使用上面列出的官方适配器,要么继承@angular/material/core中的DateAdapter基类自行实现。
例如,改用date-fns适配器时,把bootstrapApplication更新为:
import {provideDateFnsAdapter} from '@angular/material-date-fns-adapter'; bootstrapApplication(MyApp, { providers: [provideDateFnsAdapter()] });自定义解析与显示格式
MAT_DATE_FORMATS是一组 timepicker 在解析和显示 Date 对象时使用的格式集合,它们会被透传给DateAdapter,因此必须确保所提供格式与当前DateAdapter兼容。
MAT_DATE_FORMATS与mat-datepicker使用同一个对象——如果应用已在用 datepicker,这个令牌大概率已经配置好了;但对 timepicker 而言,还必须确保以下三个属性被设置:
display.timeInput:输入框 / 选项时间段的显示格式;display.timeOptionLabel:下拉选项中每一条时间的显示格式;parse.timeInput:把用户键入的字符串解析为时间的格式。
这三个属性也是 timepicker 启动时的硬性校验项:validateAdapter(util.ts)会检查它们是否缺失,任一缺失都会抛出 "IncompleteMAT_DATE_FORMATS" 错误。
如果希望在官方适配器上使用自己的格式,可以把格式传入 providers 函数,或直接提供MAT_DATE_FORMATS令牌。例如:
bootstrapApplication(MyApp, { providers: [provideNativeDateAdapter(MY_NATIVE_DATE_FORMATS)], });无障碍(Accessibility)
timepicker 实现了ARIA combobox 交互模式(W3C APG 规范):
- timepicker 输入框设置
role="combobox"; - 下拉内容设置
role="listbox"; - 下拉中的每个选项设置
role="option"。
默认情况下 listbox 的标签来自其所在的mat-form-field的 label;如果未使用 form field,或需要自定义标签,可以通过mat-timepicker的ariaLabel或ariaLabelledby输入指定。相关输入的定义见 timepicker.ts。此外,键盘交互由ActiveDescendantKeyManager(timepicker.ts)驱动,支持方向键上下移动、Home/End跳转、PageUp/PageDown翻页,保证纯键盘操作可用。
常见错误排查(Troubleshooting)
Error: MatTimepicker: No provider found for DateAdapter/MAT_DATE_FORMATS
这个错误表示 timepicker 所需的注入项(DateAdapter或MAT_DATE_FORMATS)未提供完整。最简单的解决方式是在应用配置中加入provideNativeDateAdapter或provideMomentDateAdapter等适配器,详见上文"选择日期实现与格式设置"。
Error: MatTimepicker: IncompleteMAT_DATE_FORMATShas been provided
timepicker 正常工作需要MAT_DATE_FORMATS中包含display.timeInput、display.timeOptionLabel和parse.timeInput三个字段。请补充这些字段,详见"自定义解析与显示格式"。
Error: Cannot specify both theoptionsandintervalinputs at the same time
一个mat-timepicker不能同时指定options和interval两个输入。此校验在组件初始化时执行(见 timepicker.ts),请修改模板移除其中一个。
Error: Value ofoptionsinput cannot be an empty array
传入options输入的数组不能为空——否则用户没有任何选项可选。同样是组件初始化期的硬性校验(timepicker.ts)。
Error: A MatTimepicker can only be associated with a single input
当多个<input>通过matTimepicker属性试图绑定同一个<mat-timepicker>时抛出此错误。一个 timepicker 只能与唯一一个输入框关联(注册逻辑见 timepicker-input.ts 中的_registerTimepicker)。
测试与进一步探索
仓库为 timepicker 提供了完整的三层测试保障,可作为理解行为边界的参考:
- 组件单元测试:timepicker.spec.ts 覆盖生成选项、校验、键盘交互、min/max 边界等核心行为;
- 工具函数测试:util.spec.ts 覆盖
parseInterval、generateOptions等纯函数; - Harness 测试:testing/ 目录下的
timepicker-harness.spec.ts、timepicker-input-harness.spec.ts、timepicker-toggle-harness.spec.ts演示了如何用 Component Harness 在测试中打开面板、读取选项、断言值,配套的 harness 实现位于 timepicker-harness.ts 与 timepicker-input-harness.ts。
全部可复现的官方示例集中在 src/components-examples/material/timepicker 目录下,涵盖 overview、forms、validation、options、custom-icon、locale、datepicker-integration、harness 八个主题,适合直接对照学习或拷贝改造。
【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考