Skip to content

Commit 249db69

Browse files
authored
refactor(material/autocomplete): Remove use of zone onStable to wait for options (#28654)
* refactor(material/autocomplete): Remove use of zone onStable to wait for options * test: fix tests
1 parent c8c0230 commit 249db69

File tree

2 files changed

+264
-234
lines changed

2 files changed

+264
-234
lines changed

src/material/autocomplete/autocomplete-trigger.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {addAriaReferencedId, removeAriaReferencedId} from '@angular/cdk/a11y';
1010
import {
11+
afterNextRender,
1112
AfterViewInit,
1213
booleanAttribute,
1314
ChangeDetectorRef,
@@ -18,6 +19,7 @@ import {
1819
inject,
1920
Inject,
2021
InjectionToken,
22+
Injector,
2123
Input,
2224
NgZone,
2325
OnChanges,
@@ -227,6 +229,10 @@ export class MatAutocompleteTrigger
227229
@Input({alias: 'matAutocompleteDisabled', transform: booleanAttribute})
228230
autocompleteDisabled: boolean;
229231

232+
private _initialized = new Subject();
233+
234+
private _injector = inject(Injector);
235+
230236
constructor(
231237
private _element: ElementRef<HTMLInputElement>,
232238
private _overlay: Overlay,
@@ -249,6 +255,9 @@ export class MatAutocompleteTrigger
249255
private _aboveClass = 'mat-mdc-autocomplete-panel-above';
250256

251257
ngAfterViewInit() {
258+
this._initialized.next();
259+
this._initialized.complete();
260+
252261
const window = this._getWindow();
253262

254263
if (typeof window !== 'undefined') {
@@ -301,8 +310,8 @@ export class MatAutocompleteTrigger
301310

302311
if (this.panelOpen) {
303312
// Only emit if the panel was visible.
304-
// The `NgZone.onStable` always emits outside of the Angular zone,
305-
// so all the subscriptions from `_subscribeToClosingActions()` are also outside of the Angular zone.
313+
// `afterNextRender` always runs outside of the Angular zone, so all the subscriptions from
314+
// `_subscribeToClosingActions()` are also outside of the Angular zone.
306315
// We should manually run in Angular zone to update UI after panel closing.
307316
this._zone.run(() => {
308317
this.autocomplete.closed.emit();
@@ -378,10 +387,7 @@ export class MatAutocompleteTrigger
378387

379388
// If there are any subscribers before `ngAfterViewInit`, the `autocomplete` will be undefined.
380389
// Return a stream that we'll replace with the real one once everything is in place.
381-
return this._zone.onStable.pipe(
382-
take(1),
383-
switchMap(() => this.optionSelections),
384-
);
390+
return this._initialized.pipe(switchMap(() => this.optionSelections));
385391
}) as Observable<MatOptionSelectionChange>;
386392

387393
/** The currently active option, coerced to MatOption type. */
@@ -592,25 +598,32 @@ export class MatAutocompleteTrigger
592598
* stream every time the option list changes.
593599
*/
594600
private _subscribeToClosingActions(): Subscription {
595-
const firstStable = this._zone.onStable.pipe(take(1));
601+
const initialRender = new Observable(subscriber => {
602+
afterNextRender(
603+
() => {
604+
subscriber.next();
605+
},
606+
{injector: this._injector},
607+
);
608+
});
596609
const optionChanges = this.autocomplete.options.changes.pipe(
597610
tap(() => this._positionStrategy.reapplyLastPosition()),
598611
// Defer emitting to the stream until the next tick, because changing
599612
// bindings in here will cause "changed after checked" errors.
600613
delay(0),
601614
);
602615

603-
// When the zone is stable initially, and when the option list changes...
616+
// When the options are initially rendered, and when the option list changes...
604617
return (
605-
merge(firstStable, optionChanges)
618+
merge(initialRender, optionChanges)
606619
.pipe(
607620
// create a new stream of panelClosingActions, replacing any previous streams
608621
// that were created, and flatten it so our stream only emits closing events...
609-
switchMap(() => {
610-
// The `NgZone.onStable` always emits outside of the Angular zone, thus we have to re-enter
611-
// the Angular zone. This will lead to change detection being called outside of the Angular
612-
// zone and the `autocomplete.opened` will also emit outside of the Angular.
622+
switchMap(() =>
613623
this._zone.run(() => {
624+
// `afterNextRender` always runs outside of the Angular zone, thus we have to re-enter
625+
// the Angular zone. This will lead to change detection being called outside of the Angular
626+
// zone and the `autocomplete.opened` will also emit outside of the Angular.
614627
const wasOpen = this.panelOpen;
615628
this._resetActiveItem();
616629
this._updatePanelState();
@@ -634,10 +647,10 @@ export class MatAutocompleteTrigger
634647
this.autocomplete.closed.emit();
635648
}
636649
}
637-
});
638650

639-
return this.panelClosingActions;
640-
}),
651+
return this.panelClosingActions;
652+
}),
653+
),
641654
// when the first closing event occurs...
642655
take(1),
643656
)

0 commit comments

Comments
 (0)