Skip to content

Commit 3a11927

Browse files
devversionkara
authored andcommitted
fix(input): properly determine input value (#2455)
Fixes #2441. Fixes #2363
1 parent b4b4224 commit 3a11927

File tree

2 files changed

+95
-18
lines changed

2 files changed

+95
-18
lines changed

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

Lines changed: 79 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,9 @@ describe('MdInputContainer', function () {
4343
MdInputContainerWithDisabled,
4444
MdInputContainerWithRequired,
4545
MdInputContainerWithType,
46+
MdInputContainerWithValueBinding,
47+
MdInputContainerWithFormControl,
48+
MdInputContainerWithStaticPlaceholder,
4649
MdInputContainerMissingMdInputTestController
4750
],
4851
});
@@ -130,6 +133,40 @@ describe('MdInputContainer', function () {
130133
expect(el.classList.contains('md-empty')).toBe(false, 'should not be empty');
131134
}));
132135

136+
it('should update the placeholder when input entered', async(() => {
137+
let fixture = TestBed.createComponent(MdInputContainerWithStaticPlaceholder);
138+
fixture.detectChanges();
139+
140+
let inputEl = fixture.debugElement.query(By.css('input'));
141+
let labelEl = fixture.debugElement.query(By.css('label')).nativeElement;
142+
143+
expect(labelEl.classList).toContain('md-empty');
144+
expect(labelEl.classList).not.toContain('md-float');
145+
146+
// Update the value of the input.
147+
inputEl.nativeElement.value = 'Text';
148+
149+
// Fake behavior of the `(input)` event which should trigger a change detection.
150+
fixture.detectChanges();
151+
152+
expect(labelEl.classList).not.toContain('md-empty');
153+
expect(labelEl.classList).not.toContain('md-float');
154+
}));
155+
156+
it('should not be empty when the value set before view init', async(() => {
157+
let fixture = TestBed.createComponent(MdInputContainerWithValueBinding);
158+
fixture.detectChanges();
159+
160+
let placeholderEl = fixture.debugElement.query(By.css('.md-input-placeholder')).nativeElement;
161+
162+
expect(placeholderEl.classList).not.toContain('md-empty');
163+
164+
fixture.componentInstance.value = '';
165+
fixture.detectChanges();
166+
167+
expect(placeholderEl.classList).toContain('md-empty');
168+
}));
169+
133170
it('should not treat the number 0 as empty', async(() => {
134171
let fixture = TestBed.createComponent(MdInputContainerZeroTestController);
135172
fixture.detectChanges();
@@ -143,6 +180,20 @@ describe('MdInputContainer', function () {
143180
});
144181
}));
145182

183+
it('should update the value when using FormControl.setValue', () => {
184+
let fixture = TestBed.createComponent(MdInputContainerWithFormControl);
185+
fixture.detectChanges();
186+
187+
let input = fixture.debugElement.query(By.directive(MdInputDirective))
188+
.injector.get(MdInputDirective) as MdInputDirective;
189+
190+
expect(input.value).toBeFalsy();
191+
192+
fixture.componentInstance.formControl.setValue('something');
193+
194+
expect(input.value).toBe('something');
195+
});
196+
146197
it('should add id', () => {
147198
let fixture = TestBed.createComponent(MdInputContainerTextTestController);
148199
fixture.detectChanges();
@@ -379,6 +430,13 @@ class MdInputContainerPlaceholderElementTestComponent {
379430
placeholder: string = 'Default Placeholder';
380431
}
381432

433+
@Component({
434+
template: `<md-input-container><input md-input [formControl]="formControl"></md-input-container>`
435+
})
436+
class MdInputContainerWithFormControl {
437+
formControl = new FormControl();
438+
}
439+
382440
@Component({
383441
template: `<md-input-container><input mdInput [placeholder]="placeholder"></md-input-container>`
384442
})
@@ -482,6 +540,25 @@ class MdInputContainerZeroTestController {
482540
value = 0;
483541
}
484542

543+
@Component({
544+
template: `
545+
<md-input-container>
546+
<input mdInput placeholder="Label" [value]="value">
547+
</md-input-container>`
548+
})
549+
class MdInputContainerWithValueBinding {
550+
value: string = 'Initial';
551+
}
552+
553+
@Component({
554+
template: `
555+
<md-input-container [floatingPlaceholder]="false">
556+
<input md-input placeholder="Label">
557+
</md-input-container>
558+
`
559+
})
560+
class MdInputContainerWithStaticPlaceholder {}
561+
485562
@Component({
486563
template: `
487564
<md-input-container>

src/lib/input/input-container.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ export class MdHint {
8383
'[required]': 'required',
8484
'(blur)': '_onBlur()',
8585
'(focus)': '_onFocus()',
86-
'(input)': '_onInput()',
86+
'(input)': '_onInput()'
8787
}
8888
})
89-
export class MdInputDirective implements AfterContentInit {
89+
export class MdInputDirective {
9090

9191
/** Variables used as cache for getters and setters. */
9292
private _type = 'text';
@@ -96,9 +96,6 @@ export class MdInputDirective implements AfterContentInit {
9696
private _id: string;
9797
private _cachedUid: string;
9898

99-
/** The element's value. */
100-
value: any;
101-
10299
/** Whether the element is focused or not. */
103100
focused = false;
104101

@@ -141,6 +138,10 @@ export class MdInputDirective implements AfterContentInit {
141138
}
142139
}
143140

141+
/** The input element's value. */
142+
get value() { return this._elementRef.nativeElement.value; }
143+
set value(value: string) { this._elementRef.nativeElement.value = value; }
144+
144145
/**
145146
* Emits an event when the placeholder changes so that the `md-input-container` can re-validate.
146147
*/
@@ -162,18 +163,9 @@ export class MdInputDirective implements AfterContentInit {
162163
constructor(private _elementRef: ElementRef,
163164
private _renderer: Renderer,
164165
@Optional() public _ngControl: NgControl) {
166+
165167
// Force setter to be called in case id was not specified.
166168
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;
177169
}
178170

179171
/** Focuses the input element. */
@@ -183,7 +175,15 @@ export class MdInputDirective implements AfterContentInit {
183175

184176
_onBlur() { this.focused = false; }
185177

186-
_onInput() { this.value = this._elementRef.nativeElement.value; }
178+
_onInput() {
179+
// This is a noop function and is used to let Angular know whenever the value changes.
180+
// Angular will run a new change detection each time the `input` event has been dispatched.
181+
// It's necessary that Angular recognizes the value change, because when floatingLabel
182+
// is set to false and Angular forms aren't used, the placeholder won't recognize the
183+
// value changes and will not disappear.
184+
// Listening to the input event wouldn't be necessary when the input is using the
185+
// FormsModule or ReactiveFormsModule, because Angular forms also listens to input events.
186+
}
187187

188188
/** Make sure the input is a supported type. */
189189
private _validateType() {

0 commit comments

Comments
 (0)