Skip to content

Commit

Permalink
fix(core): Dropdown triggers change detection too frequently (#8738)
Browse files Browse the repository at this point in the history
  • Loading branch information
nsbarsukov authored Aug 29, 2024
1 parent 226a763 commit 8bdcb19
Show file tree
Hide file tree
Showing 20 changed files with 78 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Directive, inject, Input, NgZone} from '@angular/core';
import {Directive, inject, Input} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {WA_ANIMATION_FRAME, WA_PERFORMANCE} from '@ng-web-apis/common';
import {tuiDescribeSector} from '@taiga-ui/addon-charts/utils';
Expand Down Expand Up @@ -44,7 +44,7 @@ export class TuiPieChartDirective {
]),
);
}),
tuiZonefree(inject(NgZone)),
tuiZonefree(),
takeUntilDestroyed(),
)
.subscribe(([start, end]) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import {AsyncPipe, NgStyle} from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
inject,
Input,
NgZone,
Output,
} from '@angular/core';
import {ChangeDetectionStrategy, Component, inject, Input, Output} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {tuiScrollFrom, tuiZonefree} from '@taiga-ui/cdk/observables';
import {TUI_IS_IOS} from '@taiga-ui/cdk/tokens';
Expand Down Expand Up @@ -65,7 +58,7 @@ export class TuiPullToRefresh {
const el: HTMLElement = inject(TUI_SCROLL_REF).nativeElement;

tuiScrollFrom(el)
.pipe(startWith(null), tuiZonefree(inject(NgZone)), takeUntilDestroyed())
.pipe(startWith(null), tuiZonefree(), takeUntilDestroyed())
.subscribe(() => {
if (el.scrollTop) {
el.style.touchAction = '';
Expand Down
4 changes: 2 additions & 2 deletions projects/cdk/directives/obscured/obscured.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {inject, Injectable, NgZone} from '@angular/core';
import {inject, Injectable} from '@angular/core';
import {WA_ANIMATION_FRAME} from '@ng-web-apis/common';
import {tuiZoneOptimized} from '@taiga-ui/cdk/observables';
import {tuiGetElementObscures, tuiInjectElement} from '@taiga-ui/cdk/utils/dom';
Expand All @@ -16,7 +16,7 @@ export class TuiObscuredService extends Observable<readonly Element[] | null> {
map(() => tuiGetElementObscures(this.el)),
startWith(null),
distinctUntilChanged(),
tuiZoneOptimized(inject(NgZone)),
tuiZoneOptimized(),
);

constructor() {
Expand Down
44 changes: 39 additions & 5 deletions projects/cdk/observables/zone.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import type {NgZone} from '@angular/core';
import type {MonoTypeOperatorFunction} from 'rxjs';
import {inject, NgZone} from '@angular/core';
import {
asyncScheduler,
type MonoTypeOperatorFunction,
type SchedulerLike,
type Subscription,
} from 'rxjs';
import {Observable, pipe} from 'rxjs';

export function tuiZonefull<T>(zone: NgZone): MonoTypeOperatorFunction<T> {
export function tuiZonefull<T>(zone = inject(NgZone)): MonoTypeOperatorFunction<T> {
return (source) =>
new Observable((subscriber) =>
source.subscribe({
Expand All @@ -13,13 +18,42 @@ export function tuiZonefull<T>(zone: NgZone): MonoTypeOperatorFunction<T> {
);
}

export function tuiZonefree<T>(zone: NgZone): MonoTypeOperatorFunction<T> {
export function tuiZonefree<T>(zone = inject(NgZone)): MonoTypeOperatorFunction<T> {
return (source) =>
new Observable((subscriber) =>
zone.runOutsideAngular(() => source.subscribe(subscriber)),
);
}

export function tuiZoneOptimized<T>(zone: NgZone): MonoTypeOperatorFunction<T> {
export function tuiZoneOptimized<T>(zone = inject(NgZone)): MonoTypeOperatorFunction<T> {
return pipe(tuiZonefree(zone), tuiZonefull(zone));
}

class TuiZoneScheduler implements SchedulerLike {
constructor(
private readonly zoneConditionFn: <T>(fn: (...args: unknown[]) => T) => T,
private readonly scheduler: SchedulerLike = asyncScheduler,
) {}

public now(): number {
return this.scheduler.now();
}

public schedule(...args: Parameters<SchedulerLike['schedule']>): Subscription {
return this.zoneConditionFn(() => this.scheduler.schedule(...args));
}
}

export function tuiZonefreeScheduler(
zone = inject(NgZone),
scheduler: SchedulerLike = asyncScheduler,
): SchedulerLike {
return new TuiZoneScheduler(zone.runOutsideAngular.bind(zone), scheduler);
}

export function tuiZonefullScheduler(
zone = inject(NgZone),
scheduler: SchedulerLike = asyncScheduler,
): SchedulerLike {
return new TuiZoneScheduler(zone.run.bind(zone), scheduler);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {AsyncPipe, NgIf} from '@angular/common';
import {ChangeDetectionStrategy, Component, inject, NgZone} from '@angular/core';
import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
import {WA_ANIMATION_FRAME} from '@ng-web-apis/common';
import {tuiZoneOptimized} from '@taiga-ui/cdk/observables';
import {tuiFadeIn} from '@taiga-ui/core/animations';
Expand Down Expand Up @@ -27,7 +27,7 @@ export class TuiScrollControls {
map(() => this.scrollbars),
startWith([false, false]),
distinctUntilChanged((a, b) => a[0] === b[0] && a[1] === b[1]),
tuiZoneOptimized(inject(NgZone)),
tuiZoneOptimized(),
);

private get scrollbars(): [boolean, boolean] {
Expand Down
4 changes: 2 additions & 2 deletions projects/core/components/scrollbar/scrollbar.directive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Directive, inject, Input, NgZone} from '@angular/core';
import {Directive, inject, Input} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {WA_ANIMATION_FRAME} from '@ng-web-apis/common';
import {tuiScrollFrom, tuiZonefree} from '@taiga-ui/cdk/observables';
Expand Down Expand Up @@ -40,7 +40,7 @@ export class TuiScrollbarDirective {
inject(WA_ANIMATION_FRAME).pipe(throttleTime(100)),
tuiScrollFrom(this.el),
)
.pipe(tuiZonefree(inject(NgZone)), takeUntilDestroyed())
.pipe(tuiZonefree(), takeUntilDestroyed())
.subscribe(() => {
const dimension: ComputedDimension = {
scrollTop: this.el.scrollTop,
Expand Down
4 changes: 2 additions & 2 deletions projects/core/components/scrollbar/scrollbar.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {inject, Injectable, NgZone} from '@angular/core';
import {inject, Injectable} from '@angular/core';
import {tuiTypedFromEvent, tuiZonefree} from '@taiga-ui/cdk/observables';
import {tuiInjectElement} from '@taiga-ui/cdk/utils/dom';
import {TUI_SCROLL_REF} from '@taiga-ui/core/tokens';
Expand All @@ -13,7 +13,7 @@ export class TuiScrollbarService extends Observable<[number, number]> {
map((event) => this.getScrolled(event, 0.5, 0.5)),
),
tuiTypedFromEvent(this.el, 'mousedown').pipe(
tuiZonefree(inject(NgZone)),
tuiZonefree(),
switchMap((event) => {
const {ownerDocument} = this.el;
const rect = this.el.getBoundingClientRect();
Expand Down
4 changes: 2 additions & 2 deletions projects/core/directives/dropdown/dropdown-hover.directive.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {DOCUMENT} from '@angular/common';
import {ContentChild, Directive, ElementRef, inject, Input, NgZone} from '@angular/core';
import {ContentChild, Directive, ElementRef, inject, Input} from '@angular/core';
import {TuiActiveZone} from '@taiga-ui/cdk/directives/active-zone';
import {tuiTypedFromEvent, tuiZoneOptimized} from '@taiga-ui/cdk/observables';
import {
Expand Down Expand Up @@ -37,7 +37,7 @@ export class TuiDropdownHover extends TuiDriver {
map((element) => tuiIsElement(element) && this.isHovered(element)),
distinctUntilChanged(),
switchMap((v) => of(v).pipe(delay(v ? this.showDelay : this.hideDelay))),
tuiZoneOptimized(inject(NgZone)),
tuiZoneOptimized(),
tap((hovered) => {
this.hovered = hovered;
this.open?.toggle(hovered);
Expand Down
10 changes: 8 additions & 2 deletions projects/core/directives/dropdown/dropdown.directive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type {AfterViewChecked, ComponentRef, OnChanges, OnDestroy} from '@angular/core';
import {
type AfterViewChecked,
type ComponentRef,
type OnChanges,
type OnDestroy,
} from '@angular/core';
import {
ChangeDetectorRef,
Directive,
Expand All @@ -9,6 +14,7 @@ import {
TemplateRef,
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {tuiZonefreeScheduler} from '@taiga-ui/cdk/observables';
import type {TuiContext} from '@taiga-ui/cdk/types';
import {tuiInjectElement} from '@taiga-ui/cdk/utils/dom';
import {tuiPure} from '@taiga-ui/cdk/utils/miscellaneous';
Expand Down Expand Up @@ -49,7 +55,7 @@ export class TuiDropdownDirective
private readonly cdr = inject(ChangeDetectorRef);

protected readonly sub = this.refresh$
.pipe(throttleTime(0), takeUntilDestroyed())
.pipe(throttleTime(0, tuiZonefreeScheduler()), takeUntilDestroyed())
.subscribe(() => {
this.ref()?.changeDetectorRef.detectChanges();
this.ref()?.changeDetectorRef.markForCheck();
Expand Down
4 changes: 2 additions & 2 deletions projects/core/directives/hint/hint-describe.directive.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {DOCUMENT} from '@angular/common';
import {Directive, inject, Input, NgZone} from '@angular/core';
import {Directive, inject, Input} from '@angular/core';
import {tuiIfMap, tuiTypedFromEvent, tuiZoneOptimized} from '@taiga-ui/cdk/observables';
import {tuiInjectElement} from '@taiga-ui/cdk/utils/dom';
import {tuiIsNativeFocused} from '@taiga-ui/cdk/utils/focus';
Expand Down Expand Up @@ -43,7 +43,7 @@ export class TuiHintDescribe extends TuiDriver {
startWith(false),
distinctUntilChanged(),
skip(1),
tuiZoneOptimized(inject(NgZone)),
tuiZoneOptimized(),
);

public readonly type = 'hint';
Expand Down
4 changes: 2 additions & 2 deletions projects/core/services/breakpoint.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {inject, Injectable, NgZone} from '@angular/core';
import {inject, Injectable} from '@angular/core';
import {tuiZoneOptimized} from '@taiga-ui/cdk/observables';
import {TUI_WINDOW_SIZE} from '@taiga-ui/cdk/tokens';
import type {TuiMedia} from '@taiga-ui/core/tokens';
Expand Down Expand Up @@ -30,7 +30,7 @@ export class TuiBreakpointService extends Observable<TuiBreakpointMediaKey | nul
map(({width}) => this.sorted.find((size) => size > width)),
map((key) => this.invert[key || this.sorted[this.sorted.length - 1] || 0]),
distinctUntilChanged(),
tuiZoneOptimized(inject(NgZone)),
tuiZoneOptimized(),
shareReplay({bufferSize: 1, refCount: true}),
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {inject, Injectable, NgZone} from '@angular/core';
import {inject, Injectable} from '@angular/core';
import {MutationObserverService} from '@ng-web-apis/mutation-observer';
import {ResizeObserverService} from '@ng-web-apis/resize-observer';
import {tuiZoneOptimized} from '@taiga-ui/cdk/observables';
Expand All @@ -21,7 +21,7 @@ export class TuiItemsWithMoreService extends Observable<number> {
throttleTime(0),
map(() => this.getOverflowIndex()),
distinctUntilChanged(),
tuiZoneOptimized(inject(NgZone)),
tuiZoneOptimized(),
share(),
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ChangeDetectorRef, Directive, inject, Input, NgZone} from '@angular/core';
import {ChangeDetectorRef, Directive, inject, Input} from '@angular/core';
import {toSignal} from '@angular/core/rxjs-interop';
import {ResizeObserverService} from '@ng-web-apis/resize-observer';
import {tuiWatch, tuiZonefull} from '@taiga-ui/cdk/observables';
Expand Down Expand Up @@ -33,7 +33,7 @@ export class TuiProgressColorSegments {

return `linear-gradient(to right ${colorsString})`;
}),
tuiZonefull(inject(NgZone)),
tuiZonefull(),
tuiWatch(inject(ChangeDetectorRef)),
),
);
Expand Down
3 changes: 1 addition & 2 deletions projects/kit/components/segmented/segmented.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
EventEmitter,
inject,
Input,
NgZone,
Output,
ViewEncapsulation,
} from '@angular/core';
Expand Down Expand Up @@ -36,7 +35,7 @@ export class TuiSegmented implements OnChanges {
private readonly el = tuiInjectElement();

protected readonly sub = inject(ResizeObserverService, {self: true})
.pipe(tuiZonefree(inject(NgZone)), takeUntilDestroyed())
.pipe(tuiZonefree(), takeUntilDestroyed())
.subscribe(() => this.refresh());

@Input()
Expand Down
11 changes: 2 additions & 9 deletions projects/kit/components/tabs/tabs-horizontal.directive.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import type {AfterViewChecked, QueryList} from '@angular/core';
import {
ContentChildren,
Directive,
forwardRef,
inject,
Input,
NgZone,
} from '@angular/core';
import {ContentChildren, Directive, forwardRef, inject, Input} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {
MutationObserverService,
Expand Down Expand Up @@ -60,7 +53,7 @@ export class TuiTabsHorizontal implements AfterViewChecked {
protected readonly children: QueryList<unknown> = EMPTY_QUERY;

protected readonly sub = inject(MutationObserverService, {self: true})
.pipe(tuiZonefree(inject(NgZone)), takeUntilDestroyed())
.pipe(tuiZonefree(), takeUntilDestroyed())
.subscribe(() => this.refresh());

@Input()
Expand Down
3 changes: 1 addition & 2 deletions projects/kit/directives/fade/fade.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
Directive,
inject,
Input,
NgZone,
ViewEncapsulation,
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
Expand Down Expand Up @@ -75,7 +74,7 @@ export class TuiFade {
inject(MutationObserverService, {self: true}),
fromEvent(el, 'scroll'),
)
.pipe(tuiZonefree(inject(NgZone)), takeUntilDestroyed())
.pipe(tuiZonefree(), takeUntilDestroyed())
.subscribe(() => {
el.classList.toggle('_start', !!el.scrollLeft || !!el.scrollTop);
el.classList.toggle('_end', this.isEnd(el));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {OnChanges} from '@angular/core';
import {Directive, inject, Input, NgZone} from '@angular/core';
import {Directive, inject, Input} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {
MutationObserverService,
Expand Down Expand Up @@ -39,7 +39,7 @@ export class TuiFluidTypography implements OnChanges {
inject(MutationObserverService, {self: true}),
fromEvent(this.el, 'input'),
)
.pipe(tuiZonefree(inject(NgZone)), takeUntilDestroyed())
.pipe(tuiZonefree(), takeUntilDestroyed())
.subscribe(() => {
const min = Number(this.tuiFluidTypography[0] || this.options.min);
const max = Number(this.tuiFluidTypography[1] || this.options.max);
Expand Down
3 changes: 1 addition & 2 deletions projects/kit/directives/sensitive/sensitive.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
Directive,
inject,
Input,
NgZone,
ViewEncapsulation,
} from '@angular/core';
import {toSignal} from '@angular/core/rxjs-interop';
Expand Down Expand Up @@ -50,7 +49,7 @@ export class TuiSensitive {
return [Math.max(2, Math.floor(height / 16) + 1), height];
}),
map(([rows, height]) => height * (rowsInSvg / rows)),
tuiZonefull(inject(NgZone)),
tuiZonefull(),
tuiWatch(inject(ChangeDetectorRef)),
),
);
Expand Down
3 changes: 1 addition & 2 deletions projects/layout/components/app-bar/app-bar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
Component,
inject,
Input,
NgZone,
ViewChildren,
ViewEncapsulation,
} from '@angular/core';
Expand Down Expand Up @@ -40,7 +39,7 @@ export class TuiAppBarComponent {
inject(ResizeObserverService, {self: true}),
inject(MutationObserverService, {self: true}),
).pipe(
tuiZonefull(inject(NgZone)),
tuiZonefull(),
map(
() =>
2 *
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
ElementRef,
inject,
Input,
NgZone,
type QueryList,
signal,
TemplateRef,
Expand Down Expand Up @@ -75,7 +74,7 @@ export class TuiSearchFiltersComponent implements AfterContentInit {
return Math.floor((width - this.more) / WIDTH / this.unit);
}),
distinctUntilChanged(),
tuiZonefull(inject(NgZone)),
tuiZonefull(),
),
{initialValue: 0},
);
Expand Down

0 comments on commit 8bdcb19

Please sign in to comment.