Skip to content

fix(input): properly determine input value #2455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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