Skip to content

Commit d1a0f7a

Browse files
committed
fix(autocomplete): close panel when options list is empty
1 parent 2f70a0e commit d1a0f7a

File tree

2 files changed

+289
-267
lines changed

2 files changed

+289
-267
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

+24-11
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {
44
ElementRef,
55
forwardRef,
66
Input,
7+
NgZone,
78
Optional,
89
OnDestroy,
10+
QueryList,
911
ViewContainerRef,
1012
} from '@angular/core';
1113
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
@@ -18,6 +20,7 @@ import {MdOptionSelectEvent, MdOption} from '../core/option/option';
1820
import {ActiveDescendantKeyManager} from '../core/a11y/activedescendant-key-manager';
1921
import {ENTER, UP_ARROW, DOWN_ARROW} from '../core/keyboard/keycodes';
2022
import {Subscription} from 'rxjs/Subscription';
23+
import 'rxjs/add/observable/of';
2124
import 'rxjs/add/observable/merge';
2225
import {Dir} from '../core/rtl/dir';
2326
import 'rxjs/add/operator/startWith';
@@ -57,7 +60,7 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
5760
'[attr.aria-owns]': 'autocomplete?.id',
5861
'(focus)': 'openPanel()',
5962
'(blur)': '_onTouched()',
60-
'(input)': '_onChange($event.target.value)',
63+
'(input)': '_handleInput($event.target.value)',
6164
'(keydown)': '_handleKeydown($event)',
6265
},
6366
providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR]
@@ -85,7 +88,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
8588

8689
constructor(private _element: ElementRef, private _overlay: Overlay,
8790
private _viewContainerRef: ViewContainerRef,
88-
@Optional() private _dir: Dir) {}
91+
@Optional() private _dir: Dir, private _zone: NgZone) {}
8992

9093
ngAfterContentInit() {
9194
this._keyManager = new ActiveDescendantKeyManager(this.autocomplete.options).withWrap();
@@ -131,7 +134,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
131134
* A stream of actions that should close the autocomplete panel, including
132135
* when an option is selected and when the backdrop is clicked.
133136
*/
134-
get panelClosingActions(): Observable<any> {
137+
get panelClosingActions(): Observable<MdOptionSelectEvent | null> {
135138
return Observable.merge(
136139
...this.optionSelections,
137140
this._overlayRef.backdropClick(),
@@ -140,7 +143,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
140143
}
141144

142145
/** Stream of autocomplete option selections. */
143-
get optionSelections(): Observable<any>[] {
146+
get optionSelections(): Observable<MdOptionSelectEvent>[] {
144147
return this.autocomplete.options.map(option => option.onSelect);
145148
}
146149

@@ -149,6 +152,11 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
149152
return this._keyManager.activeItem as MdOption;
150153
}
151154

155+
/** The initial list of autocomplete options, as soon as the zone has stabilized */
156+
get initialOptionListg(): Observable<QueryList<MdOption>> {
157+
return this._zone.onStable.first().map(() => this.autocomplete.options);
158+
}
159+
152160
/**
153161
* Sets the autocomplete's value. Part of the ControlValueAccessor interface
154162
* required to integrate with Angular's core forms API.
@@ -185,14 +193,19 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
185193
if (this.activeOption && event.keyCode === ENTER) {
186194
this.activeOption._selectViaInteraction();
187195
} else {
188-
this.openPanel();
189196
this._keyManager.onKeydown(event);
190197
if (event.keyCode === UP_ARROW || event.keyCode === DOWN_ARROW) {
198+
this.openPanel();
191199
this._scrollToOption();
192200
}
193201
}
194202
}
195203

204+
_handleInput(value: string): void {
205+
this._onChange(value);
206+
this.openPanel();
207+
}
208+
196209
/**
197210
* Given that we are not actually focusing active options, we must manually adjust scroll
198211
* to reveal options below the fold. First, we find the offset of the option from the top
@@ -211,15 +224,15 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
211224
* stream every time the option list changes.
212225
*/
213226
private _subscribeToClosingActions(): void {
214-
// Every time the option list changes...
215-
this.autocomplete.options.changes
216-
// and also at initialization, before there are any option changes...
217-
.startWith(null)
227+
// When the zone is stable initially, and when the option list changes...
228+
Observable.merge(this.initialOptionList, this.autocomplete.options.changes)
218229
// create a new stream of panelClosingActions, replacing any previous streams
219230
// that were created, and flatten it so our stream only emits closing events...
220-
.switchMap(() => {
231+
.switchMap(options => {
221232
this._resetPanel();
222-
return this.panelClosingActions;
233+
// If the options list is empty, emit close event immediately.
234+
// Otherwise, listen for panel closing actions...
235+
return options.length ? this.panelClosingActions : Observable.of(null);
223236
})
224237
// when the first closing event occurs...
225238
.first()

0 commit comments

Comments
 (0)