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
  • Loading branch information
mmalerba committed Feb 26, 2024
1 parent 63a764d commit a191c36
Showing 1 changed file with 42 additions and 39 deletions.
81 changes: 42 additions & 39 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,12 +310,7 @@ 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.
// We should manually run in Angular zone to update UI after panel closing.
this._zone.run(() => {
this.autocomplete.closed.emit();
});
this.autocomplete.closed.emit();
}

this.autocomplete._isOpen = this._overlayAttached = false;
Expand Down Expand Up @@ -378,10 +382,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,49 +593,51 @@ 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.
this._zone.run(() => {
const wasOpen = this.panelOpen;
this._resetActiveItem();
this._updatePanelState();
this._changeDetectorRef.detectChanges();

const wasOpen = this.panelOpen;
this._resetActiveItem();
this._updatePanelState();
this._changeDetectorRef.detectChanges();

if (this.panelOpen) {
this._overlayRef!.updatePosition();
}

if (wasOpen !== this.panelOpen) {
// If the `panelOpen` state changed, we need to make sure to emit the `opened` or
// `closed` event, because we may not have emitted it. This can happen
// - if the users opens the panel and there are no options, but the
// options come in slightly later or as a result of the value changing,
// - if the panel is closed after the user entered a string that did not match any
// of the available options,
// - if a valid string is entered after an invalid one.
if (this.panelOpen) {
this._overlayRef!.updatePosition();
}

if (wasOpen !== this.panelOpen) {
// If the `panelOpen` state changed, we need to make sure to emit the `opened` or
// `closed` event, because we may not have emitted it. This can happen
// - if the users opens the panel and there are no options, but the
// options come in slightly later or as a result of the value changing,
// - if the panel is closed after the user entered a string that did not match any
// of the available options,
// - if a valid string is entered after an invalid one.
if (this.panelOpen) {
this._emitOpened();
} else {
this.autocomplete.closed.emit();
}
this._emitOpened();
} else {
this.autocomplete.closed.emit();
}
});
}

return this.panelClosingActions;
}),
Expand Down

0 comments on commit a191c36

Please sign in to comment.