From f0e56f01a4fb833ccace1248b227e3627e0f1f0d Mon Sep 17 00:00:00 2001 From: swseverance Date: Wed, 29 Jul 2020 10:23:45 -0400 Subject: [PATCH] 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. --- .../adapter/moment-date-adapter.spec.ts | 6 ++++++ src/material/core/datetime/date-adapter.ts | 10 +++++++++ .../core/datetime/native-date-adapter.spec.ts | 12 +++++++++++ src/material/datepicker/calendar.ts | 16 ++++---------- .../datepicker/date-range-input-parts.ts | 5 +++-- src/material/datepicker/date-range-input.ts | 12 ++--------- src/material/datepicker/datepicker-base.ts | 10 +-------- .../datepicker/datepicker-input-base.ts | 21 +++++++------------ src/material/datepicker/datepicker-input.ts | 4 ++-- src/material/datepicker/month-view.ts | 18 ++++++---------- src/material/datepicker/multi-year-view.ts | 18 ++++++---------- src/material/datepicker/year-view.ts | 18 ++++++---------- tools/public_api_guard/material/core.d.ts | 1 + 13 files changed, 67 insertions(+), 84 deletions(-) diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts index 41a97f3a6dde..7eef19ef1dc8 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts @@ -319,6 +319,12 @@ describe('MomentDateAdapter', () => { expect(adapter.isDateInstance(d)).toBe(false); }); + it('should provide a method to return a valid date or null', () => { + let d = moment(); + expect(adapter.getValidDateOrNull(d)).toBe(d); + expect(adapter.getValidDateOrNull(moment(NaN))).toBeNull(); + }); + it('should create valid dates from valid ISO strings', () => { assertValidDate(adapter.deserialize('1985-04-12T23:20:50.52Z'), true); assertValidDate(adapter.deserialize('1996-12-19T16:39:57-08:00'), true); diff --git a/src/material/core/datetime/date-adapter.ts b/src/material/core/datetime/date-adapter.ts index 812de536d019..15337667476e 100644 --- a/src/material/core/datetime/date-adapter.ts +++ b/src/material/core/datetime/date-adapter.ts @@ -203,6 +203,16 @@ export abstract class DateAdapter { */ abstract invalid(): D; + /** + * Given a potential date object, returns that same date object if it is + * a valid date, or `null` if it's not a valid date. + * @param obj The object to check. + * @returns A date or `null`. + */ + getValidDateOrNull(obj: unknown): D | null { + return this.isDateInstance(obj) && this.isValid(obj as D) ? obj as D : null; + } + /** * Attempts to deserialize a value to a valid date object. This is different from parsing in that * deserialize should only accept non-ambiguous, locale-independent formats (e.g. a ISO 8601 diff --git a/src/material/core/datetime/native-date-adapter.spec.ts b/src/material/core/datetime/native-date-adapter.spec.ts index 7a165a8edf24..9411d9abe721 100644 --- a/src/material/core/datetime/native-date-adapter.spec.ts +++ b/src/material/core/datetime/native-date-adapter.spec.ts @@ -349,6 +349,18 @@ describe('NativeDateAdapter', () => { expect(adapter.isDateInstance(d)).toBe(false); }); + it('should provide a method to return a valid date or null', () => { + let d = new Date(); + expect(adapter.getValidDateOrNull(d)).toBe(d); + expect(adapter.getValidDateOrNull(new Date(NaN))).toBeNull(); + expect(adapter.getValidDateOrNull(null)).toBeNull(); + expect(adapter.getValidDateOrNull(undefined)).toBeNull(); + expect(adapter.getValidDateOrNull('')).toBeNull(); + expect(adapter.getValidDateOrNull(0)).toBeNull(); + expect(adapter.getValidDateOrNull('Wed Jul 28 1993')).toBeNull(); + expect(adapter.getValidDateOrNull('1595204418000')).toBeNull(); + }); + it('should create dates from valid ISO strings', () => { assertValidDate(adapter.deserialize('1985-04-12T23:20:50.52Z'), true); assertValidDate(adapter.deserialize('1996-12-19T16:39:57-08:00'), true); diff --git a/src/material/datepicker/calendar.ts b/src/material/datepicker/calendar.ts index dcc37f147f51..7bf85a1a70c5 100644 --- a/src/material/datepicker/calendar.ts +++ b/src/material/datepicker/calendar.ts @@ -206,7 +206,7 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes @Input() get startAt(): D | null { return this._startAt; } set startAt(value: D | null) { - this._startAt = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _startAt: D | null; @@ -220,7 +220,7 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes if (value instanceof DateRange) { this._selected = value; } else { - this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } } private _selected: DateRange | D | null; @@ -229,7 +229,7 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes @Input() get minDate(): D | null { return this._minDate; } set minDate(value: D | null) { - this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _minDate: D | null; @@ -237,7 +237,7 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes @Input() get maxDate(): D | null { return this._maxDate; } set maxDate(value: D | null) { - this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _maxDate: D | null; @@ -417,14 +417,6 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes this.currentView = view; } - /** - * @param obj The object to check. - * @returns The given object if it is both a date instance and valid, otherwise null. - */ - private _getValidDateOrNull(obj: any): D | null { - return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null; - } - /** Returns the component instance that corresponds to the current calendar view. */ private _getCurrentViewComponent() { return this.monthView || this.yearView || this.multiYearView; diff --git a/src/material/datepicker/date-range-input-parts.ts b/src/material/datepicker/date-range-input-parts.ts index 74c93d7f9303..f21954106485 100644 --- a/src/material/datepicker/date-range-input-parts.ts +++ b/src/material/datepicker/date-range-input-parts.ts @@ -200,7 +200,8 @@ const _MatDateRangeInputBase: export class MatStartDate extends _MatDateRangeInputBase implements CanUpdateErrorState { /** Validator that checks that the start date isn't after the end date. */ private _startValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - const start = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value)); + const start = this._dateAdapter.getValidDateOrNull( + this._dateAdapter.deserialize(control.value)); const end = this._model ? this._model.selection.end : null; return (!start || !end || this._dateAdapter.compareDate(start, end) <= 0) ? @@ -284,7 +285,7 @@ export class MatStartDate extends _MatDateRangeInputBase implements CanUpd export class MatEndDate extends _MatDateRangeInputBase implements CanUpdateErrorState { /** Validator that checks that the end date isn't before the start date. */ private _endValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - const end = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value)); + const end = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value)); const start = this._model ? this._model.selection.start : null; return (!end || !start || this._dateAdapter.compareDate(end, start) >= 0) ? diff --git a/src/material/datepicker/date-range-input.ts b/src/material/datepicker/date-range-input.ts index 10940b41f9fe..856f15318e70 100644 --- a/src/material/datepicker/date-range-input.ts +++ b/src/material/datepicker/date-range-input.ts @@ -129,7 +129,7 @@ export class MatDateRangeInput implements MatFormFieldControl>, @Input() get min(): D | null { return this._min; } set min(value: D | null) { - this._min = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._min = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); this._revalidate(); } private _min: D | null; @@ -138,7 +138,7 @@ export class MatDateRangeInput implements MatFormFieldControl>, @Input() get max(): D | null { return this._max; } set max(value: D | null) { - this._max = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._max = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); this._revalidate(); } private _max: D | null; @@ -319,14 +319,6 @@ export class MatDateRangeInput implements MatFormFieldControl>, return formField && formField._hasFloatingLabel() ? formField._labelId : null; } - /** - * @param obj The object to check. - * @returns The given object if it is both a date instance and valid, otherwise null. - */ - private _getValidDateOrNull(obj: any): D | null { - return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null; - } - /** Re-runs the validators on the start/end inputs. */ private _revalidate() { if (this._startInput) { diff --git a/src/material/datepicker/datepicker-base.ts b/src/material/datepicker/datepicker-base.ts index 40ffe697a959..72d62fd32c3d 100644 --- a/src/material/datepicker/datepicker-base.ts +++ b/src/material/datepicker/datepicker-base.ts @@ -253,7 +253,7 @@ export abstract class MatDatepickerBase, S, return this._startAt || (this._datepickerInput ? this._datepickerInput.getStartValue() : null); } set startAt(value: D | null) { - this._startAt = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._startAt = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _startAt: D | null; @@ -641,14 +641,6 @@ export abstract class MatDatepickerBase, S, ]); } - /** - * @param obj The object to check. - * @returns The given object if it is both a date instance and valid, otherwise null. - */ - private _getValidDateOrNull(obj: any): D | null { - return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null; - } - static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_touchUi: BooleanInput; } diff --git a/src/material/datepicker/datepicker-input-base.ts b/src/material/datepicker/datepicker-input-base.ts index a6a67e3ce31b..0d5974de9043 100644 --- a/src/material/datepicker/datepicker-input-base.ts +++ b/src/material/datepicker/datepicker-input-base.ts @@ -77,7 +77,7 @@ export abstract class MatDatepickerInputBase { - const controlValue = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value)); + const controlValue = this._dateAdapter.getValidDateOrNull( + this._dateAdapter.deserialize(control.value)); const dateFilter = this._getDateFilter(); return !dateFilter || !controlValue || dateFilter(controlValue) ? null : {'matDatepickerFilter': true}; @@ -157,7 +158,8 @@ export abstract class MatDatepickerInputBase { - const controlValue = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value)); + const controlValue = this._dateAdapter.getValidDateOrNull( + this._dateAdapter.deserialize(control.value)); const min = this._getMinDate(); return (!min || !controlValue || this._dateAdapter.compareDate(min, controlValue) <= 0) ? @@ -166,7 +168,8 @@ export abstract class MatDatepickerInputBase { - const controlValue = this._getValidDateOrNull(this._dateAdapter.deserialize(control.value)); + const controlValue = this._dateAdapter.getValidDateOrNull( + this._dateAdapter.deserialize(control.value)); const max = this._getMaxDate(); return (!max || !controlValue || this._dateAdapter.compareDate(max, controlValue) >= 0) ? @@ -318,7 +321,7 @@ export abstract class MatDatepickerInputBase extends MatDatepickerInputBase @Input() get min(): D | null { return this._min; } set min(value: D | null) { - this._min = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._min = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); this._validatorOnChange(); } private _min: D | null; @@ -92,7 +92,7 @@ export class MatDatepickerInput extends MatDatepickerInputBase @Input() get max(): D | null { return this._max; } set max(value: D | null) { - this._max = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._max = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); this._validatorOnChange(); } private _max: D | null; diff --git a/src/material/datepicker/month-view.ts b/src/material/datepicker/month-view.ts index f9b7ccf95887..090329234adf 100644 --- a/src/material/datepicker/month-view.ts +++ b/src/material/datepicker/month-view.ts @@ -76,7 +76,9 @@ export class MatMonthView implements AfterContentInit, OnDestroy { set activeDate(value: D) { const oldActiveDate = this._activeDate; const validDate = - this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today(); + this._dateAdapter.getValidDateOrNull( + this._dateAdapter.deserialize(value) + ) || this._dateAdapter.today(); this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate); if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) { this._init(); @@ -91,7 +93,7 @@ export class MatMonthView implements AfterContentInit, OnDestroy { if (value instanceof DateRange) { this._selected = value; } else { - this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } this._setRanges(this._selected); @@ -102,7 +104,7 @@ export class MatMonthView implements AfterContentInit, OnDestroy { @Input() get minDate(): D | null { return this._minDate; } set minDate(value: D | null) { - this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _minDate: D | null; @@ -110,7 +112,7 @@ export class MatMonthView implements AfterContentInit, OnDestroy { @Input() get maxDate(): D | null { return this._maxDate; } set maxDate(value: D | null) { - this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _maxDate: D | null; @@ -412,14 +414,6 @@ export class MatMonthView implements AfterContentInit, OnDestroy { return null; } - /** - * @param obj The object to check. - * @returns The given object if it is both a date instance and valid, otherwise null. - */ - private _getValidDateOrNull(obj: any): D | null { - return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null; - } - /** Determines whether the user has the RTL layout direction. */ private _isRtl() { return this._dir && this._dir.value === 'rtl'; diff --git a/src/material/datepicker/multi-year-view.ts b/src/material/datepicker/multi-year-view.ts index 587ab0125b8a..c2380da8137b 100644 --- a/src/material/datepicker/multi-year-view.ts +++ b/src/material/datepicker/multi-year-view.ts @@ -63,7 +63,9 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { set activeDate(value: D) { let oldActiveDate = this._activeDate; const validDate = - this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today(); + this._dateAdapter.getValidDateOrNull( + this._dateAdapter.deserialize(value) + ) || this._dateAdapter.today(); this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate); if (!isSameMultiYearView( @@ -80,7 +82,7 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { if (value instanceof DateRange) { this._selected = value; } else { - this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } this._setSelectedYear(value); @@ -92,7 +94,7 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { @Input() get minDate(): D | null { return this._minDate; } set minDate(value: D | null) { - this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _minDate: D | null; @@ -100,7 +102,7 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { @Input() get maxDate(): D | null { return this._maxDate; } set maxDate(value: D | null) { - this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _maxDate: D | null; @@ -280,14 +282,6 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { return false; } - /** - * @param obj The object to check. - * @returns The given object if it is both a date instance and valid, otherwise null. - */ - private _getValidDateOrNull(obj: any): D | null { - return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null; - } - /** Determines whether the user has the RTL layout direction. */ private _isRtl() { return this._dir && this._dir.value === 'rtl'; diff --git a/src/material/datepicker/year-view.ts b/src/material/datepicker/year-view.ts index f86b14f31eec..b6530b419940 100644 --- a/src/material/datepicker/year-view.ts +++ b/src/material/datepicker/year-view.ts @@ -60,7 +60,9 @@ export class MatYearView implements AfterContentInit, OnDestroy { set activeDate(value: D) { let oldActiveDate = this._activeDate; const validDate = - this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today(); + this._dateAdapter.getValidDateOrNull( + this._dateAdapter.deserialize(value) + ) || this._dateAdapter.today(); this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate); if (this._dateAdapter.getYear(oldActiveDate) !== this._dateAdapter.getYear(this._activeDate)) { this._init(); @@ -75,7 +77,7 @@ export class MatYearView implements AfterContentInit, OnDestroy { if (value instanceof DateRange) { this._selected = value; } else { - this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._selected = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } this._setSelectedMonth(value); @@ -86,7 +88,7 @@ export class MatYearView implements AfterContentInit, OnDestroy { @Input() get minDate(): D | null { return this._minDate; } set minDate(value: D | null) { - this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _minDate: D | null; @@ -94,7 +96,7 @@ export class MatYearView implements AfterContentInit, OnDestroy { @Input() get maxDate(): D | null { return this._maxDate; } set maxDate(value: D | null) { - this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value)); } private _maxDate: D | null; @@ -317,14 +319,6 @@ export class MatYearView implements AfterContentInit, OnDestroy { return false; } - /** - * @param obj The object to check. - * @returns The given object if it is both a date instance and valid, otherwise null. - */ - private _getValidDateOrNull(obj: any): D | null { - return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null; - } - /** Determines whether the user has the RTL layout direction. */ private _isRtl() { return this._dir && this._dir.value === 'rtl'; diff --git a/tools/public_api_guard/material/core.d.ts b/tools/public_api_guard/material/core.d.ts index 3838444717af..9026a50d2e43 100644 --- a/tools/public_api_guard/material/core.d.ts +++ b/tools/public_api_guard/material/core.d.ts @@ -67,6 +67,7 @@ export declare abstract class DateAdapter { abstract getMonth(date: D): number; abstract getMonthNames(style: 'long' | 'short' | 'narrow'): string[]; abstract getNumDaysInMonth(date: D): number; + getValidDateOrNull(obj: unknown): D | null; abstract getYear(date: D): number; abstract getYearName(date: D): string; abstract invalid(): D;