Skip to content

Commit b0f4196

Browse files
committed
fix(input): continue checking for input child after initialization
Currently we only check if an `md-input-container` has a child upon initialization, however the child might be removed via `ngIf` at a later point. If that happens, we eventually run into some check that assumed that we have an input child, however it would be better if we threw the appropriate error. These changes add extra validation that ensure that the input continues to be in place after init. Fixes #4551.
1 parent 05dbb90 commit b0f4196

File tree

2 files changed

+56
-19
lines changed

2 files changed

+56
-19
lines changed

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ describe('MdInputContainer', function () {
6565
MdInputContainerWithValueBinding,
6666
MdInputContainerZeroTestController,
6767
MdTextareaWithBindings,
68+
MdInputContainerWithNgIf,
6869
],
6970
});
7071

@@ -266,6 +267,18 @@ describe('MdInputContainer', function () {
266267
wrappedErrorMessage(getMdInputContainerMissingMdInputError()));
267268
});
268269

270+
it('validates that mdInput child is present after initialization', async(() => {
271+
let fixture = TestBed.createComponent(MdInputContainerWithNgIf);
272+
273+
expect(() => fixture.detectChanges()).not.toThrowError(
274+
wrappedErrorMessage(getMdInputContainerMissingMdInputError());
275+
276+
fixture.componentInstance.renderInput = false;
277+
278+
expect(() => fixture.detectChanges()).toThrowError(
279+
wrappedErrorMessage(getMdInputContainerMissingMdInputError());
280+
}));
281+
269282
it('validates the type', () => {
270283
let fixture = TestBed.createComponent(MdInputContainerInvalidTypeTestController);
271284

@@ -997,3 +1010,14 @@ class MdInputContainerWithFormGroupErrorMessages {
9971010
`
9981011
})
9991012
class MdInputContainerWithPrefixAndSuffix {}
1013+
1014+
@Component({
1015+
template: `
1016+
<md-input-container>
1017+
<input mdInput *ngIf="renderInput">
1018+
</md-input-container>
1019+
`
1020+
})
1021+
class MdInputContainerWithNgIf {
1022+
renderInput = true;
1023+
}

src/lib/input/input-container.ts

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
AfterContentInit,
3+
AfterContentChecked,
34
AfterViewInit,
45
ChangeDetectorRef,
56
Component,
@@ -286,7 +287,7 @@ export class MdInputDirective {
286287
},
287288
encapsulation: ViewEncapsulation.None,
288289
})
289-
export class MdInputContainer implements AfterViewInit, AfterContentInit {
290+
export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterContentChecked {
290291
/** Alignment of the input container's content. */
291292
@Input() align: 'start' | 'end' = 'start';
292293

@@ -356,10 +357,7 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit {
356357
@Optional() private _parentFormGroup: FormGroupDirective) { }
357358

358359
ngAfterContentInit() {
359-
if (!this._mdInputChild) {
360-
throw getMdInputContainerMissingMdInputError();
361-
}
362-
360+
this._validateInputChild();
363361
this._processHints();
364362
this._validatePlaceholders();
365363

@@ -368,6 +366,10 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit {
368366
this._mdInputChild._placeholderChange.subscribe(() => this._validatePlaceholders());
369367
}
370368

369+
ngAfterContentChecked() {
370+
this._validateInputChild();
371+
}
372+
371373
ngAfterViewInit() {
372374
// Avoid animations on load.
373375
this._subscriptAnimationState = 'enter';
@@ -449,22 +451,33 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit {
449451
* of the currently-specified hints, as well as a generated id for the hint label.
450452
*/
451453
private _syncAriaDescribedby() {
452-
let ids: string[] = [];
453-
let startHint = this._hintChildren ?
454-
this._hintChildren.find(hint => hint.align === 'start') : null;
455-
let endHint = this._hintChildren ?
456-
this._hintChildren.find(hint => hint.align === 'end') : null;
457-
458-
if (startHint) {
459-
ids.push(startHint.id);
460-
} else if (this._hintLabel) {
461-
ids.push(this._hintLabelId);
454+
if (this._mdInputChild) {
455+
let ids: string[] = [];
456+
let startHint = this._hintChildren ?
457+
this._hintChildren.find(hint => hint.align === 'start') : null;
458+
let endHint = this._hintChildren ?
459+
this._hintChildren.find(hint => hint.align === 'end') : null;
460+
461+
if (startHint) {
462+
ids.push(startHint.id);
463+
} else if (this._hintLabel) {
464+
ids.push(this._hintLabelId);
465+
}
466+
467+
if (endHint) {
468+
ids.push(endHint.id);
469+
}
470+
471+
this._mdInputChild.ariaDescribedby = ids.join(' ');
462472
}
473+
}
463474

464-
if (endHint) {
465-
ids.push(endHint.id);
475+
/**
476+
* Throws an error if the container's input child was removed.
477+
*/
478+
private _validateInputChild() {
479+
if (!this._mdInputChild) {
480+
throw getMdInputContainerMissingMdInputError();
466481
}
467-
468-
this._mdInputChild.ariaDescribedby = ids.join(' ');
469482
}
470483
}

0 commit comments

Comments
 (0)