Skip to content

Commit 02e5cea

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 angular#2441. Fixes angular#2363
1 parent b4b4224 commit 02e5cea

File tree

2 files changed

+55
-19
lines changed

2 files changed

+55
-19
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 {
@@ -43,6 +43,8 @@ describe('MdInputContainer', function () {
4343
MdInputContainerWithDisabled,
4444
MdInputContainerWithRequired,
4545
MdInputContainerWithType,
46+
MdInputContainerWithValueBinding,
47+
MdInputContainerWithFormControl,
4648
MdInputContainerMissingMdInputTestController
4749
],
4850
});
@@ -130,6 +132,20 @@ describe('MdInputContainer', function () {
130132
expect(el.classList.contains('md-empty')).toBe(false, 'should not be empty');
131133
}));
132134

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

162+
it('should update the value when using FormControl.setValue', () => {
163+
let fixture = TestBed.createComponent(MdInputContainerWithFormControl);
164+
fixture.detectChanges();
165+
166+
let input = fixture.debugElement.query(By.directive(MdInputDirective))
167+
.injector.get(MdInputDirective) as MdInputDirective;
168+
169+
expect(input.value).toBeFalsy();
170+
171+
fixture.componentInstance.formControl.setValue('something');
172+
173+
expect(input.value).toBe('something');
174+
});
175+
146176
it('should add id', () => {
147177
let fixture = TestBed.createComponent(MdInputContainerTextTestController);
148178
fixture.detectChanges();
@@ -379,6 +409,13 @@ class MdInputContainerPlaceholderElementTestComponent {
379409
placeholder: string = 'Default Placeholder';
380410
}
381411

412+
@Component({
413+
template: `<md-input-container><input md-input [formControl]="formControl"></md-input-container>`
414+
})
415+
class MdInputContainerWithFormControl {
416+
formControl = new FormControl();
417+
}
418+
382419
@Component({
383420
template: `<md-input-container><input mdInput [placeholder]="placeholder"></md-input-container>`
384421
})
@@ -482,6 +519,16 @@ class MdInputContainerZeroTestController {
482519
value = 0;
483520
}
484521

522+
@Component({
523+
template: `
524+
<md-input-container>
525+
<input mdInput placeholder="Label" [value]="value">
526+
</md-input-container>`
527+
})
528+
class MdInputContainerWithValueBinding {
529+
value: string = 'Initial';
530+
}
531+
485532
@Component({
486533
template: `
487534
<md-input-container>

src/lib/input/input-container.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,9 @@ export class MdHint {
8383
'[required]': 'required',
8484
'(blur)': '_onBlur()',
8585
'(focus)': '_onFocus()',
86-
'(input)': '_onInput()',
8786
}
8887
})
89-
export class MdInputDirective implements AfterContentInit {
88+
export class MdInputDirective {
9089

9190
/** Variables used as cache for getters and setters. */
9291
private _type = 'text';
@@ -96,9 +95,6 @@ export class MdInputDirective implements AfterContentInit {
9695
private _id: string;
9796
private _cachedUid: string;
9897

99-
/** The element's value. */
100-
value: any;
101-
10298
/** Whether the element is focused or not. */
10399
focused = false;
104100

@@ -141,6 +137,10 @@ export class MdInputDirective implements AfterContentInit {
141137
}
142138
}
143139

140+
/** The input element's value. */
141+
get value() { return this._elementRef.nativeElement.value; }
142+
set value(value: string) { this._elementRef.nativeElement.value = value; }
143+
144144
/**
145145
* Emits an event when the placeholder changes so that the `md-input-container` can re-validate.
146146
*/
@@ -162,18 +162,9 @@ export class MdInputDirective implements AfterContentInit {
162162
constructor(private _elementRef: ElementRef,
163163
private _renderer: Renderer,
164164
@Optional() public _ngControl: NgControl) {
165+
165166
// Force setter to be called in case id was not specified.
166167
this.id = this.id;
167-
168-
if (this._ngControl && this._ngControl.valueChanges) {
169-
this._ngControl.valueChanges.subscribe((value) => {
170-
this.value = value;
171-
});
172-
}
173-
}
174-
175-
ngAfterContentInit() {
176-
this.value = this._elementRef.nativeElement.value;
177168
}
178169

179170
/** Focuses the input element. */
@@ -183,8 +174,6 @@ export class MdInputDirective implements AfterContentInit {
183174

184175
_onBlur() { this.focused = false; }
185176

186-
_onInput() { this.value = this._elementRef.nativeElement.value; }
187-
188177
/** Make sure the input is a supported type. */
189178
private _validateType() {
190179
if (MD_INPUT_INVALID_TYPES.indexOf(this._type) !== -1) {

0 commit comments

Comments
 (0)