diff --git a/src/lib/input/input-container.spec.ts b/src/lib/input/input-container.spec.ts
index 335acfaf3c40..6dba14d56f57 100644
--- a/src/lib/input/input-container.spec.ts
+++ b/src/lib/input/input-container.spec.ts
@@ -41,6 +41,8 @@ describe('MdInputContainer', function () {
MdInputContainerZeroTestController,
MdTextareaWithBindings,
MdInputContainerWithDisabled,
+ MdInputContainerWithRequired,
+ MdInputContainerWithType,
MdInputContainerMissingMdInputTestController
],
});
@@ -236,16 +238,20 @@ describe('MdInputContainer', function () {
let fixture = TestBed.createComponent(MdInputContainerPlaceholderAttrTestComponent);
fixture.detectChanges();
- let el = fixture.debugElement.query(By.css('label'));
- expect(el).toBeNull();
+ let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
+
+ expect(fixture.debugElement.query(By.css('label'))).toBeNull();
+ expect(inputEl.placeholder).toBe('');
fixture.componentInstance.placeholder = 'Other placeholder';
fixture.detectChanges();
- el = fixture.debugElement.query(By.css('label'));
- expect(el).not.toBeNull();
- expect(el.nativeElement.textContent).toMatch('Other placeholder');
- expect(el.nativeElement.textContent).not.toMatch(/\*/g);
+ let labelEl = fixture.debugElement.query(By.css('label'));
+
+ expect(inputEl.placeholder).toBe('Other placeholder');
+ expect(labelEl).not.toBeNull();
+ expect(labelEl.nativeElement.textContent).toMatch('Other placeholder');
+ expect(labelEl.nativeElement.textContent).not.toMatch(/\*/g);
}));
it('supports placeholder element', async(() => {
@@ -274,18 +280,51 @@ describe('MdInputContainer', function () {
expect(el.nativeElement.textContent).toMatch(/hello\s+\*/g);
});
- it('supports the disabled attribute', async(() => {
+ it('supports the disabled attribute as binding', async(() => {
let fixture = TestBed.createComponent(MdInputContainerWithDisabled);
fixture.detectChanges();
let underlineEl = fixture.debugElement.query(By.css('.md-input-underline')).nativeElement;
+ let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
+
expect(underlineEl.classList.contains('md-disabled')).toBe(false, 'should not be disabled');
+ expect(inputEl.disabled).toBe(false);
fixture.componentInstance.disabled = true;
fixture.detectChanges();
+
+ expect(inputEl.disabled).toBe(true);
expect(underlineEl.classList.contains('md-disabled')).toBe(true, 'should be disabled');
}));
+ it('supports the required attribute as binding', async(() => {
+ let fixture = TestBed.createComponent(MdInputContainerWithRequired);
+ fixture.detectChanges();
+
+ let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
+
+ expect(inputEl.required).toBe(false);
+
+ fixture.componentInstance.required = true;
+ fixture.detectChanges();
+
+ expect(inputEl.required).toBe(true);
+ }));
+
+ it('supports the type attribute as binding', async(() => {
+ let fixture = TestBed.createComponent(MdInputContainerWithType);
+ fixture.detectChanges();
+
+ let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
+
+ expect(inputEl.type).toBe('text');
+
+ fixture.componentInstance.type = 'password';
+ fixture.detectChanges();
+
+ expect(inputEl.type).toBe('password');
+ }));
+
it('supports textarea', () => {
let fixture = TestBed.createComponent(MdTextareaWithBindings);
fixture.detectChanges();
@@ -310,6 +349,20 @@ class MdInputContainerWithDisabled {
disabled: boolean;
}
+@Component({
+ template: ``
+})
+class MdInputContainerWithRequired {
+ required: boolean;
+}
+
+@Component({
+ template: ``
+})
+class MdInputContainerWithType {
+ type: string;
+}
+
@Component({
template: ``
})
diff --git a/src/lib/input/input-container.ts b/src/lib/input/input-container.ts
index 37d18f2ff0db..8f4bd965f1ab 100644
--- a/src/lib/input/input-container.ts
+++ b/src/lib/input/input-container.ts
@@ -71,41 +71,56 @@ export class MdHint {
selector: 'input[md-input], textarea[md-input], input[mat-input], textarea[mat-input]',
host: {
'class': 'md-input-element',
+ // Native input properties that are overwritten by Angular inputs need to be synced with
+ // the native input element. Otherwise property bindings for those don't work.
'[id]': 'id',
+ '[placeholder]': 'placeholder',
+ '[disabled]': 'disabled',
+ '[required]': 'required',
'(blur)': '_onBlur()',
'(focus)': '_onFocus()',
'(input)': '_onInput()',
}
})
export class MdInputDirective implements AfterContentInit {
+
+ /** Variables used as cache for getters and setters. */
+ private _type = 'text';
+ private _placeholder: string = '';
+ private _disabled = false;
+ private _required = false;
+ private _id: string;
+ private _cachedUid: string;
+
+ /** The element's value. */
+ value: any;
+
+ /** Whether the element is focused or not. */
+ focused = false;
+
/** Whether the element is disabled. */
@Input()
get disabled() { return this._disabled; }
set disabled(value: any) { this._disabled = coerceBooleanProperty(value); }
- private _disabled = false;
/** Unique id of the element. */
@Input()
get id() { return this._id; };
- set id(value: string) { this._id = value || this._uid; }
- private _id: string;
+ set id(value: string) {this._id = value || this._uid; }
/** Placeholder attribute of the element. */
@Input()
get placeholder() { return this._placeholder; }
set placeholder(value: string) {
- if (this._placeholder != value) {
+ if (this._placeholder !== value) {
this._placeholder = value;
this._placeholderChange.emit(this._placeholder);
}
}
- private _placeholder = '';
-
/** Whether the element is required. */
@Input()
get required() { return this._required; }
set required(value: any) { this._required = coerceBooleanProperty(value); }
- private _required = false;
/** Input type of the element. */
@Input()
@@ -113,11 +128,14 @@ export class MdInputDirective implements AfterContentInit {
set type(value: string) {
this._type = value || 'text';
this._validateType();
- }
- private _type = 'text';
- /** The element's value. */
- value: any;
+ // When using Angular inputs, developers are no longer able to set the properties on the native
+ // input element. To ensure that bindings for `type` work, we need to sync the setter
+ // with the native property. Textarea elements don't support the type property or attribute.
+ if (!this._isTextarea()) {
+ this._renderer.setElementProperty(this._elementRef.nativeElement, 'type', this._type);
+ }
+ }
/**
* Emits an event when the placeholder changes so that the `md-input-container` can re-validate.
@@ -126,10 +144,7 @@ export class MdInputDirective implements AfterContentInit {
get empty() { return (this.value == null || this.value === '') && !this._isNeverEmpty(); }
- focused = false;
-
private get _uid() { return this._cachedUid = this._cachedUid || `md-input-${nextUniqueId++}`; }
- private _cachedUid: string;
private _neverEmptyInputTypes = [
'date',
@@ -168,12 +183,23 @@ export class MdInputDirective implements AfterContentInit {
/** Make sure the input is a supported type. */
private _validateType() {
- if (MD_INPUT_INVALID_TYPES.indexOf(this._type) != -1) {
+ if (MD_INPUT_INVALID_TYPES.indexOf(this._type) !== -1) {
throw new MdInputContainerUnsupportedTypeError(this._type);
}
}
- private _isNeverEmpty() { return this._neverEmptyInputTypes.indexOf(this._type) != -1; }
+ private _isNeverEmpty() { return this._neverEmptyInputTypes.indexOf(this._type) !== -1; }
+
+ /** Determines if the component host is a textarea. If not recognizable it returns false. */
+ private _isTextarea() {
+ let nativeElement = this._elementRef.nativeElement;
+ return nativeElement ? nativeElement.nodeName === 'textarea' : 'input';
+ }
+
+ /** Sets a property on the native input element. */
+ private _setElementProperty(property: string, value: any) {
+ this._renderer.setElementProperty(this._elementRef.nativeElement, property, value);
+ }
}