From a3b7f50844a3e3133b59f00b65d65d5fb6bb736f Mon Sep 17 00:00:00 2001 From: Luca Peruzzo Date: Tue, 30 May 2023 10:23:13 +0200 Subject: [PATCH] FIX: content duplication on async carousels --- .../ngu-carousel/ngu-carousel.component.ts | 94 +++++++------------ 1 file changed, 35 insertions(+), 59 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 e074c556..9c21e6c9 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 @@ -15,6 +15,7 @@ import { IterableChanges, IterableDiffer, IterableDiffers, + NgIterable, NgZone, OnDestroy, OnInit, @@ -46,7 +47,7 @@ import { NguCarouselHammerManager } from './ngu-carousel-hammer-manager'; type DirectionSymbol = '' | '-'; -type NguCarouselDataSource = Observable | any[] | null | undefined; +type NguCarouselDataSource = (U & NgIterable) | null | undefined; // This will be provided through Terser global definitions by Angular CLI. // This is how Angular does tree-shaking internally. @@ -62,9 +63,9 @@ const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode; providers: [NguCarouselHammerManager] }) // eslint-disable-next-line @angular-eslint/component-class-suffix -export class NguCarousel +export class NguCarousel = NgIterable> extends NguCarouselStore - implements OnInit, AfterContentInit, AfterViewInit, OnDestroy, DoCheck + implements AfterContentInit, AfterViewInit, OnDestroy, DoCheck { /** Public property that may be accessed outside of the component. */ activePoint = 0; @@ -77,28 +78,18 @@ export class NguCarousel // eslint-disable-next-line @angular-eslint/no-output-on-prefix @Output() onMove = new EventEmitter>(); - private _arrayChanges: IterableChanges<{}> | null = null; + private _arrayChanges: IterableChanges | null = null; @Input() - get dataSource(): NguCarouselDataSource { + get dataSource(): NguCarouselDataSource { return this._dataSource; } - set dataSource(data: NguCarouselDataSource) { + set dataSource(data: NguCarouselDataSource) { if (data) { this._switchDataSource(data); } } - 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 _dataSource: NguCarouselDataSource = null; private _defaultNodeDef: NguCarouselDefDirective | null; @@ -145,7 +136,7 @@ export class NguCarousel private _carouselCssNode: HTMLStyleElement; - private _dataDiffer: IterableDiffer<{}>; + private _dataDiffer: IterableDiffer; private _styleid: string; @@ -153,6 +144,8 @@ export class NguCarousel private _destroy$ = new Subject(); + private ngu_dirty: boolean = true; + /** * Tracking function that will be used to check the differences in data changes. Used similarly * to `ngFor` `trackBy` function. Optimize Items operations by identifying a Items based on its data @@ -189,47 +182,32 @@ export class NguCarousel this._setupButtonListeners(); } - ngOnInit() { - this._dataDiffer = this._differs - .find([]) - .create((index: number, item: any) => (this.trackBy ? this.trackBy(index, item) : item)); - } - ngDoCheck() { - this._arrayChanges = this._dataDiffer.diff(this._unwrappedData)!; - if (this._arrayChanges && this._defDirectives) { - this._observeRenderChanges(); + if (this.ngu_dirty) { + this.ngu_dirty = false; + const dataStream = this._dataSource; + if (!this._arrayChanges && !!dataStream) { + this._dataDiffer = this._differs + .find(dataStream) + .create((index: number, item: any) => (this.trackBy ? this.trackBy(index, item) : item))!; + } + } + if (this._dataDiffer && this._defDirectives) { + this._arrayChanges = this._dataDiffer.diff(this._dataSource)!; + if (this._arrayChanges) { + this.renderNodeChanges(Array.from(this._dataSource!)); + } } } private _switchDataSource(dataSource: any): any { this._dataSource = dataSource; - if (this._defDirectives) { - this._observeRenderChanges(); - } - } - - private _observeRenderChanges() { - let dataStream: Observable | undefined; - - if (this._dataSource instanceof Observable) { - dataStream = this._dataSource; - } else if (Array.isArray(this._dataSource)) { - dataStream = of(this._dataSource); - } - - dataStream - ?.pipe(takeUntil(merge(this._intervalController$, this._destroy$))) - .subscribe(data => { - this._unwrappedData = data; - this.renderNodeChanges(data); - this.isLast = this._pointIndex === this.currentSlide; - }); + this.ngu_dirty = true; } private renderNodeChanges(data: any[]) { if (!this._arrayChanges) return; - + this.isLast = this._pointIndex === this.currentSlide; const viewContainer = this._nodeOutlet.viewContainer; this._arrayChanges.forEachOperation( @@ -304,8 +282,6 @@ export class NguCarousel } ngAfterContentInit() { - this._observeRenderChanges(); - this._cdr.markForCheck(); } @@ -507,7 +483,7 @@ export class NguCarousel /** Init carousel point */ private _carouselPoint(): void { - const Nos = this._unwrappedData.length - (this.items - this.slideItems); + const Nos = Array.from(this._dataSource!).length - (this.items - this.slideItems); this._pointIndex = Math.ceil(Nos / this.slideItems); const pointers: number[] = []; @@ -552,7 +528,7 @@ export class NguCarousel break; case this._pointIndex - 1: this._btnBoolean(0, 1); - slideremains = this._unwrappedData.length - this.items; + slideremains = Array.from(this._dataSource!).length - this.items; break; default: this._btnBoolean(0, 0); @@ -665,7 +641,7 @@ export class NguCarousel const MoveSlide = currentSlideD + this.slideItems; this._btnBoolean(0, 1); if (this.currentSlide === 0) { - currentSlide = this._unwrappedData.length - this.items; + currentSlide = Array.from(this._dataSource!).length - this.items; itemSpeed = 400; this._btnBoolean(0, 1); } else if (this.slideItems >= MoveSlide) { @@ -683,10 +659,10 @@ export class NguCarousel this._carouselScrollTwo(Btn, currentSlide, itemSpeed); } else if (Btn === 1 && ((!this.loop && !this.isLast) || this.loop)) { if ( - this._unwrappedData.length <= this.currentSlide + this.items + this.slideItems && + Array.from(this._dataSource!).length <= this.currentSlide + this.items + this.slideItems && !this.isLast ) { - currentSlide = this._unwrappedData.length - this.items; + currentSlide = Array.from(this._dataSource!).length - this.items; this._btnBoolean(0, 1); } else if (this.isLast) { currentSlide = 0; @@ -732,7 +708,7 @@ export class NguCarousel this._setStyle(this._nguItemsContainer.nativeElement, 'transition', ``); } - this.itemLength = this._unwrappedData.length; + this.itemLength = Array.from(this._dataSource!).length; this._transformStyle(currentSlide); this.currentSlide = currentSlide; this.onMove.emit(this); @@ -785,7 +761,7 @@ export class NguCarousel /** this will trigger the carousel to load the items */ private _carouselLoadTrigger(): void { if (typeof this.inputs.load === 'number') { - this._unwrappedData.length - this.load <= this.currentSlide + this.items && + Array.from(this._dataSource!).length - this.load <= this.currentSlide + this.items && this.carouselLoad.emit(this.currentSlide); } } @@ -957,5 +933,5 @@ export class NguCarousel ); } - static ngAcceptInputType_dataSource: NguCarouselDataSource; + static ngAcceptInputType_dataSource: NguCarouselDataSource; }