preamble
This post will only teach the most critical component in Angular Material Datepicker - the Calendar component.
And how to customize the DateAdapter so that Calendar supports theTC39 Temporal。
For those interested in learning the full Datepicker, please go toofficial website。
Those interested only in the Calendar component and custom DateAdapter, stay!
Let's start 🚀。
Angular Material DateAdapter
App Components
@Component({ selector: 'app-root', standalone: true, imports: [MatDatepickerModule], // Importing the Datepicker Module templateUrl: './', styleUrl: './', providers: [], }) export class AppComponent {}
App Template
<mat-calendar []="256" />
effect
It reports an error straight away because we're missing the DateAdapter.
Date Library
Before we get into DateAdapter, let's talk about JavaScript's Date and Date Library.
JavaScript uses Date to manage dates.
Anyone who has ever used a Date can easily feel itsflaws。
The Date was modeled after Java in 1995.
Java has since made a number of improvements to Date in versions 1.1 in 1997 and 8.0 in 2014.
JavaScript, on the other hand, has been stuck in a rut, following 1995's Date to the present day... 🙈
This leads to the fact that in real projects, Date is simply not enough, and we have to use a third-party Date Library to fulfill our needs.
Currently the hot Date Library has:
-
(2020 Abandoned. Hottest year ever.)
-
(light weight)
-
date-fns ()
-
Luxon (successor)
All four Libraries are popular 😱.
Why need DateAdapter?
Having multiple Date Library choices is a good thing for developers, but not for frameworks / libraries.
Something like the Angular Material Datepicker would need to support all the popular Date Libraries at the same time, otherwise there would be a bunch of tedious conversions. (. convert Date to Luxon / Moment / Dayjs / date-fns date object)
Same function (interface), but different library. This... This is one of the 23 object-oriented design patterns known asadapter modeWhat do you think?
Angular Material built-in DateAdapter
Angular Material does a good job of making DateAdapters for different Date Libraries for us:
-
moment-adapter
-
(under development...)
-
date-fns-adapter
-
luxon-adapter
I didn't realize the hottest thing was the abandoned moment-adapter 🙈...
Besides these 3 Date Library Adapters, there is another one for JavaScript native Date -- NativeDateAdapter, total 4 DateAdapters are available.
NativeDateAdapter
Go back to the App component and add a provider
import { Component } from '@angular/core'; import { provideNativeDateAdapter } from '@angular/material/core'; import { MatDatepickerModule } from '@angular/material/datepicker'; @Component({ selector: 'app-root', standalone: true, imports: [MatDatepickerModule], templateUrl: './', styleUrl: './', providers: [provideNativeDateAdapter()], // Add NativeDateAdapter }) export class AppComponent {}
Note: You want to put in as a global provider as well.
effect
No more errors were reported 🙂 .
Let's add another selected date to it and see what happens.
export class AppComponent { readonly selectedDate = signal(new Date('2024-09-14')); // Since this is a NativeDateAdapter, the Date object is used here. }
App Template
<mat-calendar [(selected)]="selectedDate" []="256" />
effect
13 has border because 13 is today (this was written on 2024-09-13), and 14 has background-color because 14 is the selected date.
LuxonDateAdapter
Let's move on to another built-in DateAdapter -- LuxonDateAdapter.
Installation of Luxon
yarn add luxon yarn add @types/luxon --dev yarn add @angular/material-luxon-adapter
provider
import { provideLuxonDateAdapter } from '@angular/material-luxon-adapter'; @Component({ providers: [provideLuxonDateAdapter()], // Add LuxonDateAdapter }) export class AppComponent {}
selected date is changed from a Date object to a DateTime object (Luxon uses a DateTime object, oops, kind of like .)
import { DateTime } from 'luxon'; export class AppComponent { // Since this is a LuxonDateAdapter, the DateTime object is used here. readonly selectedDate = signal(('2024-09-14', 'yyyy-MM-dd')); }
effect
It comes out exactly the same as the NativeDateAdapter 🙂 .
Angular Material custom DateAdapter for Temporal
Temporal is JavaScript's future date scheme to replace Date. It's currently in Stage 3 and doesn't have a browser.be in favor ofBut there has been a completePolyfill It's ready to use.
For those who know nothing about him, read this firstJavaScript – Temporal API & Date。
Angular doesn't have a built-in Temporal DateAdapter, so if we're using Temporal in our project, we'll need to make our own.
Let's start 🚀。
Install Temporal
Install Temporal Polyfill
yarn add temporal-polyfill
Create class TemporalDateAdapter
Create class TemporalDateAdapter and inherit from abstract class DateAdapter.
import { DateAdapter } from '@angular/material/core'; import { Temporal } from 'temporal-polyfill'; export class TemporalDateAdapter extends DateAdapter<> {}
DateAdapter requires a generic type, which we pass in , which represents only the date (year, month, day), but not the time (hours, minutes, seconds), nor the time zone.
This abstract class DateAdapter has a lot of abstract methods in it that we need to implement one by one.
The source code is in the
We can refer to LuxonDateAdapter (source code) and NativeDateAdapter (source code) to implement the above abstract methods.
Implement all abstract methods
Let's realize it one by one 🚀.
constructor & locale
constructor() { super(); this.locale = inject(MAT_DATE_LOCALE) as string; }
The locale is responsible for formatting and language, such as whether to display "January" or "Jan" or "January".
MAT_DATE_LOCALE by default takes Angular'sLOCALE_ID (I didn't teach this one, I'll fill in later when I teach i18n.) The default value for Angular LOCALE_ID is "en-US".
Anyway, the default is US English, and if you want to display Chinese, we can change LOCALE_ID to "zh-Hans-CN" (zh-Hans stands for Simplified Chinese, CN stands for Chinese).
export const appConfig: ApplicationConfig = { providers: [ provideExperimentalZonelessChangeDetection(), provideAnimations(), { provide: LOCALE_ID, // Set the locale for the entire project useValue: 'zh-Hans-CN', }, { provide: MAT_DATE_LOCALE, // Only set the locale of the Datepicker useValue: 'zh-Hans-CN', }, ], };
Note: If MAT_DATE_LOCALE and LOCALE_ID are the same, you only need to set LOCALE_ID.
getYear
override getYear(date: ): number { return ; }
Too simple for me to explain, next.
getMonth
override getMonth(date: ): number { return - 1; }
Temporal January is represented by 1, and JavaScript Date January is represented by 0.
Angular Material chooses to use JS Date as its standard, so we need to do an additional -1 action.
getDate
override getDate(date: ): number { return ; }
getDayOfWeek
override getDayOfWeek(date: ): number { return === 7 ? 0 : ; }
Temporal Sundays are represented by 7, JavaScript Date Sundays are represented by 0.
Angular Material is compatible with both, and returning 7 or 0 can be used to indicate Sunday.
getMonthNames
getMonthNames is to return a string array from January to December, . ['Jan', 'Feb', 'Mar', ... . months].
import { Intl, Temporal } from 'temporal-polyfill'; override getMonthNames(style: 'long' | 'short' | 'narrow'): string[] { return new Array(12).fill(undefined).map<string>((_, index) => { const month = index + 1; const date = ({ year: 2017, month, day: 1 }); const formatter = new (this.locale, { month: style, }); return (date); // long: January, short: Jan, narrow: J }); }
We do this with the help of handling locale.
getDateNames
getDateNames is to return a string array from 1 to 31, . [1, 2, 3, ... . days].
override getDateNames(): string[] { return new Array(31).fill(undefined).map<string>((_, index) => { const day = index + 1; const date = ({ year: 2017, month: 1, day }); const formatter = new (this.locale, { day: 'numeric', }); // . zh-Hans-CN: 31st return (date); }); }
A little trivia: Chinese ends with the character 'date'. If it were MomentDateAdapter, it would use ('D'), and the word 'date' would not appear.
It depends on the needs of the project to write the nuances, how we want to format are customized.
getDayOfWeekNames
getDayOfWeekNames is to return a string array from Sunday to Saturday, . ['Sun', 'Mon', 'Tue', . .dayOfWeeks].
override getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] { return new Array(7).fill(undefined).map<string>((_, index) => { const day = index + 1; // It just so happens that 2017-01-01 is a Sunday, and dayOfWeek also starts on a Sunday const date = ({ year: 2017, month: 1, day }); const formatter = new (this.locale, { weekday: style, }); return (date); // long: Sunday, short: Sun, narrow: S }); }
We do this with the help of handling locale.
getYearName
override getYearName(date: ): string { const formatter = new (this.locale, { year: 'numeric', }); return (date); // . 2017 (Note: zh-Hans-CN is also 2017 not 2017 Oh) }
getFirstDayOfWeek
override getFirstDayOfWeek(): number { return 0; }
Since getDayOfWeek above uses 0 to represent Sunday, it follows here as well.
The first day of the week is Sunday, so 0 is returned.
Of course, this is a US custom. Malaysia, China usually the first day is Monday, then we can change it to return 1.
getNumDaysInMonth
Returns the number of days in a given month, such as 28, 29, 30, or 31.
override getNumDaysInMonth(date: ): number { return ; }
clone
Clone a date
override clone(date: ): { return date.with({}); }
createDate
Creating a Date Object Object
override createDate(year: number, month: number, date: number): { return ({ year, month: month + 1, day: date }); }
today
Back to today
override today(): { return (); }
addCalendarYears, addCalendarMonths, addCalendarDays
Add year, month and day
override addCalendarYears(date: , years: number): { return ({ years }); } override addCalendarMonths(date: , months: number): { return ({ months }); } override addCalendarDays(date: , days: number): { return ({ days }); }
toIso8601
Return to ISO 8601 date format
override toIso8601(date: ): string { return (); // 2017-01-01 year-month-day }
isDateInstance
Determine if value is a date object
override isDateInstance(obj: any): boolean { return obj instanceof ; }
isValid
Determine if a date is valid
override isValid(_date: ): boolean { return true; }
JavaScript Date has a concept of Invalid Date.
const date = new Date('whatever'); (()); // Invalid Date (()); // NaN
It means that although value is a date object, it is invalid.
Temporal does not have this concept, as long as it is a Temporal object, then it must be a valid date.
So the isValid method just returns true.
invalid
invalid To return an Invalid Date.
override invalid(): { throw new Error('Method not implemented.'); }
Temporal doesn't have a concept of Invalid Date, so we can just throw error here.
format
format that is, put Temporal convert to date string with specify display format。
override format(date: , displayFormat: any): string { const formatter = new (this.locale, { ...displayFormat, timeZone: 'UTC', }); return (date); }
The parameter displayFormat is also customizable through the provider MAT_DATE_FORMATS, and its default value is
Different DateAdapter will work with different MatDateFormats, for example Luxon's displayFormat is expressed as string . yyyy-MM-dd = 2017-01-01 (see the list of completed formats)here are)
parse
There is format and there is parse.
override parse(value: any, _parseFormat: any): | null { if (typeof value !== 'string') throw new Error('Method not implemented.'); const date = new Date(value); if (!(())) { return (date).toZonedDateTimeISO('UTC').toPlainDate(); } return null; }
Similarly, the parameter parseFormat is customized from (when the user inputs a date string using input, the date adapter has to convert it from a string to a date object).
I've just written a simple parse as an example; parse is usually more complex in real projects.
deserialize
deserialize is very similar to parse, but deserialize is only used to parse ISO 8601 string format.
(indicates contrast) parse Then you can handle the whatever string format even whatever types.
override deserialize(value: any): | null { if (typeof value === 'string') { if (value === '') return null; const ISO_8601_REGEX = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|(?:(?:\+|-)\d{2}:\d{2}))?)?$/; if (ISO_8601_REGEX.test(value)) { return this.parse(value, undefined); } } return (value); }
I copied the above paragraph from NativeDateAdapter.
summarize
These are all the abstract methods that need to be implemented.
parse, format, deserialize need to be adjusted according to the needs of different projects, the other is basically common.
Try TemporalDateAdapter
App Components
@Component({ selector: 'app-root', standalone: true, imports: [MatDatepickerModule, TestAdapterComponent], templateUrl: './', styleUrl: './', providers: [ { provide: MAT_DATE_LOCALE, useValue: 'zh-Hans-CN', }, { provide: DateAdapter, useClass: TemporalDateAdapter, }, { provide: MAT_DATE_FORMATS, useValue: MAT_NATIVE_DATE_FORMATS, }, ], }) export class AppComponent { // Because it's a TemporalDateAdapter, we're using the object readonly selectedDate = signal(('2024-09-14')); }
Note: Providers can also be placed globally.
effect
Angular Material Calendar
Let's take a look.official websiteThe first Datepicker example given
<mat-form-field> <mat-label>Choose a date</mat-label> <input matInput [matDatepicker]="picker"> <mat-hint>MM/DD/YYYY</mat-hint> <mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle> <mat-datepicker #picker></mat-datepicker> </mat-form-field>
There are 8 components/instructions involved:
-
MatFormField component -- <mat-form-field>
- MatLabel command -- <mat-label>
-
MatInput command -- input[matInput]
-
MatDatepickerInput command -- input[matDatepicker]
- MatHint command -- <mat-hint>
-
MatDatepickerToggle component -- <mat-datepicker-toggle>
- MatSuffix command -- [matIconSuffix]
-
MatDatepicker component -- <mat-datepicker>
It was such a mess when it came up 🙁 ... Let's cut out the irrelevant ones through.
<input [matDatepicker]="picker"> <mat-datepicker-toggle [for]="picker"></mat-datepicker-toggle> <mat-datepicker #picker></mat-datepicker>
Three remain: the MatDatepickerInput command, the MatDatepickerToggle component, and the MatDatepicker component.
effect
Clicking on the MatDatepickerToggle will popup the MatDatepicker and update it to the MatDatepickerInput when the date is selected.
toggle and input are just supporting characters, the main character is the date picker.
At the bottom of the MatDatepicker is the Calendar component, which we're going to talk about in this post.
Calendar Component
We already used the Calendar component as an example in the previous part Custom DateAdapter.
<mat-calendar []="300" />
effect
It has some common @Input / @Output that can be configured.
selected
readonly selectedDate = signal(('2024-09-14'));
<pre>{{ selectedDate() }}</pre> <mat-calendar [(selected)]="selectedDate" []="300" />
effect
The Calendar component is not supportedReactive FormsIt's selected is simply @Input @Output orTwo-way Binding。
minDate, maxDate, dateFilter
minDate, maxDate are used to limit the optional range of Calendar.
For example, only dates seven days before and after today can be selected.
private readonly today = signal(()); readonly minDate = computed(() => this.today().subtract({ days: 7 })); readonly maxDate = computed(() => this.today().add({ days: 7 }));
<mat-calendar [minDate]="minDate()" [maxDate]="maxDate()" []="300" />
effect
Grayed out dates are disabled and unselectable.
dateFilter is a date-based filter that determines whether a date is optional or not.
readonly dateFilterFn = signal((date: ) => !== 6 && !== 7); // Only available outside of Saturday and Sunday
<mat-calendar [dateFilter]="dateFilterFn()" []="300" />
effect
startAt, startView
startAt is the date on which the initial Calendar screen will be displayed. (Default is today)
readonly startAt = ('2017-01-01'); // Displayed January 1, 2017
<mat-calendar [startAt]="startAt" []="300" />
Reminder: startAt is only valid for the initial screen, the Calendar will not be synchronized with subsequent changes to the startAt date.
effect
The startView is the view to be displayed on the initial screen.
Calendar has 3 views.
<mat-calendar startView="multi-year" []="300" />
Reminder: Like startAt, startView is only valid for the initial screen, Calendar will not be synchronized with subsequent changes to the startView value.
effect
dateClass
We can add class to a specific date and then do styling.
readonly dateClassFn = signal<MatCalendarCellClassFunction<>>((date, _view) => { if ( === 7 || === 6) return 'weekend'; return ''; });
Add 'weekend' class for weekends.
Then styling.
mat-calendar ::ng-deep .weekend { background-color: pink; }
Need to use ::ng-deep Oh, or penetration won't work.
effect
Also, dateClass currently has a bug -- theGithub Issue – bug(MatCalendar): updating dateClass does not update calendar view。
When we change the dateClass @input value, it doesn't update the Calendar immediately, whereas dateFilter does.
selected DateRange
selected can select a date or a range.
For example, select an entire month from 2024-09-01 to 2024-09-30.
Note: It is only possible to select range, not multiple dates.
readonly selectedDateRange = signal( new DateRange(('2024-09-01'), ('2024-09-30')), );
Using the DateRange object, parameter 1 is the start date and parameter 2 is the end date.
<p>start: {{ selectedDate().start }}</p> <p>end: {{ selectedDate().end }}</p> <mat-calendar [(selected)]="selectedDateRange" []="300" />
effect
selectedChange
In the last example, if we went to interact, it would break.
on account of Calendar (used form a nominal expression) two-way binding unsupported date range。
We need to move a little bit.
First, take apart the two-way binding.
<mat-calendar [selected]="selectedDateRange()" (selectedChange)="handleSelectedChange($event)" []="300" />
App Components
private selectCount = 1; handleSelectedChange(selectedDate: | null) { if (!selectedDate) return; // 2 operations // First select start // Second select end // and then loop const selectCount = ((this.selectCount - 1) % 2) + 1; this.selectCount++; const { selectedDateRange } = this; if (selectCount === 1) { // select start this.(new DateRange(selectedDate, null)); return; } if (selectCount === 2) { const start = selectedDateRange().start!; // If the second selectedDate is earlier than start, re-select. if ((selectedDate, start) === -1) { (new DateRange(selectedDate, null)); this.selectCount--; return; } // select end (new DateRange(start, selectedDate)); return; } }
effect
MAT_DATE_RANGE_SELECTION_STRATEGY
MAT_DATE_RANGE_SELECTION_STRATEGY can add to the interactive experience.
App component provider
@Component({
providers: [
{
provide: MAT_DATE_RANGE_SELECTION_STRATEGY,
useClass: DefaultMatCalendarRangeStrategy,
},
],
})
export class AppComponent {}
effect
Notice the dash border that appears after the hover? It's called preview.
comparisonStart,comparisonEnd
Comparison means contrast and is usually used to compare 2 date ranges.
For example, let's say you want to compare last week's data from 2024-09-01 to 2024-09-07 to this week's data from 2024-09-08 to 2024-09-17.
readonly comparisonStart = signal(('2024-09-08'));
readonly comparisonEnd = signal(('2024-09-14'));
<p>start: {{ selectedDateRange().start }}</p> <p>end: {{ selectedDateRange().end }}</p> <p>comparison start: {{ comparisonStart() }}</p> <p>comparison end: {{ comparisonEnd() }}</p> <mat-calendar [selected]="selectedDateRange()" (selectedChange)="handleSelectedChange($event)" [comparisonStart]="comparisonStart()" [comparisonEnd]="comparisonEnd()" []="300" />
Note: the comparison is split into 2 @Inputs, and it's not using the DateRange object (inconsistent 🙄).
effect
The colors of the 2 date ranges are different.
Also the selected date range has a dark background-color for the first and last dates, but the comparison date range does not (inconsistent 🙄).
styling
If you want to modify a style, override its CSS variables.
@use '@angular/material' as mat; mat-calendar { @include -date-range-colors(hotpink, teal, yellow, purple); /* equivalence*/ --mat-datepicker-calendar-date-in-range-state-background-color: hotpink; --mat-datepicker-calendar-date-in-comparison-range-state-background-color: teal; --mat-datepicker-calendar-date-in-overlap-range-state-background-color: yellow; --mat-datepicker-calendar-date-in-overlap-range-selected-state-background-color: purple; }
effect
There are many more variables you can change, so I won't list them all here, just use Chrome DevTools to find element and look at styles.
select comparisonStart and comparisonEnd
From the two "inconsistencies 🙄" above, we can guess that the COMPARISON feature must have been hardwired into Calendar later.
And it's true, even the select date range feature was added later, so two-way binding doesn't work.
See how bad the Angular Material team is... 😔
Let's look at Google's own product, Google Ads, and see how it selects comparison date range.
Let's take a look at how Angular Material selects comparison date range.
That's right, it must use 2 Calendars separately to make the selection, not just one Calendar like Google Ads.
See, the incompetence of the Angular Material team isn't going to matter much to Google, because they're capable of implementing it themselves.
The community, however, is not so sure and will have to accept the incompetence of the Angular Material team to use the less experienced implementation 😔.
Okay, we'll just barely make it work -- selecting the comparison date range in the same Calendar.
App Components
export class AppComponent { readonly selectedDateRange = signal(new DateRange<>(null, null)); readonly comparisonDateRange = signal(new DateRange<>(null, null)); private readonly totalSelectCount = signal(1); readonly selectCount = computed(() => ((this.totalSelectCount() - 1) % 4) + 1); handleSelectedChange(selectedDate: | null) { if (!selectedDate) return; const { selectedDateRange, comparisonDateRange, totalSelectCount } = this; const currSelectCount = this.selectCount(); (totalSelectCount() + 1); // 4 operations // select start > end > comparison start > comparison end, then keep looping if (currSelectCount === 1) { // select start (new DateRange(selectedDate, null)); return; } if (currSelectCount === 2) { const start = selectedDateRange().start!; const isPrevDate = (selectedDate, start) === -1; if (isPrevDate) { // re-select due to selected date is early than start (new DateRange(selectedDate, null)); (totalSelectCount() - 1); return; } // select end (new DateRange(start, selectedDate)); return; } if (currSelectCount === 3) { // Here's a stolen dragon to turn the wind // Because Calendar can only listen for selected, it can't listen for comparison. (new DateRange(selectedDateRange().start, selectedDateRange().end)); (new DateRange(selectedDate, null)); return; } if (currSelectCount === 4) { const startDate = selectedDateRange().start!; const isPrevDate = (selectedDate, startDate) === -1; if (isPrevDate) { // re-select due to selected date is early than start (new DateRange(selectedDate, null)); (totalSelectCount() - 1); return; } // And finally steal the dragon to turn the wind one more time to get it back to the right position (new DateRange(comparisonDateRange().start, comparisonDateRange().end)); (new DateRange(startDate, selectedDate)); return; } } }
The most special thing was doing a Stealing Dragon Turning Wind on the third select, not perfect but barely usable, really out of tricks 😔.
App Template
<p>start: {{ selectedDateRange().start }}</p> <p>end: {{ selectedDateRange().end }}</p> <p>comparison start: {{ comparisonDateRange().start }}</p> <p>comparison end: {{ comparisonDateRange().end }}</p> <mat-calendar [selected]="selectedDateRange()" (selectedChange)="handleSelectedChange($event)" [comparisonStart]="comparisonDateRange().start" [comparisonEnd]="comparisonDateRange().end" []="300" [class]="'select-count-' + selectCount()" />
The most unusual thing is the inclusion of a class: select-count-n
App Styles
Since we did steal the dragon to turn the wind above, the styling needs to match as well or the color will run away.
@use '@angular/material' as mat; $color-theme: -theme( ( color: ( theme-type: light, primary: mat.$blue-palette, tertiary: mat.$orange-palette, ), ) ); mat-calendar { --primary: #{-theme-color($color-theme, 'primary')}; --on-primary: #{-theme-color($color-theme, 'on-primary')}; --primary-light: #{-theme-color($color-theme, 'primary-container')}; --on-primary-light: #{-theme-color($color-theme, 'on-primary-container')}; --tertiary: #{-theme-color($color-theme, 'tertiary')}; --on-tertiary: #{-theme-color($color-theme, 'on-tertiary')}; --tertiary-light: #{-theme-color($color-theme, 'tertiary-container')}; --on-tertiary-light: #{-theme-color($color-theme, 'on-tertiary-container')}; &.select-count-2 ::ng-deep { /* select start */ --mat-datepicker-calendar-date-selected-state-background-color: var(--primary); --mat-datepicker-calendar-date-selected-state-text-color: var(--on-primary); /* select preview */ --mat-datepicker-calendar-date-preview-state-outline-color: var(--primary); } &.select-count-3 ::ng-deep { /* select start & end */ --mat-datepicker-calendar-date-selected-state-background-color: var(--primary); --mat-datepicker-calendar-date-selected-state-text-color: var(--on-primary); /* select range */ --mat-datepicker-calendar-date-in-range-state-background-color: var(--primary-light); .mat-calendar-body-in-range { --mat-datepicker-calendar-date-text-color: var(--on-primary-light); } } &.select-count-4 ::ng-deep { /* select start & end */ .mat-calendar-body-comparison-start .mat-calendar-body-cell-content, .mat-calendar-body-comparison-end .mat-calendar-body-cell-content { background-color: var(--primary) !important; --mat-datepicker-calendar-date-text-color: var(--on-primary); } /* select range */ --mat-datepicker-calendar-date-in-comparison-range-state-background-color: var(--primary-light); .mat-calendar-body-in-comparison-range { --mat-datepicker-calendar-date-text-color: var(--on-primary-light); } /* comparison start */ --mat-datepicker-calendar-date-selected-state-background-color: var(--tertiary); --mat-datepicker-calendar-date-selected-state-text-color: var(--on-tertiary); /* comparison range preview */ --mat-datepicker-calendar-date-preview-state-outline-color: var(--tertiary); } &.select-count-1 ::ng-deep { /* select start & end */ --mat-datepicker-calendar-date-selected-state-background-color: var(--primary); --mat-datepicker-calendar-date-selected-state-text-color: var(--on-primary); /* select range */ --mat-datepicker-calendar-date-in-range-state-background-color: var(--primary-light); .mat-calendar-body-in-range { --mat-datepicker-calendar-date-text-color: var(--on-primary-light); } /* comparison start & end */ .mat-calendar-body-comparison-start .mat-calendar-body-cell-content, .mat-calendar-body-comparison-end .mat-calendar-body-cell-content { background-color: var(--tertiary) !important; --mat-datepicker-calendar-date-text-color: var(--on-tertiary); } /* comparison range */ --mat-datepicker-calendar-date-in-comparison-range-state-background-color: var(--tertiary-light); .mat-calendar-body-in-comparison-range { --mat-datepicker-calendar-date-text-color: var(--on-tertiary-light); } } }
It looks like a lot of code, but it's all similarly duplicated, and the reason it's not encapsulated is because looking at it one by one, copy paste would be better managed.
final result
summarize
That's all for Calendar. By the way, I would like to mention the MatDatepickerInput command, the MatDatepickerToggle component, and the MatDatepicker component mentioned above.
The MatDatepicker component essentially wraps the Calendar component and makes it aPopoverThe underlying technology isCDK Overlay。
The MatDatepickerToggle component is just a simple trigger responsible for popup MatDatepicker.
The MatDatepickerInput command links to the selected date of the Calendar, using the DateAdapter to do the parse and format.
There is also a component called MatDateRangeInput component <mat-date-range-input> which is responsible for handling the date range case.
For those interested in these upper level components / commands, you can take a stroll through the source code. I'll leave you to it 🤪.
summarize
This post introduces the Calendar component of the Angular Material Datepicker module and how to customize the DateAdapter.
I hope to write another post about other related components / commands sometime in the future. Bye bye 👋
catalogs
Previous article.Angular Material 18+ Advanced Tutorial - Material Form Field
Next TODO
To see the table of contents, go toAngular 18+ Advanced Tutorial - Table of Contents
Like it please click recommend 👍 and if you find the tutorial content to be out of context with the new version please comment to let me know. happy coding 😊 💻