Skip to content

Commit fa95dc7

Browse files
committed
refactor: tweak date selection model signature and implement model in range input
Reworks the signature of the date selection model to make it easier to consume in the different datepicker input types. Also wires up the individual range inputs with their model.
1 parent ca03b8f commit fa95dc7

File tree

10 files changed

+107
-49
lines changed

10 files changed

+107
-49
lines changed

src/dev-app/datepicker/datepicker-demo.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ export class DatepickerDemo {
5959
return !(date.getFullYear() % 2) && Boolean(date.getMonth() % 2) && !(date.getDate() % 2);
6060
}
6161

62-
onDateInput = (e: MatDatepickerInputEvent<Date>) => this.lastDateInput = e.value;
63-
onDateChange = (e: MatDatepickerInputEvent<Date>) => this.lastDateChange = e.value;
62+
onDateInput = (e: MatDatepickerInputEvent<Date | null>) => this.lastDateInput = e.value;
63+
onDateChange = (e: MatDatepickerInputEvent<Date | null>) => this.lastDateChange = e.value;
6464

6565
// pass custom header component type as input
6666
customHeader = CustomHeader;

src/material/core/datetime/date-selection-model.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ export class DateRange<D> {
2626
readonly end: D | null) {}
2727
}
2828

29-
type ExtractDateTypeFromSelection<T> = T extends DateRange<infer D> ? D : NonNullable<T>;
30-
3129
/** Event emitted by the date selection model when its selection changes. */
3230
export interface DateSelectionModelChange<S> {
3331
/** New value for the selection. */
@@ -38,7 +36,7 @@ export interface DateSelectionModelChange<S> {
3836
}
3937

4038
/** A selection model containing a date selection. */
41-
export abstract class MatDateSelectionModel<S, D = ExtractDateTypeFromSelection<S>>
39+
export abstract class MatDateSelectionModel<D, S = D>
4240
implements OnDestroy {
4341
private _selectionChanged = new Subject<DateSelectionModelChange<S>>();
4442

@@ -85,7 +83,7 @@ export abstract class MatDateSelectionModel<S, D = ExtractDateTypeFromSelection<
8583

8684
/** A selection model that contains a single date. */
8785
@Injectable()
88-
export class MatSingleDateSelectionModel<D> extends MatDateSelectionModel<D | null, D> {
86+
export class MatSingleDateSelectionModel<D> extends MatDateSelectionModel<D | null> {
8987
constructor(adapter: DateAdapter<D>) {
9088
super(adapter, null);
9189
}
@@ -128,7 +126,7 @@ export class MatSingleDateSelectionModel<D> extends MatDateSelectionModel<D | nu
128126

129127
/** A selection model that contains a date range. */
130128
@Injectable()
131-
export class MatRangeDateSelectionModel<D> extends MatDateSelectionModel<DateRange<D>, D> {
129+
export class MatRangeDateSelectionModel<D> extends MatDateSelectionModel<D, DateRange<D>> {
132130
constructor(adapter: DateAdapter<D>) {
133131
super(adapter, new DateRange<D>(null, null));
134132
}
@@ -213,3 +211,17 @@ export const MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER: FactoryProvider = {
213211
deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter],
214212
useFactory: MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY,
215213
};
214+
215+
216+
/** @docs-private */
217+
export function MAT_RANGE_DATE_SELECTION_MODEL_FACTORY(
218+
parent: MatRangeDateSelectionModel<unknown>, adapter: DateAdapter<unknown>) {
219+
return parent || new MatRangeDateSelectionModel(adapter);
220+
}
221+
222+
/** Used to provide a range selection model to a component. */
223+
export const MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER: FactoryProvider = {
224+
provide: MatDateSelectionModel,
225+
deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter],
226+
useFactory: MAT_RANGE_DATE_SELECTION_MODEL_FACTORY,
227+
};

src/material/datepicker/calendar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ export class MatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
305305
@Optional() private _dateAdapter: DateAdapter<D>,
306306
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
307307
private _changeDetectorRef: ChangeDetectorRef,
308-
private _model: MatDateSelectionModel<D | null, D>) {
308+
private _model: MatDateSelectionModel<D | null>) {
309309

310310
if (!this._dateAdapter) {
311311
throw createMissingDateImplError('DateAdapter');

src/material/datepicker/date-range-input-parts.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
DateAdapter,
3535
MatDateFormats,
3636
ErrorStateMatcher,
37+
DateRange,
38+
MatDateSelectionModel,
3739
} from '@angular/material/core';
3840
import {BooleanInput} from '@angular/cdk/coercion';
3941
import {MatDatepickerInputBase} from './datepicker-input-base';
@@ -58,8 +60,8 @@ export const MAT_DATE_RANGE_INPUT_PARENT =
5860
* Base class for the individual inputs that can be projected inside a `mat-date-range-input`.
5961
*/
6062
@Directive()
61-
class MatDateRangeInputPartBase<D> extends MatDatepickerInputBase<D> implements OnInit, DoCheck {
62-
protected _validator: ValidatorFn | null;
63+
class MatDateRangeInputPartBase<D> extends MatDatepickerInputBase<D | null, DateRange<D | null>>
64+
implements OnInit, DoCheck {
6365

6466
/** @docs-private */
6567
ngControl: NgControl;
@@ -75,8 +77,13 @@ class MatDateRangeInputPartBase<D> extends MatDatepickerInputBase<D> implements
7577
@Optional() public _parentForm: NgForm,
7678
@Optional() public _parentFormGroup: FormGroupDirective,
7779
@Optional() dateAdapter: DateAdapter<D>,
78-
@Optional() @Inject(MAT_DATE_FORMATS) dateFormats: MatDateFormats) {
80+
@Optional() @Inject(MAT_DATE_FORMATS) dateFormats: MatDateFormats,
81+
82+
// TODO(crisbeto): this will be provided by the datepicker eventually.
83+
// We provide it here for the moment so we have something to test against.
84+
model: MatDateSelectionModel<DateRange<D | null>>) {
7985
super(elementRef, dateAdapter, dateFormats);
86+
super._registerModel(model);
8087
}
8188

8289
ngOnInit() {
@@ -123,9 +130,11 @@ class MatDateRangeInputPartBase<D> extends MatDatepickerInputBase<D> implements
123130
this._rangeInput._openDatepicker();
124131
}
125132

126-
protected _assignModelValue(_model: D | null): void {
127-
// TODO(crisbeto): implement
128-
}
133+
// Dummy property implementations since we can't pass an abstract class
134+
// into a mixin. These are overridden by the individual input classes.
135+
protected _validator: ValidatorFn | null;
136+
protected _assignValueToModel: (_value: D | null) => void;
137+
protected _getValueFromModel: (_modelValue: DateRange<D | null>) => D | null;
129138
}
130139

131140
const _MatDateRangeInputBase:
@@ -157,9 +166,17 @@ const _MatDateRangeInputBase:
157166
{provide: NG_VALIDATORS, useExisting: MatStartDate, multi: true}
158167
]
159168
})
160-
export class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpdateErrorState {
169+
export class MatStartDate<D> extends _MatDateRangeInputBase<D | null>
170+
implements CanUpdateErrorState {
161171
// TODO(crisbeto): start-range-specific validators should go here.
162172
protected _validator = Validators.compose([this._parseValidator]);
173+
protected _getValueFromModel = (modelValue: DateRange<D | null>) => modelValue.start;
174+
175+
protected _assignValueToModel = (value: D | null) => {
176+
if (this._model) {
177+
this._model.updateSelection(new DateRange(value, this._model.selection.end), this);
178+
}
179+
}
163180

164181
/** Gets the value that should be used when mirroring the input's size. */
165182
getMirrorValue(): string {
@@ -197,9 +214,15 @@ export class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpd
197214
{provide: NG_VALIDATORS, useExisting: MatEndDate, multi: true}
198215
]
199216
})
200-
export class MatEndDate<D> extends _MatDateRangeInputBase<D> implements CanUpdateErrorState {
217+
export class MatEndDate<D> extends _MatDateRangeInputBase<D | null> implements CanUpdateErrorState {
201218
// TODO(crisbeto): end-range-specific validators should go here.
202219
protected _validator = Validators.compose([this._parseValidator]);
220+
protected _getValueFromModel = (modelValue: DateRange<D | null>) => modelValue.end;
221+
protected _assignValueToModel = (value: D | null) => {
222+
if (this._model) {
223+
this._model.updateSelection(new DateRange(this._model.selection.start, value), this);
224+
}
225+
}
203226

204227
static ngAcceptInputType_disabled: BooleanInput;
205228
}

src/material/datepicker/date-range-input.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
Self,
2020
} from '@angular/core';
2121
import {MatFormFieldControl, MatFormField} from '@angular/material/form-field';
22-
import {DateRange} from '@angular/material/core';
22+
import {DateRange, MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER} from '@angular/material/core';
2323
import {NgControl, ControlContainer} from '@angular/forms';
2424
import {Subject} from 'rxjs';
2525
import {coerceBooleanProperty, BooleanInput} from '@angular/cdk/coercion';
@@ -49,6 +49,10 @@ let nextUniqueId = 0;
4949
providers: [
5050
{provide: MatFormFieldControl, useExisting: MatDateRangeInput},
5151
{provide: MAT_DATE_RANGE_INPUT_PARENT, useExisting: MatDateRangeInput},
52+
53+
// TODO(crisbeto): this will be provided by the datepicker eventually.
54+
// We provide it here for the moment so we have something to test against.
55+
MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER,
5256
]
5357
})
5458
export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,

src/material/datepicker/datepicker-input-base.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ import {createMissingDateImplError} from './datepicker-errors';
3939
* input or change event because the event may have been triggered by the user clicking on the
4040
* calendar popup. For consistency, we always use MatDatepickerInputEvent instead.
4141
*/
42-
export class MatDatepickerInputEvent<D> {
42+
export class MatDatepickerInputEvent<D, M = any> {
4343
/** The new value for the target datepicker input. */
4444
value: D | null;
4545

4646
constructor(
4747
/** Reference to the datepicker input component that emitted the event. */
48-
public target: MatDatepickerInputBase<D>,
48+
public target: MatDatepickerInputBase<D, M>,
4949
/** Reference to the native input element associated with the datepicker input. */
5050
public targetElement: HTMLElement) {
5151
this.value = this.target.value;
@@ -54,11 +54,13 @@ export class MatDatepickerInputEvent<D> {
5454

5555
/** Base class for datepicker inputs. */
5656
@Directive()
57-
export abstract class MatDatepickerInputBase<D> implements ControlValueAccessor, OnDestroy,
58-
Validator {
57+
export abstract class MatDatepickerInputBase<D, M = D>
58+
implements ControlValueAccessor, OnDestroy, Validator {
5959
/** The value of the input. */
6060
@Input()
61-
get value(): D | null { return this._model ? this._model.selection : this._pendingValue; }
61+
get value(): D | null {
62+
return this._model ? this._getValueFromModel(this._model.selection) : this._pendingValue;
63+
}
6264
set value(value: D | null) {
6365
value = this._dateAdapter.deserialize(value);
6466
this._lastValueValid = !value || this._dateAdapter.isValid(value);
@@ -71,7 +73,7 @@ export abstract class MatDatepickerInputBase<D> implements ControlValueAccessor,
7173
this._valueChange.emit(value);
7274
}
7375
}
74-
protected _model: MatDateSelectionModel<D | null, D> | undefined;
76+
protected _model: MatDateSelectionModel<M> | undefined;
7577

7678
/** Whether the datepicker-input is disabled. */
7779
@Input()
@@ -96,12 +98,12 @@ export abstract class MatDatepickerInputBase<D> implements ControlValueAccessor,
9698
private _disabled: boolean;
9799

98100
/** Emits when a `change` event is fired on this `<input>`. */
99-
@Output() readonly dateChange: EventEmitter<MatDatepickerInputEvent<D>> =
100-
new EventEmitter<MatDatepickerInputEvent<D>>();
101+
@Output() readonly dateChange: EventEmitter<MatDatepickerInputEvent<D, M>> =
102+
new EventEmitter<MatDatepickerInputEvent<D, M>>();
101103

102104
/** Emits when an `input` event is fired on this `<input>`. */
103-
@Output() readonly dateInput: EventEmitter<MatDatepickerInputEvent<D>> =
104-
new EventEmitter<MatDatepickerInputEvent<D>>();
105+
@Output() readonly dateInput: EventEmitter<MatDatepickerInputEvent<D, M>> =
106+
new EventEmitter<MatDatepickerInputEvent<D, M>>();
105107

106108
/** Emits when the value changes (either due to user input or programmatic change). */
107109
_valueChange = new EventEmitter<D | null>();
@@ -129,7 +131,7 @@ export abstract class MatDatepickerInputBase<D> implements ControlValueAccessor,
129131
null : {'matDatepickerParse': {'text': this._elementRef.nativeElement.value}};
130132
}
131133

132-
protected _registerModel(model: MatDateSelectionModel<D | null, D>): void {
134+
protected _registerModel(model: MatDateSelectionModel<M>): void {
133135
this._model = model;
134136
this._valueChangesSubscription.unsubscribe();
135137

@@ -139,11 +141,12 @@ export abstract class MatDatepickerInputBase<D> implements ControlValueAccessor,
139141

140142
this._valueChangesSubscription = this._model.selectionChanged.subscribe(event => {
141143
if (event.source !== this) {
142-
this._cvaOnChange(event.selection);
144+
const value = this._getValueFromModel(event.selection);
145+
this._cvaOnChange(value);
143146
this._onTouched();
144147
this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
145148
this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
146-
this._formatValue(event.selection);
149+
this._formatValue(value);
147150
}
148151
});
149152
}
@@ -152,7 +155,10 @@ export abstract class MatDatepickerInputBase<D> implements ControlValueAccessor,
152155
protected abstract _openPopup(): void;
153156

154157
/** Assigns a value to the input's model. */
155-
protected abstract _assignModelValue(model: D | null): void;
158+
protected abstract _assignValueToModel(model: D | null): void;
159+
160+
/** Converts a value from the model into a native value for the input. */
161+
protected abstract _getValueFromModel(modelValue: M): D | null;
156162

157163
/** The combined form control validator for this input. */
158164
protected abstract _validator: ValidatorFn | null;
@@ -272,7 +278,7 @@ export abstract class MatDatepickerInputBase<D> implements ControlValueAccessor,
272278
// We may get some incoming values before the model was
273279
// assigned. Save the value so that we can assign it later.
274280
if (this._model) {
275-
this._assignModelValue(value);
281+
this._assignValueToModel(value);
276282
this._pendingValue = null;
277283
} else {
278284
this._pendingValue = value;

src/material/datepicker/datepicker-input.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export const MAT_DATEPICKER_VALIDATORS: any = {
6969
},
7070
exportAs: 'matDatepickerInput',
7171
})
72-
export class MatDatepickerInput<D> extends MatDatepickerInputBase<D> {
72+
export class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null> {
7373
/** The datepicker that this input is associated with. */
7474
@Input()
7575
set matDatepicker(datepicker: MatDatepicker<D>) {
@@ -170,7 +170,11 @@ export class MatDatepickerInput<D> extends MatDatepickerInputBase<D> {
170170
}
171171
}
172172

173-
protected _assignModelValue(value: D | null): void {
173+
protected _getValueFromModel(modelValue: D | null): D | null {
174+
return modelValue;
175+
}
176+
177+
protected _assignValueToModel(value: D | null): void {
174178
if (this._model) {
175179
this._model.updateSelection(value, this);
176180
}

src/material/datepicker/datepicker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ export class MatDatepicker<D> implements OnDestroy, CanColor {
300300
@Optional() private _dateAdapter: DateAdapter<D>,
301301
@Optional() private _dir: Directionality,
302302
@Optional() @Inject(DOCUMENT) private _document: any,
303-
private _model: MatDateSelectionModel<D | null, D>) {
303+
private _model: MatDateSelectionModel<D | null>) {
304304
if (!this._dateAdapter) {
305305
throw createMissingDateImplError('DateAdapter');
306306
}
@@ -334,7 +334,7 @@ export class MatDatepicker<D> implements OnDestroy, CanColor {
334334
* @param input The datepicker input to register with this datepicker.
335335
* @returns Selection model that the input should hook itself up to.
336336
*/
337-
_registerInput(input: MatDatepickerInput<D>): MatDateSelectionModel<D | null, D> {
337+
_registerInput(input: MatDatepickerInput<D>): MatDateSelectionModel<D | null> {
338338
if (this._datepickerInput) {
339339
throw Error('A MatDatepicker can only be associated with a single input.');
340340
}

tools/public_api_guard/material/core.d.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ export declare const MAT_NATIVE_DATE_FORMATS: MatDateFormats;
211211

212212
export declare const MAT_OPTION_PARENT_COMPONENT: InjectionToken<MatOptionParentComponent>;
213213

214+
export declare function MAT_RANGE_DATE_SELECTION_MODEL_FACTORY(parent: MatRangeDateSelectionModel<unknown>, adapter: DateAdapter<unknown>): MatRangeDateSelectionModel<unknown>;
215+
216+
export declare const MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER: FactoryProvider;
217+
214218
export declare const MAT_RIPPLE_GLOBAL_OPTIONS: InjectionToken<RippleGlobalOptions>;
215219

216220
export declare function MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY(parent: MatSingleDateSelectionModel<unknown>, adapter: DateAdapter<unknown>): MatSingleDateSelectionModel<unknown>;
@@ -235,7 +239,7 @@ export declare type MatDateFormats = {
235239
};
236240
};
237241

238-
export declare abstract class MatDateSelectionModel<S, D = ExtractDateTypeFromSelection<S>> implements OnDestroy {
242+
export declare abstract class MatDateSelectionModel<D, S = D> implements OnDestroy {
239243
protected readonly adapter: DateAdapter<D>;
240244
readonly selection: S;
241245
selectionChanged: Observable<DateSelectionModelChange<S>>;
@@ -347,7 +351,7 @@ export declare class MatPseudoCheckboxModule {
347351

348352
export declare type MatPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate';
349353

350-
export declare class MatRangeDateSelectionModel<D> extends MatDateSelectionModel<DateRange<D>, D> {
354+
export declare class MatRangeDateSelectionModel<D> extends MatDateSelectionModel<D, DateRange<D>> {
351355
constructor(adapter: DateAdapter<D>);
352356
add(date: D | null): void;
353357
isComplete(): boolean;
@@ -383,7 +387,7 @@ export declare class MatRippleModule {
383387
static ɵmod: i0.ɵɵNgModuleDefWithMeta<MatRippleModule, [typeof i1.MatRipple], [typeof i2.MatCommonModule, typeof i3.PlatformModule], [typeof i1.MatRipple, typeof i2.MatCommonModule]>;
384388
}
385389

386-
export declare class MatSingleDateSelectionModel<D> extends MatDateSelectionModel<D | null, D> {
390+
export declare class MatSingleDateSelectionModel<D> extends MatDateSelectionModel<D | null> {
387391
constructor(adapter: DateAdapter<D>);
388392
add(date: D | null): void;
389393
isComplete(): boolean;

0 commit comments

Comments
 (0)