Skip to content

Commit

Permalink
refactor(material/autocomplete): Remove use of zone onStable to wait …
Browse files Browse the repository at this point in the history
…for options (#28654)

* refactor(material/autocomplete): Remove use of zone onStable to wait for options

* test: fix tests
  • Loading branch information
mmalerba authored Mar 14, 2024
1 parent c8c0230 commit 249db69
Show file tree
Hide file tree
Showing 2 changed files with 264 additions and 234 deletions.
45 changes: 29 additions & 16 deletions src/material/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {addAriaReferencedId, removeAriaReferencedId} from '@angular/cdk/a11y';
import {
afterNextRender,
AfterViewInit,
booleanAttribute,
ChangeDetectorRef,
Expand All @@ -18,6 +19,7 @@ import {
inject,
Inject,
InjectionToken,
Injector,
Input,
NgZone,
OnChanges,
Expand Down Expand Up @@ -227,6 +229,10 @@ export class MatAutocompleteTrigger
@Input({alias: 'matAutocompleteDisabled', transform: booleanAttribute})
autocompleteDisabled: boolean;

private _initialized = new Subject();

private _injector = inject(Injector);

constructor(
private _element: ElementRef<HTMLInputElement>,
private _overlay: Overlay,
Expand All @@ -249,6 +255,9 @@ export class MatAutocompleteTrigger
private _aboveClass = 'mat-mdc-autocomplete-panel-above';

ngAfterViewInit() {
this._initialized.next();
this._initialized.complete();

const window = this._getWindow();

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

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

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

/** The currently active option, coerced to MatOption type. */
Expand Down Expand Up @@ -592,25 +598,32 @@ export class MatAutocompleteTrigger
* stream every time the option list changes.
*/
private _subscribeToClosingActions(): Subscription {
const firstStable = this._zone.onStable.pipe(take(1));
const initialRender = new Observable(subscriber => {
afterNextRender(
() => {
subscriber.next();
},
{injector: this._injector},
);
});
const optionChanges = this.autocomplete.options.changes.pipe(
tap(() => this._positionStrategy.reapplyLastPosition()),
// Defer emitting to the stream until the next tick, because changing
// bindings in here will cause "changed after checked" errors.
delay(0),
);

// When the zone is stable initially, and when the option list changes...
// When the options are initially rendered, and when the option list changes...
return (
merge(firstStable, optionChanges)
merge(initialRender, optionChanges)
.pipe(
// create a new stream of panelClosingActions, replacing any previous streams
// that were created, and flatten it so our stream only emits closing events...
switchMap(() => {
// The `NgZone.onStable` always emits outside of the Angular zone, thus we have to re-enter
// the Angular zone. This will lead to change detection being called outside of the Angular
// zone and the `autocomplete.opened` will also emit outside of the Angular.
switchMap(() =>
this._zone.run(() => {
// `afterNextRender` always runs outside of the Angular zone, thus we have to re-enter
// the Angular zone. This will lead to change detection being called outside of the Angular
// zone and the `autocomplete.opened` will also emit outside of the Angular.
const wasOpen = this.panelOpen;
this._resetActiveItem();
this._updatePanelState();
Expand All @@ -634,10 +647,10 @@ export class MatAutocompleteTrigger
this.autocomplete.closed.emit();
}
}
});

return this.panelClosingActions;
}),
return this.panelClosingActions;
}),
),
// when the first closing event occurs...
take(1),
)
Expand Down
Loading

0 comments on commit 249db69

Please sign in to comment.