Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind the date from angular material date picker to the model as string?

We use the standard date picker component, coming from Angular material (v. 9.1.2) that looks like this:

<mat-form-field>
    <mat-label i18n>Date of birth</mat-label>
    <input matInput [matDatepicker]="picker" formControlName="dateOfBirth" />
    <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
    <mat-datepicker #picker></mat-datepicker>
</mat-form-field>

The dates are in ISO format, e.g. 1979-12-02. Once bound to the form and displayed, we get it back like by calling getRawValue on the whole form. However, this gets the date back as javascript Date, which is then converted to string and send to the backend in "full" ISO format, e.g. 1979-12-02TXX:00:00.000Z, which breaks the contact/API.

If we use the MatMomentDateModule instead of the MatNativeDateModule, we get back a moment js date (instead of the javascript Date), but this doesn't help with the formatting.

Is there a way to bind the raw value of the control as a string instead of a date? Preferably without wrapping the component in a ControlValueAccessor? Perhaps a custom DateAdapter?

like image 323
Milan Milanov Avatar asked Jun 11 '20 10:06

Milan Milanov


2 Answers

You are right, you should implement custom DateAdapter to work with short ISO date strings.
Basically you just need to create a class extending DateAdapter and implement following methods:

  abstract getYear(date: D): number;

  abstract getMonth(date: D): number;

  abstract getDate(date: D): number;

  abstract getDayOfWeek(date: D): number;

  abstract getMonthNames(style: 'long' | 'short' | 'narrow'): string[];

  abstract getDateNames(): string[];

  abstract getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[];

  abstract getYearName(date: D): string;

  abstract getFirstDayOfWeek(): number;

  abstract getNumDaysInMonth(date: D): number;

  abstract clone(date: D): D;

  abstract createDate(year: number, month: number, date: number): D;

  abstract today(): D;

  abstract parse(value: any, parseFormat: any): D | null;

  abstract format(date: D, displayFormat: any): string;

  abstract addCalendarYears(date: D, years: number): D;

  abstract addCalendarMonths(date: D, months: number): D;

  abstract addCalendarDays(date: D, days: number): D;

  abstract toIso8601(date: D): string;

  abstract isDateInstance(obj: any): boolean;

  abstract isValid(date: D): boolean;

  abstract invalid(): D;

There are two great examples provided by Angular team: MomentDateAdapter and NativeDateAdapter.
When the adapter is implemented you will need to add it to the module or component as follows:

  providers: [
    {provide: DateAdapter, useClass: YourISODateAdapter, deps: [...]}
  ],
like image 160
nickbullock Avatar answered Sep 29 '22 10:09

nickbullock


I've analyzed MatDatepickerInputBase source code and currently there is no option to configure what kind or format of value you would like to have in related FormControl. So based on this idea of overwriting class methods I've put this code in app.module and I obtained Date as string value in my desired format YYYY-MM-DD. String is passed to control when user enters date by hand or choses date in calendar component and if date is valid ofcourse. I use my own DateAdapter overridden class also but it is not related to this problem, because DateAdapter only formats date to display in MatDatepickerInput control by overriding parse() and format() methods.

    const customFormatDate = (date: Date) => formatDate(date, 'yyyy-MM-dd', 'en');
    
    MatDatepickerInput.prototype._registerModel = function(model: any): void {
      this._model = model;
      this._valueChangesSubscription.unsubscribe();
    
      if (this._pendingValue) {
        this._assignValue(this._pendingValue);
      }
    
      this._valueChangesSubscription = this._model.selectionChanged.subscribe(event => {
        if (this._shouldHandleChangeEvent(event)) {
          const value = this._getValueFromModel(event.selection);
          this._lastValueValid = this._isValidValue(value);
          // this._cvaOnChange(value);
          this._cvaOnChange(customFormatDate(value));
          this._onTouched();
          this._formatValue(value);
          this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
          this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
        }
      });
    };


    MatDatepickerInput.prototype._onInput =  function(value: string) {
      const lastValueWasValid = this._lastValueValid;
      let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);
      this._lastValueValid = this._isValidValue(date);
      date = this._dateAdapter.getValidDateOrNull(date);
    
      if (!this._dateAdapter.sameDate(date, this.value)) {
        this._assignValue(date);
    
        //this._cvaOnChange(date);
        this._cvaOnChange(customFormatDate(date));
    
        this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
      } else {
        // Call the CVA change handler for invalid values
        // since this is what marks the control as dirty.
        if ((value === '') || value && !this.value) {
          this._cvaOnChange(value);
        }
    
        if (lastValueWasValid !== this._lastValueValid) {
          this._validatorOnChange();
        }
      }
    };
like image 28
Michal.S Avatar answered Sep 29 '22 10:09

Michal.S