Skip to content

Commit

Permalink
fix(datepicker): prevent matInput from clobbering date value (#7831)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmalerba authored and josephperrott committed Nov 10, 2017
1 parent 3ca801a commit 4b59ca1
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 15 deletions.
12 changes: 12 additions & 0 deletions src/demo-app/datepicker/datepicker-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,15 @@ <h2>Input disabled, datepicker popup enabled</h2>
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
</mat-form-field>
</p>

<h2>Datepicker with value property binding</h2>
<p>
<mat-datepicker-toggle [for]="datePicker4"></mat-datepicker-toggle>
<mat-form-field>
<input matInput [matDatepicker]="datePicker4" [value]="date" [min]="minDate"
[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null"
placeholder="Value binding">
<mat-datepicker #datePicker4 [touchUi]="touch" [startAt]="startAt"
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
</mat-form-field>
</p>
11 changes: 8 additions & 3 deletions src/lib/datepicker/datepicker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
OnDestroy,
Optional,
Output,
Renderer2,
Renderer2
} from '@angular/core';
import {
AbstractControl,
Expand All @@ -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 {MatDatepicker} from './datepicker';
import {createMissingDateImplError} from './datepicker-errors';
Expand Down Expand Up @@ -70,7 +71,11 @@ export class MatDatepickerInputEvent<D> {
/** 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',
Expand Down
8 changes: 8 additions & 0 deletions src/lib/datepicker/datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}));
Expand Down Expand Up @@ -83,6 +85,12 @@ describe('MatDatepicker', () => {
fixture.detectChanges();
}));

it('should initialize with correct value shown in input', () => {
if (SUPPORTS_INTL) {
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();

Expand Down
19 changes: 19 additions & 0 deletions src/lib/input/input-value-accessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @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';


/**
* 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_INPUT_VALUE_ACCESSOR');
31 changes: 20 additions & 11 deletions src/lib/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -70,7 +72,7 @@ export class MatInput implements MatFormFieldControl<any>, 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. */
Expand Down Expand Up @@ -129,10 +131,10 @@ export class MatInput implements MatFormFieldControl<any>, 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();
}
}
Expand All @@ -157,7 +159,14 @@ export class MatInput implements MatFormFieldControl<any>, 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;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/input/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ export * from './input-module';
export * from './autosize';
export * from './input';
export * from './input-errors';

export * from './input-value-accessor';

0 comments on commit 4b59ca1

Please sign in to comment.