From 021b62dcd9564ca05f5eb680804fa6887996ab28 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 15 May 2017 20:32:57 +0200 Subject: [PATCH] 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. --- src/lib/input/input-container.spec.ts | 24 +++++++++++++ src/lib/input/input-container.ts | 51 +++++++++++++++++---------- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/lib/input/input-container.spec.ts b/src/lib/input/input-container.spec.ts index 7ce05fc5bc7c..584ea076fb65 100644 --- a/src/lib/input/input-container.spec.ts +++ b/src/lib/input/input-container.spec.ts @@ -65,6 +65,7 @@ describe('MdInputContainer', function () { MdInputContainerWithValueBinding, MdInputContainerZeroTestController, MdTextareaWithBindings, + MdInputContainerWithNgIf, ], }); @@ -266,6 +267,18 @@ describe('MdInputContainer', function () { wrappedErrorMessage(getMdInputContainerMissingMdInputError())); }); + it('validates that mdInput child is present after initialization', async(() => { + let fixture = TestBed.createComponent(MdInputContainerWithNgIf); + + expect(() => fixture.detectChanges()).not.toThrowError( + wrappedErrorMessage(getMdInputContainerMissingMdInputError())); + + fixture.componentInstance.renderInput = false; + + expect(() => fixture.detectChanges()).toThrowError( + wrappedErrorMessage(getMdInputContainerMissingMdInputError())); + })); + it('validates the type', () => { let fixture = TestBed.createComponent(MdInputContainerInvalidTypeTestController); @@ -997,3 +1010,14 @@ class MdInputContainerWithFormGroupErrorMessages { ` }) class MdInputContainerWithPrefixAndSuffix {} + +@Component({ + template: ` + + + + ` +}) +class MdInputContainerWithNgIf { + renderInput = true; +} diff --git a/src/lib/input/input-container.ts b/src/lib/input/input-container.ts index 964b203cf4d9..c7ffb4d47f5d 100644 --- a/src/lib/input/input-container.ts +++ b/src/lib/input/input-container.ts @@ -1,5 +1,6 @@ import { AfterContentInit, + AfterContentChecked, AfterViewInit, ChangeDetectorRef, Component, @@ -286,7 +287,7 @@ export class MdInputDirective { }, encapsulation: ViewEncapsulation.None, }) -export class MdInputContainer implements AfterViewInit, AfterContentInit { +export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterContentChecked { /** Alignment of the input container's content. */ @Input() align: 'start' | 'end' = 'start'; @@ -356,10 +357,7 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit { @Optional() private _parentFormGroup: FormGroupDirective) { } ngAfterContentInit() { - if (!this._mdInputChild) { - throw getMdInputContainerMissingMdInputError(); - } - + this._validateInputChild(); this._processHints(); this._validatePlaceholders(); @@ -368,6 +366,10 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit { this._mdInputChild._placeholderChange.subscribe(() => this._validatePlaceholders()); } + ngAfterContentChecked() { + this._validateInputChild(); + } + ngAfterViewInit() { // Avoid animations on load. this._subscriptAnimationState = 'enter'; @@ -449,22 +451,33 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit { * of the currently-specified hints, as well as a generated id for the hint label. */ private _syncAriaDescribedby() { - let ids: string[] = []; - let startHint = this._hintChildren ? - this._hintChildren.find(hint => hint.align === 'start') : null; - let endHint = this._hintChildren ? - this._hintChildren.find(hint => hint.align === 'end') : null; - - if (startHint) { - ids.push(startHint.id); - } else if (this._hintLabel) { - ids.push(this._hintLabelId); + if (this._mdInputChild) { + let ids: string[] = []; + let startHint = this._hintChildren ? + this._hintChildren.find(hint => hint.align === 'start') : null; + let endHint = this._hintChildren ? + this._hintChildren.find(hint => hint.align === 'end') : null; + + if (startHint) { + ids.push(startHint.id); + } else if (this._hintLabel) { + ids.push(this._hintLabelId); + } + + if (endHint) { + ids.push(endHint.id); + } + + this._mdInputChild.ariaDescribedby = ids.join(' '); } + } - if (endHint) { - ids.push(endHint.id); + /** + * Throws an error if the container's input child was removed. + */ + private _validateInputChild() { + if (!this._mdInputChild) { + throw getMdInputContainerMissingMdInputError(); } - - this._mdInputChild.ariaDescribedby = ids.join(' '); } }