Skip to content

Commit e8d558d

Browse files
committed
fix(input): properly determine input value
Right now the `MdInputDirective` tries to cache the `value` of the input. To do this, the `MdInputDirective` needs to listen for `NgControl` value changes and for native `(change)` events. This will be problematic when a value is set directly to the input element (using `[value]` property binding) because Angular is only able to recognize this change in the first ChangeDetection. ```html <md-input-container> <input md-input [value]="myValue" placeholder="Label"> </md-input-container> ``` The approach of updating the value in the `ngAfterViewInit` lifecycle hook, will result in a binding change while being in a ChangeDetection, which leads to a ChangeDetection error. ``` Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'. ``` Fixes #2441. Fixes #2363
1 parent dccbe41 commit e8d558d

File tree

2 files changed

+55
-18
lines changed

2 files changed

+55
-18
lines changed

src/lib/input/input-container.spec.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import {async, TestBed, inject} from '@angular/core/testing';
22
import {Component} from '@angular/core';
3-
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
3+
import {FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
44
import {By} from '@angular/platform-browser';
55
import {MdInputModule} from './input';
6-
import {MdInputContainer} from './input-container';
6+
import {MdInputContainer, MdInputDirective} from './input-container';
77
import {Platform} from '../core/platform/platform';
88
import {PlatformModule} from '../core/platform/index';
99
import {
@@ -41,6 +41,8 @@ describe('MdInputContainer', function () {
4141
MdInputContainerZeroTestController,
4242
MdTextareaWithBindings,
4343
MdInputContainerWithDisabled,
44+
MdInputContainerWithValueBinding,
45+
MdInputContainerWithFormControl,
4446
MdInputContainerMissingMdInputTestController
4547
],
4648
});
@@ -128,6 +130,20 @@ describe('MdInputContainer', function () {
128130
expect(el.classList.contains('md-empty')).toBe(false, 'should not be empty');
129131
}));
130132

133+
it('should not be empty when the value set before view init', async(() => {
134+
let fixture = TestBed.createComponent(MdInputContainerWithValueBinding);
135+
fixture.detectChanges();
136+
137+
let placeholderEl = fixture.debugElement.query(By.css('.md-input-placeholder')).nativeElement;
138+
139+
expect(placeholderEl.classList).not.toContain('md-empty');
140+
141+
fixture.componentInstance.value = '';
142+
fixture.detectChanges();
143+
144+
expect(placeholderEl.classList).toContain('md-empty');
145+
}));
146+
131147
it('should not treat the number 0 as empty', async(() => {
132148
let fixture = TestBed.createComponent(MdInputContainerZeroTestController);
133149
fixture.detectChanges();
@@ -141,6 +157,20 @@ describe('MdInputContainer', function () {
141157
});
142158
}));
143159

160+
it('should update the value when using FormControl.setValue', () => {
161+
let fixture = TestBed.createComponent(MdInputContainerWithFormControl);
162+
fixture.detectChanges();
163+
164+
let input = fixture.debugElement.query(By.directive(MdInputDirective))
165+
.injector.get(MdInputDirective) as MdInputDirective;
166+
167+
expect(input.value).toBeFalsy();
168+
169+
fixture.componentInstance.formControl.setValue('something');
170+
171+
expect(input.value).toBe('something');
172+
});
173+
144174
it('should add id', () => {
145175
let fixture = TestBed.createComponent(MdInputContainerTextTestController);
146176
fixture.detectChanges();
@@ -326,6 +356,13 @@ class MdInputContainerPlaceholderElementTestComponent {
326356
placeholder: string = 'Default Placeholder';
327357
}
328358

359+
@Component({
360+
template: `<md-input-container><input md-input [formControl]="formControl"></md-input-container>`
361+
})
362+
class MdInputContainerWithFormControl {
363+
formControl = new FormControl();
364+
}
365+
329366
@Component({
330367
template: `<md-input-container><input md-input [placeholder]="placeholder"></md-input-container>`
331368
})
@@ -429,6 +466,16 @@ class MdInputContainerZeroTestController {
429466
value = 0;
430467
}
431468

469+
@Component({
470+
template: `
471+
<md-input-container>
472+
<input md-input placeholder="Label" [value]="value">
473+
</md-input-container>`
474+
})
475+
class MdInputContainerWithValueBinding {
476+
value: string = 'Initial';
477+
}
478+
432479
@Component({
433480
template: `
434481
<md-input-container>

src/lib/input/input-container.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ export class MdHint {
7474
'[id]': 'id',
7575
'(blur)': '_onBlur()',
7676
'(focus)': '_onFocus()',
77-
'(input)': '_onInput()',
7877
}
7978
})
80-
export class MdInputDirective implements AfterContentInit {
79+
export class MdInputDirective {
80+
8181
/** Whether the element is disabled. */
8282
@Input()
8383
get disabled() { return this._disabled; }
@@ -116,8 +116,9 @@ export class MdInputDirective implements AfterContentInit {
116116
}
117117
private _type = 'text';
118118

119-
/** The element's value. */
120-
value: any;
119+
/** The input element's value. */
120+
get value() { return this._elementRef.nativeElement.value; }
121+
set value(value: string) { this._elementRef.nativeElement.value = value; }
121122

122123
/**
123124
* Emits an event when the placeholder changes so that the `md-input-container` can re-validate.
@@ -143,18 +144,9 @@ export class MdInputDirective implements AfterContentInit {
143144
constructor(private _elementRef: ElementRef,
144145
private _renderer: Renderer,
145146
@Optional() public _ngControl: NgControl) {
147+
146148
// Force setter to be called in case id was not specified.
147149
this.id = this.id;
148-
149-
if (this._ngControl && this._ngControl.valueChanges) {
150-
this._ngControl.valueChanges.subscribe((value) => {
151-
this.value = value;
152-
});
153-
}
154-
}
155-
156-
ngAfterContentInit() {
157-
this.value = this._elementRef.nativeElement.value;
158150
}
159151

160152
/** Focuses the input element. */
@@ -164,8 +156,6 @@ export class MdInputDirective implements AfterContentInit {
164156

165157
_onBlur() { this.focused = false; }
166158

167-
_onInput() { this.value = this._elementRef.nativeElement.value; }
168-
169159
/** Make sure the input is a supported type. */
170160
private _validateType() {
171161
if (MD_INPUT_INVALID_TYPES.indexOf(this._type) != -1) {

0 commit comments

Comments
 (0)