From 70b31f177a2dacfd08f47b2a8898fca3f7bb91f9 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 5 Jun 2017 23:44:25 +0200 Subject: [PATCH] fix(autocomplete): throw better error when autocomplete doesn't have a panel (#4851) Adds a more informative error if the user tries to open an autocomplete that doesn't exist or hasn't been defined yet by the `@ViewChild` query. Fixes #4844. --- src/lib/autocomplete/autocomplete-trigger.ts | 15 ++++++++- src/lib/autocomplete/autocomplete.spec.ts | 32 +++++++++++++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 5dd5c1f8fce..905c9aca1f8 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -51,6 +51,15 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = { multi: true }; +/** + * Creates an error to be thrown when attempting to use an autocomplete trigger without a panel. + */ +export function getMdAutocompleteMissingPanelError(): Error { + return new Error('Attempting to open an undefined instance of `md-autocomplete`. ' + + 'Make sure that the id passed to the `mdAutocomplete` is correct and that ' + + 'you\'re attempting to open it after the ngAfterContentInit hook.'); +} + @Directive({ selector: 'input[mdAutocomplete], input[matAutocomplete]', host: { @@ -124,6 +133,10 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { /** Opens the autocomplete suggestion panel. */ openPanel(): void { + if (!this.autocomplete) { + throw getMdAutocompleteMissingPanelError(); + } + if (!this._overlayRef) { this._createOverlay(); } else { @@ -177,7 +190,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { /** The currently active option, coerced to MdOption type. */ get activeOption(): MdOption { - if (this.autocomplete._keyManager) { + if (this.autocomplete && this.autocomplete._keyManager) { return this.autocomplete._keyManager.activeItem as MdOption; } } diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 0199c174678..17fdf917a32 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -10,15 +10,19 @@ import { } from '@angular/core'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {MdAutocompleteModule, MdAutocompleteTrigger} from './index'; +import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; +import { + MdAutocompleteModule, + MdAutocompleteTrigger, + MdAutocomplete, + getMdAutocompleteMissingPanelError, +} from './index'; import {OverlayContainer} from '../core/overlay/overlay-container'; import {MdInputModule} from '../input/index'; import {Dir, LayoutDirection} from '../core/rtl/dir'; -import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {Subscription} from 'rxjs/Subscription'; import {ENTER, DOWN_ARROW, SPACE, UP_ARROW, ESCAPE} from '../core/keyboard/keycodes'; import {MdOption} from '../core/option/option'; -import {MdAutocomplete} from './autocomplete'; import {MdInputContainer} from '../input/input-container'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; @@ -26,9 +30,9 @@ import {dispatchFakeEvent} from '../core/testing/dispatch-events'; import {createKeyboardEvent} from '../core/testing/event-objects'; import {typeInElement} from '../core/testing/type-in-element'; import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher'; - import 'rxjs/add/operator/map'; + describe('MdAutocomplete', () => { let overlayContainerElement: HTMLElement; let dir: LayoutDirection; @@ -50,7 +54,8 @@ describe('MdAutocomplete', () => { NgIfAutocomplete, AutocompleteWithNgModel, AutocompleteWithOnPushDelay, - AutocompleteWithNativeInput + AutocompleteWithNativeInput, + AutocompleteWithoutPanel ], providers: [ {provide: OverlayContainer, useFactory: () => { @@ -1156,6 +1161,15 @@ describe('MdAutocomplete', () => { expect(fixture.componentInstance.mdOptions.length).toBe(2); })); + it('should throw if the user attempts to open the panel too early', async(() => { + const fixture = TestBed.createComponent(AutocompleteWithoutPanel); + fixture.detectChanges(); + + expect(() => { + fixture.componentInstance.trigger.openPanel(); + }).toThrow(getMdAutocompleteMissingPanelError()); + })); + }); it('should have correct width when opened', () => { @@ -1422,3 +1436,11 @@ class AutocompleteWithNativeInput { }); } } + + +@Component({ + template: `` +}) +class AutocompleteWithoutPanel { + @ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger; +}