From 74b9dd557bf94facf2c516395b1189193d7c3df7 Mon Sep 17 00:00:00 2001 From: arturovt Date: Sun, 16 Apr 2023 23:33:47 +0300 Subject: [PATCH] fix: check length only on unwrapped data --- .../ngu-carousel/ngu-carousel.component.ts | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts index f034fa10..f455697a 100644 --- a/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts +++ b/libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts @@ -11,7 +11,6 @@ import { EventEmitter, Inject, Input, - isDevMode, IterableChangeRecord, IterableChanges, IterableDiffer, @@ -57,6 +56,14 @@ import { NguWindowScrollListener } from './ngu-window-scroll-listener'; type DirectionSymbol = '' | '-'; +type NguCarouselDataSource = Observable | any[] | null | undefined; + +// This will be provided through Terser global definitions by Angular CLI. +// This is how Angular does tree-shaking internally. +declare const ngDevMode: boolean; + +const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode; + @Component({ selector: 'ngu-carousel', templateUrl: 'ngu-carousel.component.html', @@ -81,16 +88,26 @@ export class NguCarousel private _arrayChanges: IterableChanges<{}> | null = null; - @Input('dataSource') - get dataSource(): any { + @Input() + get dataSource(): NguCarouselDataSource { return this._dataSource; } - set dataSource(data: any) { + set dataSource(data: NguCarouselDataSource) { if (data) { this._switchDataSource(data); } } - private _dataSource: any; + private _dataSource: NguCarouselDataSource = null; + + /** + * `_dataSource` allows multiple values to be set considering nullable and + * observable values. We shouldn't try to get `_dataSource.length` since it + * might be `null|undefined` which will throw an error that property doesn't + * exist on `undefined`. It will also always equal `undefined` on observable. + * We should wait until the observable is unwrapped and then check the length + * of the actual unwrapped data. + */ + private _unwrappedData: any[] = []; private _defaultNodeDef: NguCarouselDefDirective | null; @@ -156,7 +173,7 @@ export class NguCarousel return this._trackByFn; } set trackBy(fn: TrackByFunction) { - if (isDevMode() && fn != null && typeof fn !== 'function' && console && console.warn) { + if (NG_DEV_MODE && fn != null && typeof fn !== 'function' && console?.warn) { console.warn(`trackBy must be a function, but received ${JSON.stringify(fn)}.`); } this._trackByFn = fn; @@ -181,13 +198,13 @@ export class NguCarousel } ngOnInit() { - this._dataDiffer = this._differs.find([]).create((_i: number, item: any) => { - return this.trackBy ? this.trackBy(_i, item) : item; - }); + this._dataDiffer = this._differs + .find([]) + .create((index: number, item: any) => (this.trackBy ? this.trackBy(index, item) : item)); } ngDoCheck() { - this._arrayChanges = this._dataDiffer.diff(this.dataSource)!; + this._arrayChanges = this._dataDiffer.diff(this._unwrappedData)!; if (this._arrayChanges && this._defDirectives) { this._observeRenderChanges(); } @@ -210,8 +227,9 @@ export class NguCarousel } dataStream - ?.pipe(takeUntil(this._intervalController$), takeUntil(this._destroy$)) + ?.pipe(takeUntil(merge(this._intervalController$, this._destroy$))) .subscribe(data => { + this._unwrappedData = data; this.renderNodeChanges(data); this.isLast = this._pointIndex === this.currentSlide; }); @@ -331,8 +349,6 @@ export class NguCarousel ngOnDestroy() { this._hammertime?.destroy(); this._destroy$.next(); - this.carouselLoad.complete(); - this.onMove.complete(); } /** Get Touch input */ @@ -495,7 +511,7 @@ export class NguCarousel /** Init carousel point */ private _carouselPoint(): void { - const Nos = this.dataSource.length - (this.items - this.slideItems); + const Nos = this._unwrappedData.length - (this.items - this.slideItems); this._pointIndex = Math.ceil(Nos / this.slideItems); const pointers: number[] = []; @@ -540,7 +556,7 @@ export class NguCarousel break; case this._pointIndex - 1: this._btnBoolean(0, 1); - slideremains = this.dataSource.length - this.items; + slideremains = this._unwrappedData.length - this.items; break; default: this._btnBoolean(0, 0); @@ -653,7 +669,7 @@ export class NguCarousel const MoveSlide = currentSlideD + this.slideItems; this._btnBoolean(0, 1); if (this.currentSlide === 0) { - currentSlide = this.dataSource.length - this.items; + currentSlide = this._unwrappedData.length - this.items; itemSpeed = 400; this._btnBoolean(0, 1); } else if (this.slideItems >= MoveSlide) { @@ -671,10 +687,10 @@ export class NguCarousel this._carouselScrollTwo(Btn, currentSlide, itemSpeed); } else if (Btn === 1 && ((!this.loop && !this.isLast) || this.loop)) { if ( - this.dataSource.length <= this.currentSlide + this.items + this.slideItems && + this._unwrappedData.length <= this.currentSlide + this.items + this.slideItems && !this.isLast ) { - currentSlide = this.dataSource.length - this.items; + currentSlide = this._unwrappedData.length - this.items; this._btnBoolean(0, 1); } else if (this.isLast) { currentSlide = 0; @@ -720,7 +736,7 @@ export class NguCarousel this._setStyle(this._nguItemsContainer.nativeElement, 'transition', ``); } - this.itemLength = this.dataSource.length; + this.itemLength = this._unwrappedData.length; this._transformStyle(currentSlide); this.currentSlide = currentSlide; this.onMove.emit(this); @@ -773,7 +789,7 @@ export class NguCarousel /** this will trigger the carousel to load the items */ private _carouselLoadTrigger(): void { if (typeof this.inputs.load === 'number') { - this.dataSource.length - this.load <= this.currentSlide + this.items && + this._unwrappedData.length - this.load <= this.currentSlide + this.items && this.carouselLoad.emit(this.currentSlide); } } @@ -944,4 +960,6 @@ export class NguCarousel }) ); } + + static ngAcceptInputType_dataSource: NguCarouselDataSource; }