Skip to content

Commit

Permalink
fix(input): properly determine input value (#2455)
Browse files Browse the repository at this point in the history
Fixes #2441. Fixes #2363
  • Loading branch information
devversion authored and kara committed Jan 4, 2017
1 parent b4b4224 commit 3a11927
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 18 deletions.
81 changes: 79 additions & 2 deletions src/lib/input/input-container.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {async, TestBed, inject} from '@angular/core/testing';
import {Component} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
import {By} from '@angular/platform-browser';
import {MdInputModule} from './input';
import {MdInputContainer} from './input-container';
import {MdInputContainer, MdInputDirective} from './input-container';
import {Platform} from '../core/platform/platform';
import {PlatformModule} from '../core/platform/index';
import {
Expand Down Expand Up @@ -43,6 +43,9 @@ describe('MdInputContainer', function () {
MdInputContainerWithDisabled,
MdInputContainerWithRequired,
MdInputContainerWithType,
MdInputContainerWithValueBinding,
MdInputContainerWithFormControl,
MdInputContainerWithStaticPlaceholder,
MdInputContainerMissingMdInputTestController
],
});
Expand Down Expand Up @@ -130,6 +133,40 @@ describe('MdInputContainer', function () {
expect(el.classList.contains('md-empty')).toBe(false, 'should not be empty');
}));

it('should update the placeholder when input entered', async(() => {
let fixture = TestBed.createComponent(MdInputContainerWithStaticPlaceholder);
fixture.detectChanges();

let inputEl = fixture.debugElement.query(By.css('input'));
let labelEl = fixture.debugElement.query(By.css('label')).nativeElement;

expect(labelEl.classList).toContain('md-empty');
expect(labelEl.classList).not.toContain('md-float');

// Update the value of the input.
inputEl.nativeElement.value = 'Text';

// Fake behavior of the `(input)` event which should trigger a change detection.
fixture.detectChanges();

expect(labelEl.classList).not.toContain('md-empty');
expect(labelEl.classList).not.toContain('md-float');
}));

it('should not be empty when the value set before view init', async(() => {
let fixture = TestBed.createComponent(MdInputContainerWithValueBinding);
fixture.detectChanges();

let placeholderEl = fixture.debugElement.query(By.css('.md-input-placeholder')).nativeElement;

expect(placeholderEl.classList).not.toContain('md-empty');

fixture.componentInstance.value = '';
fixture.detectChanges();

expect(placeholderEl.classList).toContain('md-empty');
}));

it('should not treat the number 0 as empty', async(() => {
let fixture = TestBed.createComponent(MdInputContainerZeroTestController);
fixture.detectChanges();
Expand All @@ -143,6 +180,20 @@ describe('MdInputContainer', function () {
});
}));

it('should update the value when using FormControl.setValue', () => {
let fixture = TestBed.createComponent(MdInputContainerWithFormControl);
fixture.detectChanges();

let input = fixture.debugElement.query(By.directive(MdInputDirective))
.injector.get(MdInputDirective) as MdInputDirective;

expect(input.value).toBeFalsy();

fixture.componentInstance.formControl.setValue('something');

expect(input.value).toBe('something');
});

it('should add id', () => {
let fixture = TestBed.createComponent(MdInputContainerTextTestController);
fixture.detectChanges();
Expand Down Expand Up @@ -379,6 +430,13 @@ class MdInputContainerPlaceholderElementTestComponent {
placeholder: string = 'Default Placeholder';
}

@Component({
template: `<md-input-container><input md-input [formControl]="formControl"></md-input-container>`
})
class MdInputContainerWithFormControl {
formControl = new FormControl();
}

@Component({
template: `<md-input-container><input mdInput [placeholder]="placeholder"></md-input-container>`
})
Expand Down Expand Up @@ -482,6 +540,25 @@ class MdInputContainerZeroTestController {
value = 0;
}

@Component({
template: `
<md-input-container>
<input mdInput placeholder="Label" [value]="value">
</md-input-container>`
})
class MdInputContainerWithValueBinding {
value: string = 'Initial';
}

@Component({
template: `
<md-input-container [floatingPlaceholder]="false">
<input md-input placeholder="Label">
</md-input-container>
`
})
class MdInputContainerWithStaticPlaceholder {}

@Component({
template: `
<md-input-container>
Expand Down
32 changes: 16 additions & 16 deletions src/lib/input/input-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ export class MdHint {
'[required]': 'required',
'(blur)': '_onBlur()',
'(focus)': '_onFocus()',
'(input)': '_onInput()',
'(input)': '_onInput()'
}
})
export class MdInputDirective implements AfterContentInit {
export class MdInputDirective {

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

/** The element's value. */
value: any;

/** Whether the element is focused or not. */
focused = false;

Expand Down Expand Up @@ -141,6 +138,10 @@ export class MdInputDirective implements AfterContentInit {
}
}

/** The input element's value. */
get value() { return this._elementRef.nativeElement.value; }
set value(value: string) { this._elementRef.nativeElement.value = value; }

/**
* Emits an event when the placeholder changes so that the `md-input-container` can re-validate.
*/
Expand All @@ -162,18 +163,9 @@ export class MdInputDirective implements AfterContentInit {
constructor(private _elementRef: ElementRef,
private _renderer: Renderer,
@Optional() public _ngControl: NgControl) {

// Force setter to be called in case id was not specified.
this.id = this.id;

if (this._ngControl && this._ngControl.valueChanges) {
this._ngControl.valueChanges.subscribe((value) => {
this.value = value;
});
}
}

ngAfterContentInit() {
this.value = this._elementRef.nativeElement.value;
}

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

_onBlur() { this.focused = false; }

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

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

0 comments on commit 3a11927

Please sign in to comment.