Skip to content

Commit 2e76765

Browse files
author
Sam Severance
committed
feat(material/datepicker): add getValidDateOrNull method
Several components have identical implementations of a `_getValidDateOrNull` method. This PR reduces code duplication by adding a `getValidDateOrNull` method to the `DateAdapter` class for the components to use instead.
1 parent e08b90c commit 2e76765

File tree

13 files changed

+67
-84
lines changed

13 files changed

+67
-84
lines changed

src/material-moment-adapter/adapter/moment-date-adapter.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ describe('MomentDateAdapter', () => {
319319
expect(adapter.isDateInstance(d)).toBe(false);
320320
});
321321

322+
it('should provide a method to return a valid date or null', () => {
323+
let d = moment();
324+
expect(adapter.getValidDateOrNull(d)).toBe(d);
325+
expect(adapter.getValidDateOrNull(moment(NaN))).toBeNull();
326+
});
327+
322328
it('should create valid dates from valid ISO strings', () => {
323329
assertValidDate(adapter.deserialize('1985-04-12T23:20:50.52Z'), true);
324330
assertValidDate(adapter.deserialize('1996-12-19T16:39:57-08:00'), true);

src/material/core/datetime/date-adapter.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,16 @@ export abstract class DateAdapter<D> {
203203
*/
204204
abstract invalid(): D;
205205

206+
/**
207+
* Given a potential date object, returns that same date object if it is
208+
* a valid date, or `null` if it's not a valid date.
209+
* @param obj The object to check.
210+
* @returns A date or `null`.
211+
*/
212+
getValidDateOrNull(obj: unknown): D | null {
213+
return this.isDateInstance(obj) && this.isValid(obj as D) ? obj as D : null;
214+
}
215+
206216
/**
207217
* Attempts to deserialize a value to a valid date object. This is different from parsing in that
208218
* deserialize should only accept non-ambiguous, locale-independent formats (e.g. a ISO 8601

src/material/core/datetime/native-date-adapter.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,18 @@ describe('NativeDateAdapter', () => {
349349
expect(adapter.isDateInstance(d)).toBe(false);
350350
});
351351

352+
it('should provide a method to return a valid date or null', () => {
353+
let d = new Date();
354+
expect(adapter.getValidDateOrNull(d)).toBe(d);
355+
expect(adapter.getValidDateOrNull(new Date(NaN))).toBeNull();
356+
expect(adapter.getValidDateOrNull(null)).toBeNull();
357+
expect(adapter.getValidDateOrNull(undefined)).toBeNull();
358+
expect(adapter.getValidDateOrNull('')).toBeNull();
359+
expect(adapter.getValidDateOrNull(0)).toBeNull();
360+
expect(adapter.getValidDateOrNull('Wed Jul 28 1993')).toBeNull();
361+
expect(adapter.getValidDateOrNull('1595204418000')).toBeNull();
362+
});
363+
352364
it('should create dates from valid ISO strings', () => {
353365
assertValidDate(adapter.deserialize('1985-04-12T23:20:50.52Z'), true);
354366
assertValidDate(adapter.deserialize('1996-12-19T16:39:57-08:00'), true);

src/material/datepicker/calendar.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export class MatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
206206
@Input()
207207
get startAt(): D | null { return this._startAt; }
208208
set startAt(value: D | null) {
209-
this._startAt = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
209+
this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
210210
}
211211
private _startAt: D | null;
212212

@@ -220,7 +220,7 @@ export class MatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
220220
if (value instanceof DateRange) {
221221
this._selected = value;
222222
} else {
223-
this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
223+
this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
224224
}
225225
}
226226
private _selected: DateRange<D> | D | null;
@@ -229,15 +229,15 @@ export class MatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
229229
@Input()
230230
get minDate(): D | null { return this._minDate; }
231231
set minDate(value: D | null) {
232-
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
232+
this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
233233
}
234234
private _minDate: D | null;
235235

236236
/** The maximum selectable date. */
237237
@Input()
238238
get maxDate(): D | null { return this._maxDate; }
239239
set maxDate(value: D | null) {
240-
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
240+
this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
241241
}
242242
private _maxDate: D | null;
243243

@@ -417,14 +417,6 @@ export class MatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
417417
this.currentView = view;
418418
}
419419

420-
/**
421-
* @param obj The object to check.
422-
* @returns The given object if it is both a date instance and valid, otherwise null.
423-
*/
424-
private _getValidDateOrNull(obj: any): D | null {
425-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
426-
}
427-
428420
/** Returns the component instance that corresponds to the current calendar view. */
429421
private _getCurrentViewComponent() {
430422
return this.monthView || this.yearView || this.multiYearView;

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ const _MatDateRangeInputBase:
200200
export class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpdateErrorState {
201201
/** Validator that checks that the start date isn't after the end date. */
202202
private _startValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
203-
const start = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value));
203+
const start = this._dateAdapter.getValidDateOrNull(
204+
this._dateAdapter.deserialize(control.value));
204205
const end = this._model ? this._model.selection.end : null;
205206
return (!start || !end ||
206207
this._dateAdapter.compareDate(start, end) <= 0) ?
@@ -280,7 +281,7 @@ export class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpd
280281
export class MatEndDate<D> extends _MatDateRangeInputBase<D> implements CanUpdateErrorState {
281282
/** Validator that checks that the end date isn't before the start date. */
282283
private _endValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
283-
const end = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value));
284+
const end = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
284285
const start = this._model ? this._model.selection.start : null;
285286
return (!end || !start ||
286287
this._dateAdapter.compareDate(end, start) >= 0) ?

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
128128
@Input()
129129
get min(): D | null { return this._min; }
130130
set min(value: D | null) {
131-
this._min = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
131+
this._min = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
132132
this._revalidate();
133133
}
134134
private _min: D | null;
@@ -137,7 +137,7 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
137137
@Input()
138138
get max(): D | null { return this._max; }
139139
set max(value: D | null) {
140-
this._max = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
140+
this._max = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
141141
this._revalidate();
142142
}
143143
private _max: D | null;
@@ -314,14 +314,6 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
314314
return formField && formField._hasFloatingLabel() ? formField._labelId : null;
315315
}
316316

317-
/**
318-
* @param obj The object to check.
319-
* @returns The given object if it is both a date instance and valid, otherwise null.
320-
*/
321-
private _getValidDateOrNull(obj: any): D | null {
322-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
323-
}
324-
325317
/** Re-runs the validators on the start/end inputs. */
326318
private _revalidate() {
327319
if (this._startInput) {

src/material/datepicker/datepicker-base.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
243243
return this._startAt || (this._datepickerInput ? this._datepickerInput.getStartValue() : null);
244244
}
245245
set startAt(value: D | null) {
246-
this._startAt = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
246+
this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
247247
}
248248
private _startAt: D | null;
249249

@@ -625,14 +625,6 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
625625
]);
626626
}
627627

628-
/**
629-
* @param obj The object to check.
630-
* @returns The given object if it is both a date instance and valid, otherwise null.
631-
*/
632-
private _getValidDateOrNull(obj: any): D | null {
633-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
634-
}
635-
636628
static ngAcceptInputType_disabled: BooleanInput;
637629
static ngAcceptInputType_touchUi: BooleanInput;
638630
}

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

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
7272
set value(value: D | null) {
7373
value = this._dateAdapter.deserialize(value);
7474
this._lastValueValid = this._isValidValue(value);
75-
value = this._getValidDateOrNull(value);
75+
value = this._dateAdapter.getValidDateOrNull(value);
7676
const oldDate = this.value;
7777
this._assignValue(value);
7878
this._formatValue(value);
@@ -144,15 +144,17 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
144144

145145
/** The form control validator for the date filter. */
146146
private _filterValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
147-
const controlValue = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value));
147+
const controlValue = this._dateAdapter.getValidDateOrNull(
148+
this._dateAdapter.deserialize(control.value));
148149
const dateFilter = this._getDateFilter();
149150
return !dateFilter || !controlValue || dateFilter(controlValue) ?
150151
null : {'matDatepickerFilter': true};
151152
}
152153

153154
/** The form control validator for the min date. */
154155
private _minValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
155-
const controlValue = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value));
156+
const controlValue = this._dateAdapter.getValidDateOrNull(
157+
this._dateAdapter.deserialize(control.value));
156158
const min = this._getMinDate();
157159
return (!min || !controlValue ||
158160
this._dateAdapter.compareDate(min, controlValue) <= 0) ?
@@ -161,7 +163,8 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
161163

162164
/** The form control validator for the max date. */
163165
private _maxValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
164-
const controlValue = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value));
166+
const controlValue = this._dateAdapter.getValidDateOrNull(
167+
this._dateAdapter.deserialize(control.value));
165168
const max = this._getMaxDate();
166169
return (!max || !controlValue ||
167170
this._dateAdapter.compareDate(max, controlValue) >= 0) ?
@@ -300,7 +303,7 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
300303
const lastValueWasValid = this._lastValueValid;
301304
let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);
302305
this._lastValueValid = this._isValidValue(date);
303-
date = this._getValidDateOrNull(date);
306+
date = this._dateAdapter.getValidDateOrNull(date);
304307

305308
if (!this._dateAdapter.sameDate(date, this.value)) {
306309
this._assignValue(date);
@@ -340,14 +343,6 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
340343
value ? this._dateAdapter.format(value, this._dateFormats.display.dateInput) : '';
341344
}
342345

343-
/**
344-
* @param obj The object to check.
345-
* @returns The given object if it is both a date instance and valid, otherwise null.
346-
*/
347-
protected _getValidDateOrNull(obj: any): D | null {
348-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
349-
}
350-
351346
/** Assigns a value to the model. */
352347
private _assignValue(value: D | null) {
353348
// We may get some incoming values before the model was

src/material/datepicker/datepicker-input.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null, D>
8383
@Input()
8484
get min(): D | null { return this._min; }
8585
set min(value: D | null) {
86-
this._min = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
86+
this._min = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
8787
this._validatorOnChange();
8888
}
8989
private _min: D | null;
@@ -92,7 +92,7 @@ export class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null, D>
9292
@Input()
9393
get max(): D | null { return this._max; }
9494
set max(value: D | null) {
95-
this._max = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
95+
this._max = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
9696
this._validatorOnChange();
9797
}
9898
private _max: D | null;

src/material/datepicker/month-view.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
7676
set activeDate(value: D) {
7777
const oldActiveDate = this._activeDate;
7878
const validDate =
79-
this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
79+
this._dateAdapter.getValidDateOrNull(
80+
this._dateAdapter.deserialize(value)
81+
) || this._dateAdapter.today();
8082
this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
8183
if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) {
8284
this._init();
@@ -91,7 +93,7 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
9193
if (value instanceof DateRange) {
9294
this._selected = value;
9395
} else {
94-
this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
96+
this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
9597
}
9698

9799
this._setRanges(this._selected);
@@ -102,15 +104,15 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
102104
@Input()
103105
get minDate(): D | null { return this._minDate; }
104106
set minDate(value: D | null) {
105-
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
107+
this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
106108
}
107109
private _minDate: D | null;
108110

109111
/** The maximum selectable date. */
110112
@Input()
111113
get maxDate(): D | null { return this._maxDate; }
112114
set maxDate(value: D | null) {
113-
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
115+
this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
114116
}
115117
private _maxDate: D | null;
116118

@@ -412,14 +414,6 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
412414
return null;
413415
}
414416

415-
/**
416-
* @param obj The object to check.
417-
* @returns The given object if it is both a date instance and valid, otherwise null.
418-
*/
419-
private _getValidDateOrNull(obj: any): D | null {
420-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
421-
}
422-
423417
/** Determines whether the user has the RTL layout direction. */
424418
private _isRtl() {
425419
return this._dir && this._dir.value === 'rtl';

src/material/datepicker/multi-year-view.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ export class MatMultiYearView<D> implements AfterContentInit, OnDestroy {
6363
set activeDate(value: D) {
6464
let oldActiveDate = this._activeDate;
6565
const validDate =
66-
this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
66+
this._dateAdapter.getValidDateOrNull(
67+
this._dateAdapter.deserialize(value)
68+
) || this._dateAdapter.today();
6769
this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
6870

6971
if (!isSameMultiYearView(
@@ -80,7 +82,7 @@ export class MatMultiYearView<D> implements AfterContentInit, OnDestroy {
8082
if (value instanceof DateRange) {
8183
this._selected = value;
8284
} else {
83-
this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
85+
this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
8486
}
8587

8688
this._setSelectedYear(value);
@@ -92,15 +94,15 @@ export class MatMultiYearView<D> implements AfterContentInit, OnDestroy {
9294
@Input()
9395
get minDate(): D | null { return this._minDate; }
9496
set minDate(value: D | null) {
95-
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
97+
this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
9698
}
9799
private _minDate: D | null;
98100

99101
/** The maximum selectable date. */
100102
@Input()
101103
get maxDate(): D | null { return this._maxDate; }
102104
set maxDate(value: D | null) {
103-
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
105+
this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
104106
}
105107
private _maxDate: D | null;
106108

@@ -280,14 +282,6 @@ export class MatMultiYearView<D> implements AfterContentInit, OnDestroy {
280282
return false;
281283
}
282284

283-
/**
284-
* @param obj The object to check.
285-
* @returns The given object if it is both a date instance and valid, otherwise null.
286-
*/
287-
private _getValidDateOrNull(obj: any): D | null {
288-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
289-
}
290-
291285
/** Determines whether the user has the RTL layout direction. */
292286
private _isRtl() {
293287
return this._dir && this._dir.value === 'rtl';

0 commit comments

Comments
 (0)