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();
}
}
};
Edit may-2022:
Better solution is to extend MatDatepickerInput<D> to custom directive as:
import {Directive, ElementRef, forwardRef, Inject, Input, Optional} from '@angular/core';
import {
// MAT_DATEPICKER_VALIDATORS,
// MAT_DATEPICKER_VALUE_ACCESSOR,
MatDatepickerInput,
MatDatepickerInputEvent
} from '@angular/material/datepicker';
import {MAT_INPUT_VALUE_ACCESSOR} from '@angular/material/input';
import {numFormatDateFn} from '../staticMethods';
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
import {MAT_FORM_FIELD, MatFormField} from '@angular/material/form-field';
import {NG_VALIDATORS, NG_VALUE_ACCESSOR, Validators} from '@angular/forms';
import {MatDatepickerControl, MatDatepickerPanel} from '@angular/material/datepicker/datepicker-base';
/** @docs-private */
export const MAT_DATEPICKER_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MatDatePickerCustomDirective),
multi: true,
};
/** @docs-private */
export const MAT_DATEPICKER_VALIDATORS: any = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MatDatePickerCustomDirective),
multi: true,
};
@Directive({
selector: 'input[appMatDatePickerCustom]',
providers: [
MAT_DATEPICKER_VALUE_ACCESSOR,
MAT_DATEPICKER_VALIDATORS,
{provide: MAT_INPUT_VALUE_ACCESSOR, useExisting: MatDatePickerCustomDirective},
],
host: {
'class': 'mat-datepicker-input',
'[attr.aria-haspopup]': '_datepicker ? "dialog" : null',
'[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null',
'[attr.min]': 'min ? _dateAdapter.toIso8601(min) : null',
'[attr.max]': 'max ? _dateAdapter.toIso8601(max) : null',
// Used by the test harness to tie this input to its calendar. We can't depend on
// `aria-owns` for this, because it's only defined while the calendar is open.
'[attr.data-mat-calendar]': '_datepicker ? _datepicker.id : null',
'[disabled]': 'disabled',
'(input)': '_onInput($event.target.value)',
'(change)': '_onChange()',
'(blur)': '_onBlur()',
'(keydown)': '_onKeydown($event)',
},
})
export class MatDatePickerCustomDirective<D> extends MatDatepickerInput<D> {
/** The datepicker that this input is associated with. */
@Input()
set appMatDatePickerCustom(datepicker: MatDatepickerPanel<MatDatepickerControl<D>, D | null, D>) {
if (datepicker) {
this.matDatepicker = datepicker;
// this._datepicker = datepicker;
// this._closedSubscription = datepicker.closedStream.subscribe(() => this._onTouched());
// this._registerModel(datepicker.registerInput(this));
}
}
constructor(
elementRef: ElementRef<HTMLInputElement>,
@Optional() dateAdapter: DateAdapter<D>,
@Optional() @Inject(MAT_DATE_FORMATS) dateFormats: MatDateFormats,
@Optional() @Inject(MAT_FORM_FIELD) _formField?: MatFormField,
) {
super(elementRef, dateAdapter, dateFormats);
// this._validator = Validators.compose(super._getValidators());
}
//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(numFormatDateFn(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) {
console.warn('custom overrided _onInput in NBOX-MODULE EXECUTED!');
//debugger
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(numFormatDateFn(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(date);
// this._cvaOnChange(customFormatDate(date));
this._cvaOnChange(value);
}
if (lastValueWasValid !== this._lastValueValid) {
this._validatorOnChange();
}
}
};
}
and use it as:
<input
[formControl]="someControl"
[appMatDatePickerCustom]="picker"
type="text"
autocomplete="off">
<mat-datepicker-toggle [for]="picker"></mat-datepicker-toggle>
<mat-datepicker xPosition="end" #picker></mat-datepicker>
(dateChange)="onDateChange(true, $event.value._d)"to update the hidden field as needed. It even looks like you can use Angular's built in DatePipe for this. (we use moment.js, but this should work for either DateModule). If you feel this is the right approach, but need more information, I can go ahead and add relevant code as an answer... just let me know. I think this should be enough to get you in the right direction though.