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', () => {