Skip to content

Commit 3dc14ec

Browse files
committed
refactor(datepicker): implement range selection model
Implements the date range selection model in the individual range inputs.
1 parent ca03b8f commit 3dc14ec

File tree

8 files changed

+94
-35
lines changed

8 files changed

+94
-35
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,17 @@ export const MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER: FactoryProvider = {
213213
deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter],
214214
useFactory: MAT_SINGLE_DATE_SELECTION_MODEL_FACTORY,
215215
};
216+
217+
218+
/** @docs-private */
219+
export function MAT_RANGE_DATE_SELECTION_MODEL_FACTORY(
220+
parent: MatSingleDateSelectionModel<unknown>, adapter: DateAdapter<unknown>) {
221+
return parent || new MatRangeDateSelectionModel(adapter);
222+
}
223+
224+
/** Used to provide a range selection model to a component. */
225+
export const MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER: FactoryProvider = {
226+
provide: MatDateSelectionModel,
227+
deps: [[new Optional(), new SkipSelf(), MatDateSelectionModel], DateAdapter],
228+
useFactory: MAT_RANGE_DATE_SELECTION_MODEL_FACTORY,
229+
};

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

Lines changed: 30 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>
64+
extends MatDatepickerInputBase<D | null, DateRange<D | null>> 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,16 @@ 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+
protected _assignValueToModel = (value: D | null) => {
175+
if (this._model) {
176+
this._model.updateSelection(new DateRange(value, this._model.selection.end), this);
177+
}
178+
}
163179

164180
/** Gets the value that should be used when mirroring the input's size. */
165181
getMirrorValue(): string {
@@ -197,9 +213,15 @@ export class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpd
197213
{provide: NG_VALIDATORS, useExisting: MatEndDate, multi: true}
198214
]
199215
})
200-
export class MatEndDate<D> extends _MatDateRangeInputBase<D> implements CanUpdateErrorState {
216+
export class MatEndDate<D> extends _MatDateRangeInputBase<D | null> implements CanUpdateErrorState {
201217
// TODO(crisbeto): end-range-specific validators should go here.
202218
protected _validator = Validators.compose([this._parseValidator]);
219+
protected _getValueFromModel = (modelValue: DateRange<D | null>) => modelValue.end;
220+
protected _assignValueToModel = (value: D | null) => {
221+
if (this._model) {
222+
this._model.updateSelection(new DateRange(this._model.selection.start, value), this);
223+
}
224+
}
203225

204226
static ngAcceptInputType_disabled: BooleanInput;
205227
}

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, S = D> {
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, S>,
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, S = 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<S, D> | 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, S>> =
102+
new EventEmitter<MatDatepickerInputEvent<D, S>>();
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, S>> =
106+
new EventEmitter<MatDatepickerInputEvent<D, S>>();
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<S, D>): 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: S): 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
}

tools/public_api_guard/material/core.d.ts

Lines changed: 4 additions & 0 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: MatSingleDateSelectionModel<unknown>, adapter: DateAdapter<unknown>): MatSingleDateSelectionModel<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>;

tools/public_api_guard/material/datepicker.d.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export declare class MatDatepickerContent<D> extends _MatDatepickerContentMixinB
156156
static ɵfac: i0.ɵɵFactoryDef<MatDatepickerContent<any>>;
157157
}
158158

159-
export declare class MatDatepickerInput<D> extends MatDatepickerInputBase<D> {
159+
export declare class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null> {
160160
_dateFilter: (date: D | null) => boolean;
161161
_datepicker: MatDatepicker<D>;
162162
protected _validator: ValidatorFn | null;
@@ -165,8 +165,9 @@ export declare class MatDatepickerInput<D> extends MatDatepickerInputBase<D> {
165165
max: D | null;
166166
min: D | null;
167167
constructor(elementRef: ElementRef<HTMLInputElement>, dateAdapter: DateAdapter<D>, dateFormats: MatDateFormats, _formField: MatFormField);
168-
protected _assignModelValue(value: D | null): void;
168+
protected _assignValueToModel(value: D | null): void;
169169
_getThemePalette(): ThemePalette;
170+
protected _getValueFromModel(modelValue: D | null): D | null;
170171
protected _openPopup(): void;
171172
getConnectedOverlayOrigin(): ElementRef;
172173
getPopupConnectionElementRef(): ElementRef;
@@ -176,12 +177,12 @@ export declare class MatDatepickerInput<D> extends MatDatepickerInputBase<D> {
176177
static ɵfac: i0.ɵɵFactoryDef<MatDatepickerInput<any>>;
177178
}
178179

179-
export declare class MatDatepickerInputEvent<D> {
180-
target: MatDatepickerInputBase<D>;
180+
export declare class MatDatepickerInputEvent<D, S = D> {
181+
target: MatDatepickerInputBase<D, S>;
181182
targetElement: HTMLElement;
182183
value: D | null;
183184
constructor(
184-
target: MatDatepickerInputBase<D>,
185+
target: MatDatepickerInputBase<D, S>,
185186
targetElement: HTMLElement);
186187
}
187188

@@ -264,7 +265,9 @@ export declare class MatDateRangeInput<D> implements MatFormFieldControl<DateRan
264265
static ɵfac: i0.ɵɵFactoryDef<MatDateRangeInput<any>>;
265266
}
266267

267-
export declare class MatEndDate<D> extends _MatDateRangeInputBase<D> implements CanUpdateErrorState {
268+
export declare class MatEndDate<D> extends _MatDateRangeInputBase<D | null> implements CanUpdateErrorState {
269+
protected _assignValueToModel: (value: D | null) => void;
270+
protected _getValueFromModel: (modelValue: DateRange<D | null>) => D | null;
268271
protected _validator: ValidatorFn | null;
269272
static ngAcceptInputType_disabled: BooleanInput;
270273
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatEndDate<any>, "input[matEndDate]", never, {}, {}, never>;
@@ -329,7 +332,9 @@ export declare class MatMultiYearView<D> implements AfterContentInit, OnDestroy
329332
static ɵfac: i0.ɵɵFactoryDef<MatMultiYearView<any>>;
330333
}
331334

332-
export declare class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpdateErrorState {
335+
export declare class MatStartDate<D> extends _MatDateRangeInputBase<D | null> implements CanUpdateErrorState {
336+
protected _assignValueToModel: (value: D | null) => void;
337+
protected _getValueFromModel: (modelValue: DateRange<D | null>) => D | null;
333338
protected _validator: ValidatorFn | null;
334339
getMirrorValue(): string;
335340
static ngAcceptInputType_disabled: BooleanInput;

0 commit comments

Comments
 (0)