@@ -4,8 +4,10 @@ import {
4
4
ElementRef ,
5
5
forwardRef ,
6
6
Input ,
7
+ NgZone ,
7
8
Optional ,
8
9
OnDestroy ,
10
+ QueryList ,
9
11
ViewContainerRef ,
10
12
} from '@angular/core' ;
11
13
import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ;
@@ -18,6 +20,7 @@ import {MdOptionSelectEvent, MdOption} from '../core/option/option';
18
20
import { ActiveDescendantKeyManager } from '../core/a11y/activedescendant-key-manager' ;
19
21
import { ENTER , UP_ARROW , DOWN_ARROW } from '../core/keyboard/keycodes' ;
20
22
import { Subscription } from 'rxjs/Subscription' ;
23
+ import 'rxjs/add/observable/of' ;
21
24
import 'rxjs/add/observable/merge' ;
22
25
import { Dir } from '../core/rtl/dir' ;
23
26
import 'rxjs/add/operator/startWith' ;
@@ -57,7 +60,7 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
57
60
'[attr.aria-owns]' : 'autocomplete?.id' ,
58
61
'(focus)' : 'openPanel()' ,
59
62
'(blur)' : '_onTouched()' ,
60
- '(input)' : '_onChange ($event.target.value)' ,
63
+ '(input)' : '_handleInput ($event.target.value)' ,
61
64
'(keydown)' : '_handleKeydown($event)' ,
62
65
} ,
63
66
providers : [ MD_AUTOCOMPLETE_VALUE_ACCESSOR ]
@@ -85,7 +88,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
85
88
86
89
constructor ( private _element : ElementRef , private _overlay : Overlay ,
87
90
private _viewContainerRef : ViewContainerRef ,
88
- @Optional ( ) private _dir : Dir ) { }
91
+ @Optional ( ) private _dir : Dir , private _zone : NgZone ) { }
89
92
90
93
ngAfterContentInit ( ) {
91
94
this . _keyManager = new ActiveDescendantKeyManager ( this . autocomplete . options ) . withWrap ( ) ;
@@ -131,7 +134,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
131
134
* A stream of actions that should close the autocomplete panel, including
132
135
* when an option is selected and when the backdrop is clicked.
133
136
*/
134
- get panelClosingActions ( ) : Observable < any > {
137
+ get panelClosingActions ( ) : Observable < MdOptionSelectEvent > {
135
138
return Observable . merge (
136
139
...this . optionSelections ,
137
140
this . _overlayRef . backdropClick ( ) ,
@@ -140,7 +143,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
140
143
}
141
144
142
145
/** Stream of autocomplete option selections. */
143
- get optionSelections ( ) : Observable < any > [ ] {
146
+ get optionSelections ( ) : Observable < MdOptionSelectEvent > [ ] {
144
147
return this . autocomplete . options . map ( option => option . onSelect ) ;
145
148
}
146
149
@@ -185,14 +188,19 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
185
188
if ( this . activeOption && event . keyCode === ENTER ) {
186
189
this . activeOption . _selectViaInteraction ( ) ;
187
190
} else {
188
- this . openPanel ( ) ;
189
191
this . _keyManager . onKeydown ( event ) ;
190
192
if ( event . keyCode === UP_ARROW || event . keyCode === DOWN_ARROW ) {
193
+ this . openPanel ( ) ;
191
194
this . _scrollToOption ( ) ;
192
195
}
193
196
}
194
197
}
195
198
199
+ _handleInput ( value : string ) : void {
200
+ this . _onChange ( value ) ;
201
+ this . openPanel ( ) ;
202
+ }
203
+
196
204
/**
197
205
* Given that we are not actually focusing active options, we must manually adjust scroll
198
206
* to reveal options below the fold. First, we find the offset of the option from the top
@@ -211,22 +219,33 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
211
219
* stream every time the option list changes.
212
220
*/
213
221
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 )
222
+ const initialOptions = this . _getStableOptions ( ) ;
223
+
224
+ // When the zone is stable initially, and when the option list changes...
225
+ Observable . merge ( initialOptions , this . autocomplete . options . changes )
218
226
// create a new stream of panelClosingActions, replacing any previous streams
219
227
// that were created, and flatten it so our stream only emits closing events...
220
- . switchMap ( ( ) => {
228
+ . switchMap ( options => {
221
229
this . _resetPanel ( ) ;
222
- return this . panelClosingActions ;
230
+ // If the options list is empty, emit close event immediately.
231
+ // Otherwise, listen for panel closing actions...
232
+ return options . length ? this . panelClosingActions : Observable . of ( null ) ;
223
233
} )
224
234
// when the first closing event occurs...
225
235
. first ( )
226
236
// set the value, close the panel, and complete.
227
237
. subscribe ( event => this . _setValueAndClose ( event ) ) ;
228
238
}
229
239
240
+ /**
241
+ * Retrieves the option list once the zone stabilizes. It's important to wait until
242
+ * stable so that change detection can run first and update the query list
243
+ * with the options available under the current filter.
244
+ */
245
+ private _getStableOptions ( ) : Observable < QueryList < MdOption > > {
246
+ return this . _zone . onStable . first ( ) . map ( ( ) => this . autocomplete . options ) ;
247
+ }
248
+
230
249
/** Destroys the autocomplete suggestion panel. */
231
250
private _destroyPanel ( ) : void {
232
251
if ( this . _overlayRef ) {
0 commit comments