diff --git a/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts b/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts index ba4e8bfda8b..b219e8f24d4 100644 --- a/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts +++ b/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild, TemplateRef, ChangeDetectionStrategy, ElementRef } from '@angular/core'; +import { Component, ViewChild, TemplateRef, ChangeDetectionStrategy, ElementRef, provideZonelessChangeDetection, inject, ChangeDetectorRef } from '@angular/core'; import { TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { @@ -1049,6 +1049,47 @@ describe('Carousel', () => { }); }); +describe('Carousel Zoneless Tests:', () => { + let mockElement: any; + let mockElementRef: ElementRef; + + beforeEach(async () => { + TestBed.resetTestingModule(); + mockElement = document.createElement("div"); + mockElementRef = new ElementRef(mockElement); + await TestBed.configureTestingModule({ + imports: [ + NoopAnimationsModule, + CarouselDynamicSlidesWithNoActiveComponent, + ], + providers: [ + { provide: ElementRef, useValue: mockElementRef }, + IgxSlideComponent, + provideZonelessChangeDetection() + ] + }).compileComponents(); + }); + + it('should activate and show the correct slide when the entire collection is replaced', async () => { + const fix = TestBed.createComponent(CarouselDynamicSlidesWithNoActiveComponent); + await wait(16); + fix.detectChanges(); + await fix.whenStable(); + const car: IgxCarouselComponent = fix.componentInstance.carousel; + HelperTestFunctions.verifyActiveSlide(car, 0); + + // Replace the entire slide collection; + fix.componentInstance.changeSlides(); + await wait(16); + fix.detectChanges(); + await fix.whenStable(); + + expect(car.total).toEqual(3); + // the carousel should activate the first slide again + HelperTestFunctions.verifyActiveSlide(car, 0); + }); +}); + class HelperTestFunctions { public static NEXT_BUTTON_CLASS = '.igx-carousel__arrow--next'; public static PRIV_BUTTON_CLASS = '.igx-carousel__arrow--prev'; @@ -1117,7 +1158,7 @@ class HelperTestFunctions { const carouselElement = fixture.debugElement.query(By.css('igx-carousel')); const touchManager = carouselElement.injector.get(HammerGesturesManager); const hammerManager = touchManager.getManagerForElement(carouselElement.nativeElement); - (hammerManager as any).emit('tap', { target: activeSlide, srcEvent: { preventDefault: () => {} } }); + (hammerManager as any).emit('tap', { target: activeSlide, srcEvent: { preventDefault: () => { } } }); } public static simulatePan(fixture, carousel, deltaOffset, velocity, dir: 'horizontal' | 'vertical') { @@ -1139,8 +1180,8 @@ class HelperTestFunctions { deltaY, duration: 100, velocity, - preventDefault: ( () => { }), - srcEvent: { preventDefault: () => {} } + preventDefault: (() => { }), + srcEvent: { preventDefault: () => { } } }; (hammerManager as any).emit(event, panOptions); @@ -1298,3 +1339,42 @@ class CarouselDynamicSlidesComponent { ); } } + +@Component({ + template: ` + + @for (slide of slides; track slide) { + +

{{slide.text}}

+
+ } +
+ `, + changeDetection: ChangeDetectionStrategy.Eager, + imports: [IgxCarouselComponent, IgxSlideComponent] +}) +class CarouselDynamicSlidesWithNoActiveComponent { + @ViewChild('carousel', { static: true }) public carousel: IgxCarouselComponent; + + public loop = true; + public slides = []; + private cdr = inject(ChangeDetectorRef); + + constructor() { + this.slides.push( + { text: 'Slide 1' }, + { text: 'Slide 2' }, + { text: 'Slide 3' }, + { text: 'Slide 4' } + ); + } + + public changeSlides() { + this.slides = [ + { text: 'New Slide 1' }, + { text: 'New Slide 2' }, + { text: 'New Slide 3' } + ]; + this.cdr.markForCheck(); + } +} diff --git a/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts b/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts index d13cde0aae6..ad296e33c29 100644 --- a/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts +++ b/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts @@ -2,7 +2,7 @@ import { NgClass, NgTemplateOutlet } from '@angular/common'; import { AfterContentInit, Component, ContentChild, ContentChildren, ElementRef, EventEmitter, HostBinding, HostListener, Input, IterableChangeRecord, IterableDiffer, IterableDiffers, OnDestroy, Output, QueryList, TemplateRef, ViewChild, ViewChildren, booleanAttribute, inject, ChangeDetectionStrategy } from '@angular/core'; import { merge, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { CarouselResourceStringsEN, ICarouselResourceStrings, isLeftToRight} from 'igniteui-angular/core'; +import { CarouselResourceStringsEN, ICarouselResourceStrings, isLeftToRight } from 'igniteui-angular/core'; import { first, IBaseEventArgs, last, PlatformUtil } from 'igniteui-angular/core'; import { CarouselAnimationDirection, IgxCarouselComponentBase } from './carousel-base'; import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives'; @@ -90,9 +90,9 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On /** @hidden */ @HostBinding('class.igx-carousel--vertical') - public get isVertical(): boolean { - return this.vertical; - } + public get isVertical(): boolean { + return this.vertical; + } /** * Returns the class of the carousel component. @@ -1071,7 +1071,9 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On this.slideRemoved.emit({ carousel: this, slide }); if (slide.active) { slide.active = false; - this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1); + if (this.currentItem === slide) { // Only fall back if nothing better was found. + this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1); + } } }); @@ -1090,6 +1092,7 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On this.slides.first.active = true; } this.play(); + this.cdr.markForCheck(); }); } }