@@ -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' ;
1113import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ;
@@ -18,6 +20,7 @@ import {MdOptionSelectEvent, MdOption} from '../core/option/option';
1820import { ActiveDescendantKeyManager } from '../core/a11y/activedescendant-key-manager' ;
1921import { ENTER , UP_ARROW , DOWN_ARROW } from '../core/keyboard/keycodes' ;
2022import { Subscription } from 'rxjs/Subscription' ;
23+ import 'rxjs/add/observable/of' ;
2124import 'rxjs/add/observable/merge' ;
2225import { Dir } from '../core/rtl/dir' ;
2326import '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 > {
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
@@ -185,14 +188,19 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
185188 if ( this . activeOption && event . keyCode === ENTER ) {
186189 this . activeOption . _selectViaInteraction ( ) ;
187190 } else {
188- this . openPanel ( ) ;
189191 this . _keyManager . onKeydown ( event ) ;
190192 if ( event . keyCode === UP_ARROW || event . keyCode === DOWN_ARROW ) {
193+ this . openPanel ( ) ;
191194 this . _scrollToOption ( ) ;
192195 }
193196 }
194197 }
195198
199+ _handleInput ( value : string ) : void {
200+ this . _onChange ( value ) ;
201+ this . openPanel ( ) ;
202+ }
203+
196204 /**
197205 * Given that we are not actually focusing active options, we must manually adjust scroll
198206 * 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
211219 * stream every time the option list changes.
212220 */
213221 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 )
218226 // create a new stream of panelClosingActions, replacing any previous streams
219227 // that were created, and flatten it so our stream only emits closing events...
220- . switchMap ( ( ) => {
228+ . switchMap ( options => {
221229 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 ) ;
223233 } )
224234 // when the first closing event occurs...
225235 . first ( )
226236 // set the value, close the panel, and complete.
227237 . subscribe ( event => this . _setValueAndClose ( event ) ) ;
228238 }
229239
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+
230249 /** Destroys the autocomplete suggestion panel. */
231250 private _destroyPanel ( ) : void {
232251 if ( this . _overlayRef ) {
0 commit comments