From 8b4b3e457c2e8023dc55800dd9458cc6bc7f5025 Mon Sep 17 00:00:00 2001
From: Miles Malerba
Date: Mon, 16 Oct 2017 11:07:56 -0700
Subject: [PATCH 1/3] fix(datepicker): prevent `matInput` from clobbering date
value
---
src/demo-app/datepicker/datepicker-demo.html | 12 ++++++++
src/lib/datepicker/datepicker-input.ts | 11 +++++--
src/lib/datepicker/datepicker.spec.ts | 4 +++
src/lib/input/input-value-accessor.ts | 13 ++++++++
src/lib/input/input.ts | 31 +++++++++++++-------
src/lib/input/public-api.ts | 2 +-
6 files changed, 58 insertions(+), 15 deletions(-)
create mode 100644 src/lib/input/input-value-accessor.ts
diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html
index 03de75ca83de..b9c6c7dba22c 100644
--- a/src/demo-app/datepicker/datepicker-demo.html
+++ b/src/demo-app/datepicker/datepicker-demo.html
@@ -121,3 +121,15 @@ Input disabled, datepicker popup enabled
[startView]="yearView ? 'year' : 'month'">
+
+Datepicker with value property binding
+
+
+
+
+
+
+
diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts
index d1ee19ad40bd..9614aed394dc 100644
--- a/src/lib/datepicker/datepicker-input.ts
+++ b/src/lib/datepicker/datepicker-input.ts
@@ -19,7 +19,7 @@ import {
OnDestroy,
Optional,
Output,
- Renderer2,
+ Renderer2
} from '@angular/core';
import {
AbstractControl,
@@ -29,10 +29,11 @@ import {
ValidationErrors,
Validator,
ValidatorFn,
- Validators,
+ Validators
} from '@angular/forms';
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
import {MatFormField} from '@angular/material/form-field';
+import {MAT_INPUT_VALUE_ACCESSOR} from '@angular/material/input';
import {Subscription} from 'rxjs/Subscription';
import {coerceDateProperty} from './coerce-date-property';
import {MatDatepicker} from './datepicker';
@@ -71,7 +72,11 @@ export class MatDatepickerInputEvent {
/** Directive used to connect an input to a MatDatepicker. */
@Directive({
selector: 'input[matDatepicker]',
- providers: [MAT_DATEPICKER_VALUE_ACCESSOR, MAT_DATEPICKER_VALIDATORS],
+ providers: [
+ MAT_DATEPICKER_VALUE_ACCESSOR,
+ MAT_DATEPICKER_VALIDATORS,
+ {provide: MAT_INPUT_VALUE_ACCESSOR, useExisting: MatDatepickerInput},
+ ],
host: {
'[attr.aria-haspopup]': 'true',
'[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null',
diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts
index 919539a44db3..0080abfbde54 100644
--- a/src/lib/datepicker/datepicker.spec.ts
+++ b/src/lib/datepicker/datepicker.spec.ts
@@ -82,6 +82,10 @@ describe('MatDatepicker', () => {
fixture.detectChanges();
}));
+ it('should initialize with correct value shown in input', () => {
+ expect(fixture.nativeElement.querySelector('input').value).toBe('1/1/2020');
+ });
+
it('open non-touch should open popup', () => {
expect(document.querySelector('.cdk-overlay-pane.mat-datepicker-popup')).toBeNull();
diff --git a/src/lib/input/input-value-accessor.ts b/src/lib/input/input-value-accessor.ts
new file mode 100644
index 000000000000..c50161c7e8e4
--- /dev/null
+++ b/src/lib/input/input-value-accessor.ts
@@ -0,0 +1,13 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {InjectionToken} from '@angular/core';
+
+
+export const MAT_INPUT_VALUE_ACCESSOR =
+ new InjectionToken<{value: any}>('MAT_INPIUT_VALUE_ACCESSOR');
diff --git a/src/lib/input/input.ts b/src/lib/input/input.ts
index 204f6b8a8557..a1171211ad93 100644
--- a/src/lib/input/input.ts
+++ b/src/lib/input/input.ts
@@ -6,24 +6,26 @@
* found in the LICENSE file at https://angular.io/license
*/
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
+import {getSupportedInputTypes, Platform} from '@angular/cdk/platform';
import {
Directive,
DoCheck,
ElementRef,
+ Inject,
Input,
OnChanges,
OnDestroy,
Optional,
Renderer2,
- Self,
+ Self
} from '@angular/core';
-import {coerceBooleanProperty} from '@angular/cdk/coercion';
-import {FormGroupDirective, NgControl, NgForm, FormControl} from '@angular/forms';
-import {Platform, getSupportedInputTypes} from '@angular/cdk/platform';
-import {getMatInputUnsupportedTypeError} from './input-errors';
+import {FormControl, FormGroupDirective, NgControl, NgForm} from '@angular/forms';
import {ErrorStateMatcher} from '@angular/material/core';
-import {Subject} from 'rxjs/Subject';
import {MatFormFieldControl} from '@angular/material/form-field';
+import {Subject} from 'rxjs/Subject';
+import {getMatInputUnsupportedTypeError} from './input-errors';
+import {MAT_INPUT_VALUE_ACCESSOR} from './input-value-accessor';
// Invalid input type. Using one of these will throw an MatInputUnsupportedTypeError.
const MAT_INPUT_INVALID_TYPES = [
@@ -70,7 +72,7 @@ export class MatInput implements MatFormFieldControl, OnChanges, OnDestroy,
protected _required = false;
protected _id: string;
protected _uid = `mat-input-${nextUniqueId++}`;
- protected _previousNativeValue = this.value;
+ protected _previousNativeValue: any;
private _readonly = false;
/** Whether the input is focused. */
@@ -129,10 +131,10 @@ export class MatInput implements MatFormFieldControl, OnChanges, OnDestroy,
/** The input element's value. */
@Input()
- get value() { return this._elementRef.nativeElement.value; }
- set value(value: string) {
+ get value(): any { return this._inputValueAccessor.value; }
+ set value(value: any) {
if (value !== this.value) {
- this._elementRef.nativeElement.value = value;
+ this._inputValueAccessor.value = value;
this.stateChanges.next();
}
}
@@ -157,7 +159,14 @@ export class MatInput implements MatFormFieldControl, OnChanges, OnDestroy,
@Optional() @Self() public ngControl: NgControl,
@Optional() protected _parentForm: NgForm,
@Optional() protected _parentFormGroup: FormGroupDirective,
- private _defaultErrorStateMatcher: ErrorStateMatcher) {
+ private _defaultErrorStateMatcher: ErrorStateMatcher,
+ @Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR)
+ private _inputValueAccessor: {value: any}) {
+ // If no input value accessor was explicitly specified, use the element as the input value
+ // accessor.
+ this._inputValueAccessor = this._inputValueAccessor || this._elementRef.nativeElement;
+
+ this._previousNativeValue = this.value;
// Force setter to be called in case id was not specified.
this.id = this.id;
diff --git a/src/lib/input/public-api.ts b/src/lib/input/public-api.ts
index bc0416c79413..5eaaf7d50575 100644
--- a/src/lib/input/public-api.ts
+++ b/src/lib/input/public-api.ts
@@ -11,5 +11,5 @@ export * from './input-module';
export * from './autosize';
export * from './input';
export * from './input-errors';
-
+export * from './input-value-accessor';
From 29f0281f9c751ad0737e2089458cdd7b885ecfff Mon Sep 17 00:00:00 2001
From: Miles Malerba
Date: Tue, 17 Oct 2017 08:53:16 -0700
Subject: [PATCH 2/3] address comments
---
src/lib/input/input-value-accessor.ts | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/lib/input/input-value-accessor.ts b/src/lib/input/input-value-accessor.ts
index c50161c7e8e4..158c8b7bba19 100644
--- a/src/lib/input/input-value-accessor.ts
+++ b/src/lib/input/input-value-accessor.ts
@@ -9,5 +9,11 @@
import {InjectionToken} from '@angular/core';
+/**
+ * This token is used to inject the object whose value should be set into `MatInput`. If none is
+ * provided, the native `HTMLInputElement` is used. Directives like `MatDatepickerInput` can provide
+ * themselves for this token, in order to make `MatInput` delegate the getting and setting of the
+ * value to them.
+ */
export const MAT_INPUT_VALUE_ACCESSOR =
- new InjectionToken<{value: any}>('MAT_INPIUT_VALUE_ACCESSOR');
+ new InjectionToken<{value: any}>('MAT_INPUT_VALUE_ACCESSOR');
From 10271d984bd0d31110e7a0693c5d7d238e3d5056 Mon Sep 17 00:00:00 2001
From: Miles Malerba
Date: Wed, 18 Oct 2017 08:46:24 -0700
Subject: [PATCH 3/3] fix test
---
src/lib/datepicker/datepicker.spec.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts
index 0080abfbde54..09819328d8d0 100644
--- a/src/lib/datepicker/datepicker.spec.ts
+++ b/src/lib/datepicker/datepicker.spec.ts
@@ -30,6 +30,8 @@ import {MatDatepickerIntl, MatDatepickerModule} from './index';
describe('MatDatepicker', () => {
+ const SUPPORTS_INTL = typeof Intl != 'undefined';
+
afterEach(inject([OverlayContainer], (container: OverlayContainer) => {
container.getContainerElement().parentNode!.removeChild(container.getContainerElement());
}));
@@ -83,7 +85,9 @@ describe('MatDatepicker', () => {
}));
it('should initialize with correct value shown in input', () => {
- expect(fixture.nativeElement.querySelector('input').value).toBe('1/1/2020');
+ if (SUPPORTS_INTL) {
+ expect(fixture.nativeElement.querySelector('input').value).toBe('1/1/2020');
+ }
});
it('open non-touch should open popup', () => {