<mat-form-field>
<mat-label>Choose a date</mat-label>
<input matInput [matDatepicker]="picker" formControlName="date">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
If I set the input value to a date everything works fine.
this.form.controls['date'].setValue(new Date());
But I also want to manully set the input value to a string like this
this.form.controls['date'].setValue('permanent');
But MatDatepicker does not allow me to do that and shows nothing (it probably expects a date format) even though my form accepts the string value 'permanent'. form:
input:
What I was expecting:
By default, the mat-datepicker is using NativeDateAdapter
as the default DateAdapter
.
You can create a custom date adapter class by extending from DateAdapter
.
For example, I created custom-date-adapter.ts
and extend DateAdapter
with type param <Date | string>
which allow support of string
.
export class CustomDateAdapter extends DateAdapter<Date | string> {
Inject and use the default NativeDateAdapter
when overriding DateAdapter
abstract methods
adapter: NativeDateAdapter;
...
constructor(nativeDateAdapter: NativeDateAdapter) {
...
this.adapter = nativeDateAdapter;
For example, for parse
and isDateInstance
override, check for permanent
keyword, if true, then return. Else, call the nativeDateAdapter
default method.
NativeDateAdapter@isDateInstance
is called when setValue('permanent')
and
NativeDateAdapter@parse
is called when type inpermanent
.
parse(value: any, parseFormat: any): string | Date | null {
if (value === 'permanent') {
return 'permanent';
}
return this.adapter.parse(value, parseFormat);
}
isDateInstance(obj: any): boolean {
if (obj === 'permanent') {
return true;
}
return this.adapter.isDateInstance(obj);
}
Other small details like the private _today: Date = new Date();
, this variable is used in other override methods, to pass into nativeDateAdapter
method when the user enters any string
, so that the date picker can expand and point to today date.
For example, if return 0
in getYear
method, the picklist will point to 0 years when the user enters string input and expand it.
<form [formGroup]="form">
<mat-form-field>
<mat-label>Choose a date</mat-label>
<input matInput [matDatepicker]="picker" formControlName="date">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<hr>
date.value = {{ form.controls['date'].value }}
<br/>
date.valid = {{ form.controls['date'].valid }}
</form>
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'ng-date-picker';
form = new FormGroup({
date: new FormControl('permanent')
});
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatDatepickerModule } from '@angular/material/datepicker';
import {
DateAdapter,
MatNativeDateModule,
MAT_DATE_FORMATS,
MAT_NATIVE_DATE_FORMATS,
NativeDateAdapter,
} from '@angular/material/core';
import { MatInputModule } from '@angular/material/input';
import { CustomDateAdapter } from './custom-date-adapter';
import { MatButtonModule } from '@angular/material/button';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
ReactiveFormsModule,
MatFormFieldModule,
MatDatepickerModule,
MatNativeDateModule,
FormsModule,
MatInputModule,
MatButtonModule,
],
providers: [
NativeDateAdapter,
{ provide: DateAdapter, useClass: CustomDateAdapter },
],
bootstrap: [AppComponent],
})
export class AppModule {}
import { Injectable } from '@angular/core';
import {
DateAdapter, NativeDateAdapter
} from '@angular/material/core';
@Injectable()
export class CustomDateAdapter extends DateAdapter<Date | string> {
adapter: NativeDateAdapter;
private _today: Date = new Date();
constructor(nativeDateAdapter: NativeDateAdapter) {
super();
this.adapter = nativeDateAdapter;
}
parse(value: any, parseFormat: any): string | Date | null {
if (value === 'permanent') {
return 'permanent';
}
return this.adapter.parse(value, parseFormat);
}
isDateInstance(obj: any): boolean {
if (obj === 'permanent') {
return true;
}
return this.adapter.isDateInstance(obj);
}
isValid(date: string | Date): boolean {
return date instanceof Date
? this.adapter.isValid(date)
: date === 'permanent';
}
getYear(date: string | Date): number {
return date instanceof Date
? this.adapter.getYear(date)
: this.adapter.getYear(this._today);
}
getMonth(date: string | Date): number {
return date instanceof Date
? this.adapter.getMonth(date)
: this.adapter.getMonth(this._today);
}
getDate(date: string | Date): number {
return date instanceof Date
? this.adapter.getDate(date)
: this.adapter.getDate(this._today);
}
getDayOfWeek(date: string | Date): number {
return date instanceof Date
? this.adapter.getDayOfWeek(date)
: this.adapter.getDayOfWeek(this._today);
}
getMonthNames(style: 'long' | 'short' | 'narrow'): string[] {
return this.adapter.getMonthNames(style);
}
getDateNames(): string[] {
return this.adapter.getDateNames();
}
getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] {
return this.adapter.getDayOfWeekNames(style);
}
getYearName(date: string | Date): string {
return date instanceof Date
? this.adapter.getYearName(date)
: this.adapter.getYearName(this._today);
}
getFirstDayOfWeek(): number {
return this.adapter.getFirstDayOfWeek();
}
getNumDaysInMonth(date: string | Date): number {
return date instanceof Date
? this.adapter.getNumDaysInMonth(date)
: this.adapter.getNumDaysInMonth(this._today);
}
clone(date: string | Date): string | Date {
return date instanceof Date
? this.adapter.clone(date)
: this.adapter.clone(this._today);
}
createDate(year: number, month: number, date: number): string | Date {
return this.adapter.createDate(year, month, date);
}
today(): string | Date {
return this.adapter.today();
}
format(date: string | Date, displayFormat: any): string {
return date instanceof Date
? this.adapter.format(date, displayFormat)
: (date as string);
}
addCalendarYears(date: string | Date, years: number): string | Date {
return date instanceof Date
? this.adapter.addCalendarYears(date, years)
: '';
}
addCalendarMonths(date: string | Date, months: number): string | Date {
return date instanceof Date
? this.adapter.addCalendarMonths(date, months)
: '';
}
addCalendarDays(date: string | Date, days: number): string | Date {
return date instanceof Date ? this.adapter.addCalendarDays(date, days) : '';
}
toIso8601(date: string | Date): string {
return date instanceof Date ? this.adapter.toIso8601(date) : '';
}
invalid(): string | Date {
return this.adapter.invalid();
}
}
{
"name": "ng-date-picker",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^15.0.0",
"@angular/cdk": "^15.0.1",
"@angular/common": "^15.0.0",
"@angular/compiler": "^15.0.0",
"@angular/core": "^15.0.0",
"@angular/forms": "^15.0.0",
"@angular/material": "^15.0.1",
"@angular/platform-browser": "^15.0.0",
"@angular/platform-browser-dynamic": "^15.0.0",
"@angular/router": "^15.0.0",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.12.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.0.2",
"@angular/cli": "~15.0.2",
"@angular/compiler-cli": "^15.0.0",
"@types/jasmine": "~4.3.0",
"jasmine-core": "~4.5.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.8.2"
}
}