Skip to content

Commit 5dd582c

Browse files
crisbetojelbourn
authored andcommitted
fix(datepicker): unable to type in range inputs
In a previous fix I added some changes so that typing in an input counts as typing inside the entire date range input. The problem is that this causes some extra events to be fired which result in the user's typed value being overwritten as they're typing. These changes revert the previous fix and resolve the issue in a different way.
1 parent 373613e commit 5dd582c

File tree

2 files changed

+50
-42
lines changed

2 files changed

+50
-42
lines changed

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {
3939
} from '@angular/material/core';
4040
import {BooleanInput} from '@angular/cdk/coercion';
4141
import {MatDatepickerInputBase, DateFilterFn} from './datepicker-input-base';
42-
import {DateRange} from './date-selection-model';
42+
import {DateRange, MatDateSelectionModel} from './date-selection-model';
4343

4444
/** Parent component that should be wrapped around `MatStartDate` and `MatEndDate`. */
4545
export interface MatDateRangeInputParent<D> {
@@ -158,6 +158,15 @@ abstract class MatDateRangeInputPartBase<D>
158158
protected _parentDisabled() {
159159
return this._rangeInput._groupDisabled;
160160
}
161+
162+
_registerModel(model: MatDateSelectionModel<DateRange<D>, D>) {
163+
// The very first time the range inputs write their values, they don't know about the value
164+
// of the opposite input. When this is combined with the fact that `NgModel` defers writing
165+
// its value with a `Promise.resolve`, we can get into a situation where the first input
166+
// resets the value of the second. We work around it by deferring the registration of
167+
// the model, allowing the input enough time to assign the initial value.
168+
Promise.resolve().then(() => super._registerModel(model));
169+
}
161170
}
162171

163172
const _MatDateRangeInputBase:
@@ -225,11 +234,7 @@ export class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpd
225234
protected _assignValueToModel(value: D | null) {
226235
if (this._model) {
227236
const range = new DateRange(value, this._model.selection.end);
228-
229-
// Note that we pass the range input as the source of the event, rather than the current
230-
// field, because we treat the whole input as a single unit and we don't want the two
231-
// inner inputs to respond to each other's changes.
232-
this._model.updateSelection(range, this._rangeInput);
237+
this._model.updateSelection(range, this);
233238
}
234239
}
235240

@@ -310,11 +315,7 @@ export class MatEndDate<D> extends _MatDateRangeInputBase<D> implements CanUpdat
310315
protected _assignValueToModel(value: D | null) {
311316
if (this._model) {
312317
const range = new DateRange(this._model.selection.start, value);
313-
314-
// Note that we pass the range input as the source of the event, rather than the current
315-
// field, because we treat the whole input as a single unit and we don't want the two
316-
// inner inputs to respond to each other's changes.
317-
this._model.updateSelection(range, this._rangeInput);
318+
this._model.updateSelection(range, this);
318319
}
319320
}
320321

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

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {FocusMonitor} from '@angular/cdk/a11y';
1212
import {MatDateRangeInput} from './date-range-input';
1313
import {MatDateRangePicker} from './date-range-picker';
1414

15-
describe('MatDatepicker', () => {
15+
describe('MatDateRangeInput', () => {
1616
function createComponent<T>(component: Type<T>): ComponentFixture<T> {
1717
TestBed.configureTestingModule({
1818
imports: [
@@ -218,23 +218,25 @@ describe('MatDatepicker', () => {
218218
expect(rangeInput.empty).toBe(false);
219219
});
220220

221-
it('should mark the range controls as invalid if the start value is after the end value', () => {
222-
const fixture = createComponent(StandardRangePicker);
223-
fixture.detectChanges();
224-
const {start, end} = fixture.componentInstance.range.controls;
221+
it('should mark the range controls as invalid if the start value is after the end value',
222+
fakeAsync(() => {
223+
const fixture = createComponent(StandardRangePicker);
224+
fixture.detectChanges();
225+
tick();
226+
const {start, end} = fixture.componentInstance.range.controls;
225227

226-
expect(fixture.componentInstance.rangeInput.errorState).toBe(false);
227-
expect(start.errors?.matStartDateInvalid).toBeFalsy();
228-
expect(end.errors?.matEndDateInvalid).toBeFalsy();
228+
expect(fixture.componentInstance.rangeInput.errorState).toBe(false);
229+
expect(start.errors?.matStartDateInvalid).toBeFalsy();
230+
expect(end.errors?.matEndDateInvalid).toBeFalsy();
229231

230-
start.setValue(new Date(2020, 2, 2));
231-
end.setValue(new Date(2020, 1, 2));
232-
fixture.detectChanges();
232+
start.setValue(new Date(2020, 2, 2));
233+
end.setValue(new Date(2020, 1, 2));
234+
fixture.detectChanges();
233235

234-
expect(fixture.componentInstance.rangeInput.errorState).toBe(true);
235-
expect(start.errors?.matStartDateInvalid).toBeTruthy();
236-
expect(end.errors?.matEndDateInvalid).toBeTruthy();
237-
});
236+
expect(fixture.componentInstance.rangeInput.errorState).toBe(true);
237+
expect(start.errors?.matStartDateInvalid).toBeTruthy();
238+
expect(end.errors?.matEndDateInvalid).toBeTruthy();
239+
}));
238240

239241
it('should pass the minimum date from the range input to the inner inputs', () => {
240242
const fixture = createComponent(StandardRangePicker);
@@ -318,22 +320,24 @@ describe('MatDatepicker', () => {
318320
expect(startInput.focus).toHaveBeenCalled();
319321
});
320322

321-
it('should focus the end input when clicking on the form field when start has a value', () => {
322-
const fixture = createComponent(StandardRangePicker);
323-
fixture.detectChanges();
324-
const endInput = fixture.componentInstance.end.nativeElement;
325-
const formFieldContainer = fixture.nativeElement.querySelector('.mat-form-field-flex');
323+
it('should focus the end input when clicking on the form field when start has a value',
324+
fakeAsync(() => {
325+
const fixture = createComponent(StandardRangePicker);
326+
fixture.detectChanges();
327+
tick();
328+
const endInput = fixture.componentInstance.end.nativeElement;
329+
const formFieldContainer = fixture.nativeElement.querySelector('.mat-form-field-flex');
326330

327-
spyOn(endInput, 'focus').and.callThrough();
331+
spyOn(endInput, 'focus').and.callThrough();
328332

329-
fixture.componentInstance.range.controls.start.setValue(new Date());
330-
fixture.detectChanges();
333+
fixture.componentInstance.range.controls.start.setValue(new Date());
334+
fixture.detectChanges();
331335

332-
formFieldContainer.click();
333-
fixture.detectChanges();
336+
formFieldContainer.click();
337+
fixture.detectChanges();
334338

335-
expect(endInput.focus).toHaveBeenCalled();
336-
});
339+
expect(endInput.focus).toHaveBeenCalled();
340+
}));
337341

338342
it('should revalidate if a validation field changes', () => {
339343
const fixture = createComponent(StandardRangePicker);
@@ -404,7 +408,7 @@ describe('MatDatepicker', () => {
404408
expect(end.nativeElement.getAttribute('max')).toContain('2020');
405409
});
406410

407-
it('should pass the range input value through to the calendar', () => {
411+
it('should pass the range input value through to the calendar', fakeAsync(() => {
408412
const fixture = createComponent(StandardRangePicker);
409413
const {start, end} = fixture.componentInstance.range.controls;
410414
let overlayContainerElement: HTMLElement;
@@ -414,9 +418,11 @@ describe('MatDatepicker', () => {
414418
overlayContainerElement = overlayContainer.getContainerElement();
415419
})();
416420
fixture.detectChanges();
421+
tick();
417422

418423
fixture.componentInstance.rangePicker.open();
419424
fixture.detectChanges();
425+
tick();
420426

421427
const rangeTexts = Array.from(overlayContainerElement!.querySelectorAll([
422428
'.mat-calendar-body-range-start',
@@ -425,9 +431,9 @@ describe('MatDatepicker', () => {
425431
].join(','))).map(cell => cell.textContent!.trim());
426432

427433
expect(rangeTexts).toEqual(['2', '3', '4', '5']);
428-
});
434+
}));
429435

430-
it('should pass the comparison range through to the calendar', () => {
436+
it('should pass the comparison range through to the calendar', fakeAsync(() => {
431437
const fixture = createComponent(StandardRangePicker);
432438
let overlayContainerElement: HTMLElement;
433439

@@ -442,6 +448,7 @@ describe('MatDatepicker', () => {
442448

443449
fixture.componentInstance.rangePicker.open();
444450
fixture.detectChanges();
451+
tick();
445452

446453
const rangeTexts = Array.from(overlayContainerElement!.querySelectorAll([
447454
'.mat-calendar-body-comparison-start',
@@ -450,7 +457,7 @@ describe('MatDatepicker', () => {
450457
].join(','))).map(cell => cell.textContent!.trim());
451458

452459
expect(rangeTexts).toEqual(['2', '3', '4', '5']);
453-
});
460+
}));
454461

455462
it('should preserve the preselected values when assigning through ngModel', fakeAsync(() => {
456463
const start = new Date(2020, 1, 2);

0 commit comments

Comments
 (0)