10

I'm working on an Angular App with Material Design, and I'm using Moment.js to parse and format dates.

In one of my pages I have a Material's Datepicker. I have followed the material's guide-lines to make the Datepicker work with moment object - instead on the native Date object.

Also I managed to display the Date in the Datepicker in a certain format. But this format in hard coded in the Component's providers. How can I change the format during run-time, according to the user preferences?

Here is what I have this far:

I've installed npm's packages:

$ npm i moment -S
$ npm i @angular/material-moment-adapter -S
// app.module.ts
import { MatMomentDateModule } from '@angular/material-moment-adapter'
...
@NgModule({
  imports: [
    MatMomentDateModule,
...
// demo.component.ts
import * as moment from 'moment';
import { MAT_DATE_FORMATS } from '@angular/material/core';
const MY_FORMATS = {
  parse: {
    dateInput: 'LL',
  },
  display: {
    dateInput: 'LL',
    monthYearLabel: 'MMM YYYY',
    dateA11yLabel: 'LL',
    monthYearA11yLabel: 'MMMM YYYY',
  },
};
@Component({
  selector: 'app-demo',
  templateUrl: './demo.component.html',
  styleUrls: ['./demo.component.scss'],
  providers: [
    { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
  ],
})
export class DemoComponent {
   public dateVal = moment();
}
// demo.component.html
<mat-form-field>
  <input matInput [matDatepicker]="myDatePicker" [(ngModel)]="dateVal">
  <mat-datepicker-toggle matSuffix [for]="myDatePicker"></mat-datepicker-toggle>
  <mat-datepicker #myDatePicker></mat-datepicker>
</mat-form-field>

As you can see, the date-format is defined in the Component's providers. How can I change this in run-time?

A working example could be found here:
https://stackblitz.com/angular/mgaargebgpd?file=app%2Fdatepicker-formats-example.ts

3 Answers 3

23

OK, so I finally find a way to change the mat-date-picker format during run-time (the documentation didn't help at all).

Step #1 - Create a service that will provide the formatting.

You probable already have a service like this, if no you should create one, so you could control the date-formatting in one place.

// date-time.service
import { Injectable } from '@angular/core';
import * as moment from 'moment';

@Injectable({ providedIn: 'root' })
export class DateTimeService
{
  public getFormat(): string
  {
    return "DD-MM-YYYY"; // add you own logic here
  }
  public getLocale(): string
  {
    return "he-IL"; // add you own logic here
  }  
}

Step #2 - Create a CustomDateAdapter, that will be responsible for parsing the date during run-time

// customDateAdapter.ts
import { Injectable } from '@angular/core';
import { MomentDateAdapter } from '@angular/material-moment-adapter';
import * as moment from 'moment';
import { DateTimeService } from './date-time.service';

@Injectable()
export class CustomDateAdapter extends MomentDateAdapter
{
  constructor(private _dateTimeService: DateTimeService)
  {
    super('en-US'); // set default locale
  }

  public format(date: moment.Moment, displayFormat: string): string
  {
    const locale = this._dateTimeService.getLocale();
    const format = this._dateTimeService.getFormat();

    return date.locale(locale).format(format);
  }
}

Please Notice: That "CustomDateAdapter" is a regular class, and not a component. Although we are injecting a service to this class. To achieve this we need to add the @Injectable() decorator to the "CustomDateAdapter", and make some light changes in the app.module.ts.

Step #3 - Modify the app.module.ts to support custom formating and to allow Dependence Injection to CustomDateAdapter.

// app.module.ts
import { DateAdapter, MatNativeDateModule } from '@angular/material';
import { MatMomentDateModule } from '@angular/material-moment-adapter'
import { CustomDateAdapter } from './<some-path>/customDateAdapter';

@NgModule({
  imports:
  [
    MatNativeDateModule,
    MatMomentDateModule
  ],
  providers:
  [
    CustomDateAdapter, // so we could inject services to 'CustomDateAdapter'
    { provide: DateAdapter, useClass: CustomDateAdapter }, // Parse MatDatePicker Format
  ]
})
export class AppModule { /* ... */ }

P.S
Please notice that the code from the question (from the "demo.component.ts") isn't relevant any more.

Demo at Stackblitz

Sign up to request clarification or add additional context in comments.

5 Comments

can you create a stackblitz demo?
This will not work if I need two different format in 2 different page.
That was really helpful!
Using this method changes the display value in the header too, so instead of showing "Feb 2021" for the current month, it will display the selected date in whatever format you've provided here. That may be acceptable for your use case, just wanted to point it out.
the format changes only after the selected date. how to make it change instantly?
1

Thanks for your idea! Just for clarification. You can use non-singleton service for setting custom formatting of datepicker value. Extending your code:

import { Injectable } from '@angular/core';
    @Injectable({ providedIn: 'root' })
    export class DateTimeService {
        private _format = 'DD.MM.YYYY';

        set format(value: string) {
            this._format = value;
        }

        public getFormat(): string {
            return this._format;
        }

        public getLocale(): string {
            return 'ru-ru';
        }
    }

If you inject this service like this:

providers: [
        { provide: MAT_DATE_LOCALE, useValue: 'ru-ru' },
        CustomDateAdapter,
        DateTimeService,
        { provide: DateAdapter, useClass: CustomDateAdapter, deps: [DateTimeService] }
]

you can store any value from your component during component working.

constructor(private dateTimeService: DateTimeService) {
    }

    ngOnInit() {
        this.dateTimeService.format = this.format;
    }

Comments

0

update: @Gil Epshtain's solution doesn't work if the user wants to manually input DateTime instead of pick DateTime from DateTime picker (remove read-only attribute on input DateTime). my update on customDateAdapter.ts:

public parse(value: any, parseFormat: string | string[]): moment.Moment | null {
if(!value) {
  return null
}
const format = this._dateTimeService.getFormat();
return _moment(value, format, false);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.