我们提供安全,免费的手游软件下载!
本篇只会教 Angular Material Datepicker 里最关键的组件 -- Calendar 组件。
还有如何自定义 DateAdapter,让 Calendar 支持 TC39 Temporal 。
有兴趣想学完整 Datepicker 的朋友们,请移步 官网 。
只对 Calendar 组件和自定义 DateAdapter 感兴趣的人,留下!
Let's start ?。
App 组件
App Template
效果
直接报错了,因为我们缺少了 DateAdapter。
在讲解 DateAdapter 之前,我们先来聊聊 JavaScript 的 Date 和 Date Library。
JavaScript 使用 Date 来管理日期。
但凡用过 Date 的人都知道它 很烂 。
这个 Date 是 1995 年仿照 Java 设计出来的。
后来 Java 在 1997 年 1.1 版和 2014 年 8.0 版,对 Date 进行了大量的改进。
而 JavaScript 却一直固步自封,把 1995 年的 Date 沿用到今天...?
这就导致了,在真实项目中,Date 根本不够用,我们只能借助第三方 Date Library 来实现需求。
目前比较火的 Date Library 有:
Moment.js (2020 已废弃,当年最火)
Day.js (light weight)
date-fns (Node.js)
Luxon (Moment.js 的接棒者)
四个 Library 都很受欢迎?。
对于开发者来说,有多个 Date Library 选择是一件好事,但对于 framework / library 而言就不好了。
像 Angular Material Datepicker 就需要同时支持所有受欢迎的 Date Library,否则就会出现一堆繁琐的转换操作。(比如:convert Date to Luxon / Moment / Dayjs / date-fns date object)
相同的功能 (interface),却有着不同的实现 (different library),这...这不就是传说中面向对象 23 种设计模式之一的 适配器模式 吗??
Angular Material 替我们做好了不同 Date Library 的 DateAdapter:
moment-adapter
Day.js (开发中...)
date-fns-adapter
luxon-adapter
没想到最火的竟然是废弃了的 moment-adapter?...
除了这 3 个 Date Library Adapter,还有一个是 JavaScript 原生 Date 的 Adapter -- NativeDateAdapter,总共 4 个 DateAdapter 可选。
回到 App 组件,添加一个 provider
注:你要放在 app.config.ts 作为全局 provider 也可以。
效果
果然,加了 DateAdapter 就不再报错了?
我们再加一个 selected date 给它,看看效果。
App Template
效果
13 号有 border 是因为 13 号是 today (本篇写于 2024-09-13),14 号有 background-color 是因为 14 号是 selected date。
我们继续试试另一个 built-in DateAdapter -- LuxonDateAdapter。
安装 Luxon
provider
selected date 从 Date 对象改成 DateTime 对象 (Luxon 用的是 DateTime 对象,哎哟,有点像 .NET 哦)
效果
和 NativeDateAdapter 出来的效果一模一样?。
Temporal 是 JavaScript 未来要取代 Date 的日期方案。它目前还在 Stage 3 阶段,没有任何游览器 支持 ,但已经有完整的 Polyfill 可以使用了。
对他一窍不通的朋友,可以先看这篇 JavaScript – Temporal API & Date 。
Angular 没有 built-in 的 Temporal DateAdapter,假如我们的项目使用 Temporal,那就需要自己做一个 Temporal DateAdapter。
Let's start ?。
安装 Temporal Polyfill
创建 class TemporalDateAdapter 并继承 abstract class DateAdapter
DateAdapter 需要一个泛型,我们传入 Temporal.PlainDate,它仅仅代表日期 (年月日),不包含时间 (时分秒),也不包含时区 (time zone)。
这个 abstract class DateAdapter 里面有很多抽象方法,我们需要一一去实现。
源码在 date-adapter.ts
我们可以参考 LuxonDateAdapter (源码 luxon-date-adapter.ts ) 和 NativeDateAdapter (源码 native-date-adapter.ts ) 去实现上面这些 abstract methods。
我们一个一个来实现吧 ?。
locale 负责格式和语言,比如说是要显示 “January" 还是 "Jan" 还是 "一月"。
MAT_DATE_LOCALE 默认会拿 Angular 的 LOCALE_ID (这个我没有教过,以后教 i18n 时会补上),Angular LOCALE_ID 的默认值是 "en-US"。
总之,默认是美国英语就对了,想显示中文,我们可以把 LOCALE_ID 改成 "zh-Hans-CN" (zh-Hans 代表简体,CN 代表中国)。
app.config.ts
注:假如 MAT_DATE_LOCALE 和 LOCALE_ID 一样的话,那只需要设置 LOCALE_ID 就可以了。
太简单的,我就不解释了,下一个。
Temporal 一月是用 1 来表示,JavaScript Date 一月是用 0 来表示。
Angular Material 选择依据 JS Date 的标准来使用,所以我们需要额外做一个 -1 的动作。
Temporal 星期日是用 7 来表示,JavaScript Date 星期日是用 0 来表示。
Angular Material 两个都兼容,返回 7 或者返回 0 都可以用来表示星期日。
getMonthNames 需要返回一个 string array,从一月到十二月,e.g. ['Jan', 'Feb', ’Mar‘, ...months]。
我们借助 Intl.DateTimeFormat 处理 locale 就可以了。
getDateNames 需要返回一个 string array,从 1 到 31,e.g. [1, 2, 3, ...days]。
有一个小知识点:Intl.DateTimeFormat 中文会以 '日' 这个字作为结尾。换作是 MomentDateAdapter 的话,它用的是 Moment.format('D'),不会出现 '日' 这个字。
这些微差就看项目的需求咯,要怎样 format 都是我们自定义的。
getDayOfWeekNames 需要返回一个 string array,从星期日到星期六,e.g. ['Sun', 'Mon', 'Tue', ...dayOfWeeks]。
我们借助 Intl.DateTimeFormat 处理 locale 就可以了。
由于上面 getDayOfWeek 是以 0 来表示星期日,所以这里也 follow。
一周的第一天是星期日,所以返回 0。
当然,这个是美国的习惯。马来西亚,中国通常第一天是星期一,那我们可以改成 return 1。
返回特定月份里有多少天,比如 28天,29天,30天,或者 31天。
克隆一个日期
创建日期对象
返回今天
添加年月日
返回 ISO 8601 日期格式
判断 value 是不是一个日期对象
判断是不是一个 valid 的日期
JavaScript Date 有一个 Invalid Date 的概念
意思是 value 虽然是日期对象,但它是 invalid 的。
Temporal 没有这个概念,只要是 Temporal 对象,那它一定就是一个 valid 的日期。
所以 isValid 方法直接 return true 就可以了。
invalid 要返回一个 Invalid Date。
Temporal 没有 Invalid Date 概念,这里我们直接 throw error 就可以了。
format 就是把 Temporal convert to date string with specify display format。
参数 displayFormat 也是我们自定义的,透过 provider MAT_DATE_FORMATS,它的默认值是
不同的 DateAdapter 会搭配不同的 MatDateFormats,比如说 Luxon 的 displayFormat 是用 string 来表达的 e.g. yyyy-MM-dd = 2017-01-01 (完成的 format list 看 这里 )
有 format 自然也有 parse。
同样的,参数 parseFormat 也是自定义的,来自 MatDateFormats.parse.dateInput (当 user 使用 input 输入一个 date string 时,date adapter 要把它从 string 转换成日期对象)。
我这里只是写了一个简单的 parse 作为例子,真实项目中 parse 通常会比较复杂。
deserialize 和 parse 很类似,但是 deserialize 只用于 parse ISO 8601 string format。
而 parse 则可以处理 whatever string format 甚至是 whatever types.
上面这段,我是从 NativeDateAdapter 抄过来的。
以上就是所有需要 implement 的 abstract methods。
parse, format, deserialize 需要依据不同项目需求去做调整,其它的基本上是通用的。
App 组件
注:provider 也可以放到全局 app.config.ts。
效果
我们先看看 官网 给出的第一个 Datepicker 例子
这里头涉及了 8 个组件 / 指令:
MatFormField 组件 --
MatInput 指令 -- input[matInput]
MatDatepickerInput 指令 -- input[matDatepicker]
MatDatepickerToggle 组件 --
MatDatepicker 组件 --
一上来就这么乱?...我们把不相关的通通砍掉。
剩下三个:MatDatepickerInput 指令,MatDatepickerToggle 组件,MatDatepicker 组件。
效果
点击 MatDatepickerToggle 后会 popup MatDatepicker,选择日期后会 update 到 MatDatepickerInput。
toggle 和 input 只是配角,主角是 date picker。
而 MatDatepicker 的底层便是本篇要讲解的 Calendar 组件。
我们在上一 part Custom DateAdapter 里就已经使用 Calendar 组件作为例子了。
效果
它有一些常见的 @Input / @Output 可以做配置。
效果
Calendar 组件没有支持 Reactive Forms ,它的 selected 就是简单的 @Input @Output 或者说 Two-way Binding 。
minDate, maxDate 用于限制 Calendar 可选范围。
比如,只能选今天和前后七天的日期。
效果
灰色的日期都是 disabled,不可选了。
dateFilter 则是依据日期做判断,是否可选。
效果
startAt 是说,Calendar 初始画面要显示在哪一天?(默认是今天)
提醒:startAt 只对初始画面有效哦,后续修改 startAt 日期,Calendar 是不会同步更新的。
效果
startView 则是指初始画面要显示哪一种 view?
Calendar 一共有 3 种 view。
提醒:和 startAt 一样,startView 只对初始画面有效,后续修改 startView 值,Calendar 是不会同步更新的。
效果
我们可以给特定的日期添加 class,然后做 styling。
给周末添加 ’weekend‘ class。
接着 styling
需要使用 ::ng-deep 哦,不然渗透不进去。
效果
另外,dateClass 目前有一个 Bug -- Github Issue – bug(MatCalendar): updating dateClass does not update calendar view 。
当我们修改 dateClass @input 值后,它不会立刻更新 Calendar。对比 dateFilter 则会立刻更新的。
selected 可以选一个日期,也可以选一个 range。
比如说,从 2024-09-01 到 2024-09-30,选择一整个月份。
注:它只是可以选 range,而不是选 multiple 日期哦。
使用 DateRange 对象,参数 1 是 start date,参数 2 是 end date。
效果
上一个例子,假如我们去交互,它会坏掉。
因为 Calendar 的 two-way binding 不支持 date range。
我们需要动一点手脚。
首先,把 two-way binding 给拆开
App 组件
效果
MAT_DATE_RANGE_SELECTION_STRATEGY 可以增强交互体验。
App 组件 provider
效果
注意到那个 hover 后出现的 dash border 吗?它叫 preview。
comparison 是对比的意思,通常用于对比 2 个 date range。
比如说,想拿上个星期 2024-09-01 到 2024-09-07 的数据对比这个星期 2024-09-08 到 2024-09-17 的数据。
注:comparison 分成 2 个 @Input 哦,而且它不是使用 DateRange 对象 (不统一啊?)。
效果
2 个 date range 是不同的颜色。
另外,selected date range 的头尾日期会有深色 background-color,而 comparison date range 则没有 (又不统一啊?)。
想修改样式就 override 它的 CSS variables。
效果
还有很多 variables 可以改,这里我就不一一列出了,用 Chrome DevTools 找到 element 后,看着它的 styles 来改就是了。
从上面两个 "不统一?",我们可以猜得出来,comparison 这个功能一定是后来硬加进去 Calendar 的。
而事实也是如此,甚至连 select date range 功能也是后来才硬加进去的,所以 two-way binding 才不 work。
看到 Angular Material 团队有多糟糕了吧...?
我们看看 Google 自家产品 Google Ads,如何 select comparison date range
接着看看 Angular Material 如何 select comparison date range
没错,它必须分开使用 2 个 Calendar 来做选择,而不像 Google Ads 那样只在一个 Calendar 上做选择。
看到吗?Angular Material 团队的无能对 Google 来说并不会有多大的影响,因为人家有能力自己实现。
但社区就不一定了,只能接受 Angular Material 团队的无能,使用体验较差的实现方式?。
好,我们勉强来实现这个功能 -- 在同一个 Calendar 里选择 comparison date range。
App 组件
最特别的地方就是第三次 select 的时候做了一个偷龙转风,不完美,但勉强可以用,真的没招了?。
App Template
最特别的地方是加入了一个 class:select-count-n
App Styles
由于我们上面做了偷龙转风,所以 styling 也需要配合,不然颜色会跑掉。
看上去好像很多代码,但其实都是类似重复的,之所以没有封装是因为一个一个看,copy paste 会更好管理。
最终效果
Calendar 就介绍到这里。顺便说一说上面提到的 MatDatepickerInput 指令,MatDatepickerToggle 组件,MatDatepicker 组件。
MatDatepicker 组件主要是封装了 Calendar 组件,并且让它变成一个 Popover ,底层技术就是 CDK Overlay 。
MatDatepickerToggle 组件就只是一个简单的 trigger,负责 popup MatDatepicker。
MatDatepickerInput 指令则是链接 Calendar 的 selected 日期,链接的时候会利用 DateAdapter 做 parse 和 format。
还有一个叫 MatDateRangeInput 组件
对这些上层组件 / 指令感兴趣的朋友可以去逛一逛源码。我就不奉陪了?。
本篇主要介绍了 Angular Material Datepicker 模块当中的 Calendar 组件,还有如何自定义 DateAdapter。
希望以后有时间能再写一篇介绍其它相关的组件 / 指令。掰掰?
上一篇 Angular Material 18+ 高级教程 – Material Form Field
下一篇 TODO
想查看目录,请移步 Angular 18+ 高级教程 – 目录
喜欢请点推荐?,若发现教程内容以新版脱节请评论通知我。happy coding ??
热门资讯