Skip to content

Commit f0e56f0

Browse files
author
swseverance
authored
feat(material/datepicker): add getValidDateOrNull method (#19915)
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 77b11f4 commit f0e56f0

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) ?
@@ -284,7 +285,7 @@ export class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpd
284285
export class MatEndDate<D> extends _MatDateRangeInputBase<D> implements CanUpdateErrorState {
285286
/** Validator that checks that the end date isn't before the start date. */
286287
private _endValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
287-
const end = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value));
288+
const end = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
288289
const start = this._model ? this._model.selection.start : null;
289290
return (!end || !start ||
290291
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
@@ -129,7 +129,7 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
129129
@Input()
130130
get min(): D | null { return this._min; }
131131
set min(value: D | null) {
132-
this._min = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
132+
this._min = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
133133
this._revalidate();
134134
}
135135
private _min: D | null;
@@ -138,7 +138,7 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
138138
@Input()
139139
get max(): D | null { return this._max; }
140140
set max(value: D | null) {
141-
this._max = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
141+
this._max = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
142142
this._revalidate();
143143
}
144144
private _max: D | null;
@@ -319,14 +319,6 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
319319
return formField && formField._hasFloatingLabel() ? formField._labelId : null;
320320
}
321321

322-
/**
323-
* @param obj The object to check.
324-
* @returns The given object if it is both a date instance and valid, otherwise null.
325-
*/
326-
private _getValidDateOrNull(obj: any): D | null {
327-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
328-
}
329-
330322
/** Re-runs the validators on the start/end inputs. */
331323
private _revalidate() {
332324
if (this._startInput) {

src/material/datepicker/datepicker-base.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
253253
return this._startAt || (this._datepickerInput ? this._datepickerInput.getStartValue() : null);
254254
}
255255
set startAt(value: D | null) {
256-
this._startAt = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
256+
this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
257257
}
258258
private _startAt: D | null;
259259

@@ -641,14 +641,6 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
641641
]);
642642
}
643643

644-
/**
645-
* @param obj The object to check.
646-
* @returns The given object if it is both a date instance and valid, otherwise null.
647-
*/
648-
private _getValidDateOrNull(obj: any): D | null {
649-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
650-
}
651-
652644
static ngAcceptInputType_disabled: BooleanInput;
653645
static ngAcceptInputType_touchUi: BooleanInput;
654646
}

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

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
7777
set value(value: D | null) {
7878
value = this._dateAdapter.deserialize(value);
7979
this._lastValueValid = this._isValidValue(value);
80-
value = this._getValidDateOrNull(value);
80+
value = this._dateAdapter.getValidDateOrNull(value);
8181
const oldDate = this.value;
8282
this._assignValue(value);
8383
this._formatValue(value);
@@ -149,15 +149,17 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
149149

150150
/** The form control validator for the date filter. */
151151
private _filterValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
152-
const controlValue = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value));
152+
const controlValue = this._dateAdapter.getValidDateOrNull(
153+
this._dateAdapter.deserialize(control.value));
153154
const dateFilter = this._getDateFilter();
154155
return !dateFilter || !controlValue || dateFilter(controlValue) ?
155156
null : {'matDatepickerFilter': true};
156157
}
157158

158159
/** The form control validator for the min date. */
159160
private _minValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
160-
const controlValue = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value));
161+
const controlValue = this._dateAdapter.getValidDateOrNull(
162+
this._dateAdapter.deserialize(control.value));
161163
const min = this._getMinDate();
162164
return (!min || !controlValue ||
163165
this._dateAdapter.compareDate(min, controlValue) <= 0) ?
@@ -166,7 +168,8 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
166168

167169
/** The form control validator for the max date. */
168170
private _maxValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
169-
const controlValue = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value));
171+
const controlValue = this._dateAdapter.getValidDateOrNull(
172+
this._dateAdapter.deserialize(control.value));
170173
const max = this._getMaxDate();
171174
return (!max || !controlValue ||
172175
this._dateAdapter.compareDate(max, controlValue) >= 0) ?
@@ -318,7 +321,7 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
318321
const lastValueWasValid = this._lastValueValid;
319322
let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);
320323
this._lastValueValid = this._isValidValue(date);
321-
date = this._getValidDateOrNull(date);
324+
date = this._dateAdapter.getValidDateOrNull(date);
322325

323326
if (!this._dateAdapter.sameDate(date, this.value)) {
324327
this._assignValue(date);
@@ -358,14 +361,6 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
358361
value ? this._dateAdapter.format(value, this._dateFormats.display.dateInput) : '';
359362
}
360363

361-
/**
362-
* @param obj The object to check.
363-
* @returns The given object if it is both a date instance and valid, otherwise null.
364-
*/
365-
protected _getValidDateOrNull(obj: any): D | null {
366-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
367-
}
368-
369364
/** Assigns a value to the model. */
370365
private _assignValue(value: D | null) {
371366
// 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)