Skip to content

Commit 49ed378

Browse files
authored
fix: do not trigger change detection on scroll events (#429)
1 parent 660b449 commit 49ed378

File tree

6 files changed

+93
-35
lines changed

6 files changed

+93
-35
lines changed

libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ import {
3838
Observable,
3939
of,
4040
Subject,
41-
Subscription
41+
Subscription,
42+
timer
4243
} from 'rxjs';
4344
import { debounceTime, filter, map, startWith, switchMap, takeUntil } from 'rxjs/operators';
4445
import {
@@ -48,20 +49,20 @@ import {
4849
NguCarouselPrevDirective
4950
} from './../ngu-carousel.directive';
5051
import {
52+
Transfrom,
5153
Breakpoints,
5254
NguCarouselConfig,
5355
NguCarouselOutletContext,
5456
NguCarouselStore
5557
} from './ngu-carousel';
58+
import { NguWindowScrollListener } from './ngu-window-scroll-listener';
5659

57-
// @dynamic
5860
@Component({
5961
selector: 'ngu-carousel',
6062
templateUrl: 'ngu-carousel.component.html',
6163
styleUrls: ['ngu-carousel.component.scss'],
6264
changeDetection: ChangeDetectionStrategy.OnPush
6365
})
64-
// @dynamic
6566
// eslint-disable-next-line @angular-eslint/component-class-suffix
6667
export class NguCarousel<T>
6768
extends NguCarouselStore
@@ -88,7 +89,6 @@ export class NguCarousel<T>
8889
listener1: () => void;
8990
listener2: () => void;
9091
listener3: () => void;
91-
listener4: () => void;
9292

9393
@Input('dataSource')
9494
get dataSource(): any {
@@ -147,8 +147,6 @@ export class NguCarousel<T>
147147

148148
private carousel: any;
149149

150-
private onScrolling: any;
151-
152150
private _hammertime: HammerManager | null = null;
153151

154152
private _destroy$ = new Subject<void>();
@@ -179,7 +177,8 @@ export class NguCarousel<T>
179177
private _differs: IterableDiffers,
180178
@Inject(PLATFORM_ID) private platformId: object,
181179
private _cdr: ChangeDetectorRef,
182-
private _ngZone: NgZone
180+
private _ngZone: NgZone,
181+
private _nguWindowScrollListener: NguWindowScrollListener
183182
) {
184183
super();
185184
}
@@ -230,17 +229,21 @@ export class NguCarousel<T>
230229
if (!this.arrayChanges) return;
231230

232231
this.arrayChanges.forEachOperation(
233-
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number, currentIndex: number) => {
234-
const node = this._getNodeDef(data[currentIndex], currentIndex);
232+
(
233+
item: IterableChangeRecord<any>,
234+
adjustedPreviousIndex: number | null,
235+
currentIndex: number | null
236+
) => {
237+
const node = this._getNodeDef(data[currentIndex!], currentIndex!);
235238

236239
if (item.previousIndex == null) {
237-
const context = new NguCarouselOutletContext<any>(data[currentIndex]);
238-
context.index = currentIndex;
239-
viewContainer.createEmbeddedView(node.template, context, currentIndex);
240+
const context = new NguCarouselOutletContext<any>(data[currentIndex!]);
241+
context.index = currentIndex!;
242+
viewContainer.createEmbeddedView(node.template, context, currentIndex!);
240243
} else if (currentIndex == null) {
241-
viewContainer.remove(adjustedPreviousIndex);
244+
viewContainer.remove(adjustedPreviousIndex!);
242245
} else {
243-
const view = viewContainer.get(adjustedPreviousIndex);
246+
const view = viewContainer.get(adjustedPreviousIndex!);
244247
viewContainer.move(view!, currentIndex);
245248
}
246249
}
@@ -341,9 +344,9 @@ export class NguCarousel<T>
341344
this.onMove.complete();
342345

343346
/** remove listeners */
344-
clearTimeout(this.onScrolling);
345-
for (let i = 1; i <= 4; i++) {
346-
const str = `listener${i}`;
347+
for (let i = 1; i <= 3; i++) {
348+
// TODO: revisit later.
349+
const str = `listener${i}` as 'listener1' | 'listener2' | 'listener3';
347350
this[str] && this[str]();
348351
}
349352
}
@@ -644,7 +647,6 @@ export class NguCarousel<T>
644647
);
645648
}
646649

647-
// tslint:disable-next-line:no-unused-expression
648650
this.RTL && !this.vertical.enabled && this._renderer.addClass(this.carousel, 'ngurtl');
649651
this._createStyleElem(`${dism} ${itemStyle}`);
650652
this._storeCarouselData();
@@ -653,7 +655,6 @@ export class NguCarousel<T>
653655
/** logic to scroll the carousel step 1 */
654656
private _carouselScrollOne(Btn: number): void {
655657
let itemSpeed = this.speed;
656-
let translateXval = 0;
657658
let currentSlide = 0;
658659
let touchMove = Math.ceil(this.dexVal / this.itemWidth);
659660
touchMove = isFinite(touchMove) ? touchMove : 0;
@@ -670,7 +671,7 @@ export class NguCarousel<T>
670671
itemSpeed = 400;
671672
this._btnBoolean(0, 1);
672673
} else if (this.slideItems >= MoveSlide) {
673-
currentSlide = translateXval = 0;
674+
currentSlide = 0;
674675
this._btnBoolean(1, 0);
675676
} else {
676677
this._btnBoolean(0, 0);
@@ -690,7 +691,7 @@ export class NguCarousel<T>
690691
currentSlide = this.dataSource.length - this.items;
691692
this._btnBoolean(0, 1);
692693
} else if (this.isLast) {
693-
currentSlide = translateXval = 0;
694+
currentSlide = 0;
694695
itemSpeed = 400;
695696
this._btnBoolean(1, 0);
696697
} else {
@@ -708,8 +709,6 @@ export class NguCarousel<T>
708709

709710
/** logic to scroll the carousel step 2 */
710711
private _carouselScrollTwo(Btn: number, currentSlide: number, itemSpeed: number): void {
711-
// tslint:disable-next-line:no-unused-expression
712-
713712
if (this.dexVal !== 0) {
714713
const val = Math.abs(this.touch.velocity);
715714
let somt = Math.floor((this.dexVal / val / this.dexVal) * (this.deviceWidth - this.dexVal));
@@ -750,15 +749,15 @@ export class NguCarousel<T>
750749
this.isLast = !!last;
751750
}
752751

753-
private _transformString(grid: string, slide: number): string {
752+
private _transformString(grid: keyof Transfrom, slide: number): string {
754753
let collect = '';
755754
collect += `${this.styleid} { transform: translate3d(`;
756755

757756
if (this.vertical.enabled) {
758-
this.transform[grid] = (this.vertical.height / this.inputs.grid[grid]) * slide;
757+
this.transform[grid] = (this.vertical.height / this.inputs.grid[grid]!) * slide;
759758
collect += `0, -${this.transform[grid]}px, 0`;
760759
} else {
761-
this.transform[grid] = (100 / this.inputs.grid[grid]) * slide;
760+
this.transform[grid] = (100 / this.inputs.grid[grid]!) * slide;
762761
collect += `${this.directionSym}${this.transform[grid]}%, 0, 0`;
763762
}
764763
collect += `); }`;
@@ -808,12 +807,18 @@ export class NguCarousel<T>
808807
private _carouselInterval(): void {
809808
const container = this.carouselMain1.nativeElement;
810809
if (this.interval && this.loop) {
811-
this.listener4 = this._renderer.listen('window', 'scroll', () => {
812-
clearTimeout(this.onScrolling);
813-
this.onScrolling = setTimeout(() => {
814-
this._onWindowScrolling();
815-
}, 600);
816-
});
810+
this._nguWindowScrollListener
811+
.pipe(
812+
// Note: do not use `debounceTime` since it may flush queued actions within the Angular zone.
813+
switchMap(() => timer(600)),
814+
takeUntil(this._destroy$)
815+
)
816+
.subscribe(() => {
817+
// Note: we don't run change detection on each `scroll` event, but we re-enter the
818+
// Angular zone once the DOM timer fires to be backwards compatible.
819+
// TODO: revisit later since we may not run change detection at all on this task.
820+
this._ngZone.run(() => this._onWindowScrolling());
821+
});
817822

818823
const mapToZero = map(() => 0);
819824
const mapToOne = map(() => 1);

libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ declare interface TransformInterface {
6868
all: number;
6969
}
7070

71+
// This is misspelled. Must be changed to `Transform`.
7172
export class Transfrom implements TransformInterface {
7273
public xl? = 0;
7374
constructor(public xs = 0, public sm = 0, public md = 0, public lg = 0, public all = 0) {}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { isPlatformBrowser } from '@angular/common';
2+
import { Inject, Injectable, NgZone, OnDestroy, PLATFORM_ID } from '@angular/core';
3+
import { Subject, fromEvent } from 'rxjs';
4+
import { takeUntil } from 'rxjs/operators';
5+
6+
@Injectable({ providedIn: 'root' })
7+
export class NguWindowScrollListener extends Subject<Event> implements OnDestroy {
8+
private readonly _destroy$ = new Subject<void>();
9+
10+
constructor(@Inject(PLATFORM_ID) platformId: string, ngZone: NgZone) {
11+
super();
12+
13+
// Note: this service is shared between multiple `NguCarousel` components and each instance
14+
// doesn't add new events listener for the `window`.
15+
if (isPlatformBrowser(platformId)) {
16+
ngZone.runOutsideAngular(() =>
17+
fromEvent(window, 'scroll').pipe(takeUntil(this._destroy$)).subscribe(this)
18+
);
19+
}
20+
}
21+
22+
ngOnDestroy(): void {
23+
this._destroy$.next();
24+
}
25+
}

libs/ngu/carousel/src/lib/ngu-item/ngu-item.component.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { Component, HostBinding } from '@angular/core';
22

33
@Component({
44
selector: 'ngu-item',
5-
templateUrl: 'ngu-item.component.html',
6-
styleUrls: ['ngu-item.component.scss']
5+
templateUrl: 'ngu-item.component.html'
76
})
87
export class NguItemComponent {
98
@HostBinding('class.item') classes = true;

libs/ngu/carousel/tsconfig.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"extends": "../../../tsconfig.base.json",
3+
"files": [],
4+
"include": [],
5+
"references": [
6+
{
7+
"path": "./tsconfig.lib.json"
8+
},
9+
{
10+
"path": "./tsconfig.spec.json"
11+
}
12+
],
13+
"compilerOptions": {
14+
"forceConsistentCasingInFileNames": true,
15+
"strict": true,
16+
"strictPropertyInitialization": false,
17+
"noImplicitOverride": true,
18+
"noPropertyAccessFromIndexSignature": true,
19+
"noImplicitReturns": true,
20+
"noFallthroughCasesInSwitch": true,
21+
"target": "es2020"
22+
},
23+
"angularCompilerOptions": {
24+
"strictInjectionParameters": true,
25+
"strictInputAccessModifiers": true,
26+
"strictTemplates": true
27+
}
28+
}

libs/ngu/carousel/tsconfig.lib.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "../../../tsconfig.base.json",
2+
"extends": "./tsconfig.json",
33
"compilerOptions": {
44
"outDir": "../../../dist/out-tsc",
55
"declaration": true,

0 commit comments

Comments
 (0)