diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 120b014a57e3..b6e841046605 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -8,7 +8,6 @@ import { NgZone, Optional, OnDestroy, - QueryList, ViewContainerRef, } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; @@ -23,7 +22,6 @@ import {ENTER, UP_ARROW, DOWN_ARROW} from '../core/keyboard/keycodes'; import {Dir} from '../core/rtl/dir'; import {Subscription} from 'rxjs/Subscription'; import {Subject} from 'rxjs/Subject'; -import 'rxjs/add/observable/of'; import 'rxjs/add/observable/merge'; import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/switchMap'; @@ -258,17 +256,13 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce * stream every time the option list changes. */ private _subscribeToClosingActions(): void { - const initialOptions = this._getStableOptions(); - // When the zone is stable initially, and when the option list changes... - Observable.merge(initialOptions, this.autocomplete.options.changes) + Observable.merge(this._zone.onStable.first(), this.autocomplete.options.changes) // create a new stream of panelClosingActions, replacing any previous streams // that were created, and flatten it so our stream only emits closing events... - .switchMap(options => { + .switchMap(() => { this._resetPanel(); - // If the options list is empty, emit close event immediately. - // Otherwise, listen for panel closing actions... - return options.length ? this.panelClosingActions : Observable.of(null); + return this.panelClosingActions; }) // when the first closing event occurs... .first() @@ -276,15 +270,6 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce .subscribe(event => this._setValueAndClose(event)); } - /** - * Retrieves the option list once the zone stabilizes. It's important to wait until - * stable so that change detection can run first and update the query list - * with the options available under the current filter. - */ - private _getStableOptions(): Observable> { - return this._zone.onStable.first().map(() => this.autocomplete.options); - } - /** Destroys the autocomplete suggestion panel. */ private _destroyPanel(): void { if (this._overlayRef) { @@ -364,6 +349,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce private _resetPanel() { this._resetActiveItem(); this._positionStrategy.recalculateLastPosition(); + this.autocomplete._setVisibility(); } } diff --git a/src/lib/autocomplete/autocomplete.html b/src/lib/autocomplete/autocomplete.html index 54c2ad9117cd..cd94ceeb7340 100644 --- a/src/lib/autocomplete/autocomplete.html +++ b/src/lib/autocomplete/autocomplete.html @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/src/lib/autocomplete/autocomplete.scss b/src/lib/autocomplete/autocomplete.scss index 69ef00d8b2f5..80fbcc56d6d2 100644 --- a/src/lib/autocomplete/autocomplete.scss +++ b/src/lib/autocomplete/autocomplete.scss @@ -14,6 +14,7 @@ $mat-autocomplete-panel-above-offset: -24px !default; .mat-autocomplete-panel { @include mat-menu-base(); + visibility: hidden; max-height: $mat-autocomplete-panel-max-height; position: relative; @@ -25,4 +26,12 @@ $mat-autocomplete-panel-above-offset: -24px !default; &.mat-autocomplete-panel-above { top: $mat-autocomplete-panel-above-offset; } + + &.mat-autocomplete-visible { + visibility: visible; + } + + &.mat-autocomplete-hidden { + visibility: hidden; + } } \ No newline at end of file diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 1d12c4611ca2..e533ae34704c 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -165,20 +165,30 @@ describe('MdAutocomplete', () => { .toEqual('', `Expected closing programmatically to close the panel.`); }); - it('should close the panel when the options list is empty', async(() => { + it('should hide the panel when the options list is empty', async(() => { dispatchEvent('focus', input); - fixture.detectChanges(); fixture.whenStable().then(() => { + fixture.detectChanges(); + + const panel = + overlayContainerElement.querySelector('.mat-autocomplete-panel') as HTMLElement; + expect(panel.classList) + .toContain('mat-autocomplete-visible', `Expected panel to start out visible.`); + // Filter down the option list such that no options match the value input.value = 'af'; dispatchEvent('input', input); fixture.detectChanges(); - expect(fixture.componentInstance.trigger.panelOpen) - .toBe(false, `Expected panel to close when options list is empty.`); - expect(overlayContainerElement.textContent) - .toEqual('', `Expected panel to close when options list is empty.`); + fixture.whenStable().then(() => { + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(true, `Expected panel to stay open when options list is empty.`); + expect(panel.classList) + .toContain('mat-autocomplete-hidden', `Expected panel to hide itself when empty.`); + }); }); })); diff --git a/src/lib/autocomplete/autocomplete.ts b/src/lib/autocomplete/autocomplete.ts index ff9af9688ffd..bbed800589cc 100644 --- a/src/lib/autocomplete/autocomplete.ts +++ b/src/lib/autocomplete/autocomplete.ts @@ -34,6 +34,9 @@ export class MdAutocomplete { /** Whether the autocomplete panel displays above or below its trigger. */ positionY: AutocompletePositionY = 'below'; + /** Whether the autocomplete panel should be visible, depending on option length. */ + showPanel = false; + @ViewChild(TemplateRef) template: TemplateRef; @ViewChild('panel') panel: ElementRef; @ContentChildren(MdOption) options: QueryList; @@ -54,11 +57,18 @@ export class MdAutocomplete { } } + /** Panel should hide itself when the option list is empty. */ + _setVisibility() { + Promise.resolve().then(() => this.showPanel = !!this.options.length); + } + /** Sets a class on the panel based on its position (used to set y-offset). */ - _getPositionClass() { + _getClassList() { return { 'mat-autocomplete-panel-below': this.positionY === 'below', - 'mat-autocomplete-panel-above': this.positionY === 'above' + 'mat-autocomplete-panel-above': this.positionY === 'above', + 'mat-autocomplete-visible': this.showPanel, + 'mat-autocomplete-hidden': !this.showPanel }; }