Skip to content

Commit 4b59ca1

Browse files
mmalerbajosephperrott
authored andcommitted
fix(datepicker): prevent matInput from clobbering date value (#7831)
1 parent 3ca801a commit 4b59ca1

File tree

6 files changed

+68
-15
lines changed

6 files changed

+68
-15
lines changed

src/demo-app/datepicker/datepicker-demo.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,15 @@ <h2>Input disabled, datepicker popup enabled</h2>
121121
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
122122
</mat-form-field>
123123
</p>
124+
125+
<h2>Datepicker with value property binding</h2>
126+
<p>
127+
<mat-datepicker-toggle [for]="datePicker4"></mat-datepicker-toggle>
128+
<mat-form-field>
129+
<input matInput [matDatepicker]="datePicker4" [value]="date" [min]="minDate"
130+
[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null"
131+
placeholder="Value binding">
132+
<mat-datepicker #datePicker4 [touchUi]="touch" [startAt]="startAt"
133+
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
134+
</mat-form-field>
135+
</p>

src/lib/datepicker/datepicker-input.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
OnDestroy,
2020
Optional,
2121
Output,
22-
Renderer2,
22+
Renderer2
2323
} from '@angular/core';
2424
import {
2525
AbstractControl,
@@ -29,10 +29,11 @@ import {
2929
ValidationErrors,
3030
Validator,
3131
ValidatorFn,
32-
Validators,
32+
Validators
3333
} from '@angular/forms';
3434
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
3535
import {MatFormField} from '@angular/material/form-field';
36+
import {MAT_INPUT_VALUE_ACCESSOR} from '@angular/material/input';
3637
import {Subscription} from 'rxjs/Subscription';
3738
import {MatDatepicker} from './datepicker';
3839
import {createMissingDateImplError} from './datepicker-errors';
@@ -70,7 +71,11 @@ export class MatDatepickerInputEvent<D> {
7071
/** Directive used to connect an input to a MatDatepicker. */
7172
@Directive({
7273
selector: 'input[matDatepicker]',
73-
providers: [MAT_DATEPICKER_VALUE_ACCESSOR, MAT_DATEPICKER_VALIDATORS],
74+
providers: [
75+
MAT_DATEPICKER_VALUE_ACCESSOR,
76+
MAT_DATEPICKER_VALIDATORS,
77+
{provide: MAT_INPUT_VALUE_ACCESSOR, useExisting: MatDatepickerInput},
78+
],
7479
host: {
7580
'[attr.aria-haspopup]': 'true',
7681
'[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null',

src/lib/datepicker/datepicker.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {MatDatepickerIntl, MatDatepickerModule} from './index';
3030

3131

3232
describe('MatDatepicker', () => {
33+
const SUPPORTS_INTL = typeof Intl != 'undefined';
34+
3335
afterEach(inject([OverlayContainer], (container: OverlayContainer) => {
3436
container.getContainerElement().parentNode!.removeChild(container.getContainerElement());
3537
}));
@@ -83,6 +85,12 @@ describe('MatDatepicker', () => {
8385
fixture.detectChanges();
8486
}));
8587

88+
it('should initialize with correct value shown in input', () => {
89+
if (SUPPORTS_INTL) {
90+
expect(fixture.nativeElement.querySelector('input').value).toBe('1/1/2020');
91+
}
92+
});
93+
8694
it('open non-touch should open popup', () => {
8795
expect(document.querySelector('.cdk-overlay-pane.mat-datepicker-popup')).toBeNull();
8896

src/lib/input/input-value-accessor.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {InjectionToken} from '@angular/core';
10+
11+
12+
/**
13+
* This token is used to inject the object whose value should be set into `MatInput`. If none is
14+
* provided, the native `HTMLInputElement` is used. Directives like `MatDatepickerInput` can provide
15+
* themselves for this token, in order to make `MatInput` delegate the getting and setting of the
16+
* value to them.
17+
*/
18+
export const MAT_INPUT_VALUE_ACCESSOR =
19+
new InjectionToken<{value: any}>('MAT_INPUT_VALUE_ACCESSOR');

src/lib/input/input.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,26 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {coerceBooleanProperty} from '@angular/cdk/coercion';
10+
import {getSupportedInputTypes, Platform} from '@angular/cdk/platform';
911
import {
1012
Directive,
1113
DoCheck,
1214
ElementRef,
15+
Inject,
1316
Input,
1417
OnChanges,
1518
OnDestroy,
1619
Optional,
1720
Renderer2,
18-
Self,
21+
Self
1922
} from '@angular/core';
20-
import {coerceBooleanProperty} from '@angular/cdk/coercion';
21-
import {FormGroupDirective, NgControl, NgForm, FormControl} from '@angular/forms';
22-
import {Platform, getSupportedInputTypes} from '@angular/cdk/platform';
23-
import {getMatInputUnsupportedTypeError} from './input-errors';
23+
import {FormControl, FormGroupDirective, NgControl, NgForm} from '@angular/forms';
2424
import {ErrorStateMatcher} from '@angular/material/core';
25-
import {Subject} from 'rxjs/Subject';
2625
import {MatFormFieldControl} from '@angular/material/form-field';
26+
import {Subject} from 'rxjs/Subject';
27+
import {getMatInputUnsupportedTypeError} from './input-errors';
28+
import {MAT_INPUT_VALUE_ACCESSOR} from './input-value-accessor';
2729

2830
// Invalid input type. Using one of these will throw an MatInputUnsupportedTypeError.
2931
const MAT_INPUT_INVALID_TYPES = [
@@ -70,7 +72,7 @@ export class MatInput implements MatFormFieldControl<any>, OnChanges, OnDestroy,
7072
protected _required = false;
7173
protected _id: string;
7274
protected _uid = `mat-input-${nextUniqueId++}`;
73-
protected _previousNativeValue = this.value;
75+
protected _previousNativeValue: any;
7476
private _readonly = false;
7577

7678
/** Whether the input is focused. */
@@ -129,10 +131,10 @@ export class MatInput implements MatFormFieldControl<any>, OnChanges, OnDestroy,
129131

130132
/** The input element's value. */
131133
@Input()
132-
get value() { return this._elementRef.nativeElement.value; }
133-
set value(value: string) {
134+
get value(): any { return this._inputValueAccessor.value; }
135+
set value(value: any) {
134136
if (value !== this.value) {
135-
this._elementRef.nativeElement.value = value;
137+
this._inputValueAccessor.value = value;
136138
this.stateChanges.next();
137139
}
138140
}
@@ -157,7 +159,14 @@ export class MatInput implements MatFormFieldControl<any>, OnChanges, OnDestroy,
157159
@Optional() @Self() public ngControl: NgControl,
158160
@Optional() protected _parentForm: NgForm,
159161
@Optional() protected _parentFormGroup: FormGroupDirective,
160-
private _defaultErrorStateMatcher: ErrorStateMatcher) {
162+
private _defaultErrorStateMatcher: ErrorStateMatcher,
163+
@Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR)
164+
private _inputValueAccessor: {value: any}) {
165+
// If no input value accessor was explicitly specified, use the element as the input value
166+
// accessor.
167+
this._inputValueAccessor = this._inputValueAccessor || this._elementRef.nativeElement;
168+
169+
this._previousNativeValue = this.value;
161170

162171
// Force setter to be called in case id was not specified.
163172
this.id = this.id;

src/lib/input/public-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ export * from './input-module';
1111
export * from './autosize';
1212
export * from './input';
1313
export * from './input-errors';
14-
14+
export * from './input-value-accessor';
1515

0 commit comments

Comments
 (0)