Skip to content

Commit a022035

Browse files
karatinayuangao
authored andcommitted
fix(autocomplete): hide instead of close when options empty (#2997)
1 parent be19b2d commit a022035

File tree

5 files changed

+42
-27
lines changed

5 files changed

+42
-27
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
NgZone,
99
Optional,
1010
OnDestroy,
11-
QueryList,
1211
ViewContainerRef,
1312
} from '@angular/core';
1413
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
@@ -23,7 +22,6 @@ import {ENTER, UP_ARROW, DOWN_ARROW} from '../core/keyboard/keycodes';
2322
import {Dir} from '../core/rtl/dir';
2423
import {Subscription} from 'rxjs/Subscription';
2524
import {Subject} from 'rxjs/Subject';
26-
import 'rxjs/add/observable/of';
2725
import 'rxjs/add/observable/merge';
2826
import 'rxjs/add/operator/startWith';
2927
import 'rxjs/add/operator/switchMap';
@@ -258,33 +256,20 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
258256
* stream every time the option list changes.
259257
*/
260258
private _subscribeToClosingActions(): void {
261-
const initialOptions = this._getStableOptions();
262-
263259
// When the zone is stable initially, and when the option list changes...
264-
Observable.merge(initialOptions, this.autocomplete.options.changes)
260+
Observable.merge(this._zone.onStable.first(), this.autocomplete.options.changes)
265261
// create a new stream of panelClosingActions, replacing any previous streams
266262
// that were created, and flatten it so our stream only emits closing events...
267-
.switchMap(options => {
263+
.switchMap(() => {
268264
this._resetPanel();
269-
// If the options list is empty, emit close event immediately.
270-
// Otherwise, listen for panel closing actions...
271-
return options.length ? this.panelClosingActions : Observable.of(null);
265+
return this.panelClosingActions;
272266
})
273267
// when the first closing event occurs...
274268
.first()
275269
// set the value, close the panel, and complete.
276270
.subscribe(event => this._setValueAndClose(event));
277271
}
278272

279-
/**
280-
* Retrieves the option list once the zone stabilizes. It's important to wait until
281-
* stable so that change detection can run first and update the query list
282-
* with the options available under the current filter.
283-
*/
284-
private _getStableOptions(): Observable<QueryList<MdOption>> {
285-
return this._zone.onStable.first().map(() => this.autocomplete.options);
286-
}
287-
288273
/** Destroys the autocomplete suggestion panel. */
289274
private _destroyPanel(): void {
290275
if (this._overlayRef) {
@@ -364,6 +349,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
364349
private _resetPanel() {
365350
this._resetActiveItem();
366351
this._positionStrategy.recalculateLastPosition();
352+
this.autocomplete._setVisibility();
367353
}
368354

369355
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="mat-autocomplete-panel" role="listbox" [id]="id" [ngClass]="_getPositionClass()" #panel>
2+
<div class="mat-autocomplete-panel" role="listbox" [id]="id" [ngClass]="_getClassList()" #panel>
33
<ng-content></ng-content>
44
</div>
55
</template>

src/lib/autocomplete/autocomplete.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ $mat-autocomplete-panel-above-offset: -24px !default;
1414

1515
.mat-autocomplete-panel {
1616
@include mat-menu-base();
17+
visibility: hidden;
1718

1819
max-height: $mat-autocomplete-panel-max-height;
1920
position: relative;
@@ -25,4 +26,12 @@ $mat-autocomplete-panel-above-offset: -24px !default;
2526
&.mat-autocomplete-panel-above {
2627
top: $mat-autocomplete-panel-above-offset;
2728
}
29+
30+
&.mat-autocomplete-visible {
31+
visibility: visible;
32+
}
33+
34+
&.mat-autocomplete-hidden {
35+
visibility: hidden;
36+
}
2837
}

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,20 +165,30 @@ describe('MdAutocomplete', () => {
165165
.toEqual('', `Expected closing programmatically to close the panel.`);
166166
});
167167

168-
it('should close the panel when the options list is empty', async(() => {
168+
it('should hide the panel when the options list is empty', async(() => {
169169
dispatchEvent('focus', input);
170-
fixture.detectChanges();
171170

172171
fixture.whenStable().then(() => {
172+
fixture.detectChanges();
173+
174+
const panel =
175+
overlayContainerElement.querySelector('.mat-autocomplete-panel') as HTMLElement;
176+
expect(panel.classList)
177+
.toContain('mat-autocomplete-visible', `Expected panel to start out visible.`);
178+
173179
// Filter down the option list such that no options match the value
174180
input.value = 'af';
175181
dispatchEvent('input', input);
176182
fixture.detectChanges();
177183

178-
expect(fixture.componentInstance.trigger.panelOpen)
179-
.toBe(false, `Expected panel to close when options list is empty.`);
180-
expect(overlayContainerElement.textContent)
181-
.toEqual('', `Expected panel to close when options list is empty.`);
184+
fixture.whenStable().then(() => {
185+
fixture.detectChanges();
186+
187+
expect(fixture.componentInstance.trigger.panelOpen)
188+
.toBe(true, `Expected panel to stay open when options list is empty.`);
189+
expect(panel.classList)
190+
.toContain('mat-autocomplete-hidden', `Expected panel to hide itself when empty.`);
191+
});
182192
});
183193
}));
184194

src/lib/autocomplete/autocomplete.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ export class MdAutocomplete {
3434
/** Whether the autocomplete panel displays above or below its trigger. */
3535
positionY: AutocompletePositionY = 'below';
3636

37+
/** Whether the autocomplete panel should be visible, depending on option length. */
38+
showPanel = false;
39+
3740
@ViewChild(TemplateRef) template: TemplateRef<any>;
3841
@ViewChild('panel') panel: ElementRef;
3942
@ContentChildren(MdOption) options: QueryList<MdOption>;
@@ -54,11 +57,18 @@ export class MdAutocomplete {
5457
}
5558
}
5659

60+
/** Panel should hide itself when the option list is empty. */
61+
_setVisibility() {
62+
Promise.resolve().then(() => this.showPanel = !!this.options.length);
63+
}
64+
5765
/** Sets a class on the panel based on its position (used to set y-offset). */
58-
_getPositionClass() {
66+
_getClassList() {
5967
return {
6068
'mat-autocomplete-panel-below': this.positionY === 'below',
61-
'mat-autocomplete-panel-above': this.positionY === 'above'
69+
'mat-autocomplete-panel-above': this.positionY === 'above',
70+
'mat-autocomplete-visible': this.showPanel,
71+
'mat-autocomplete-hidden': !this.showPanel
6272
};
6373
}
6474

0 commit comments

Comments
 (0)