• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 30891705717

04 Aug 2026 08:22AM UTC coverage: 90.16% (-0.03%) from 90.187%
30891705717

Pull #15125

github

web-flow
Merge 19b57fb2e into 0a0755cbb
Pull Request #15125: refactor(*): bundle styles with components

14975 of 17448 branches covered (85.83%)

Branch coverage included in aggregate %.

30133 of 32583 relevant lines covered (92.48%)

37490.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.93
/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts
1
import { NgClass, NgTemplateOutlet } from '@angular/common';
2
import {
3
    AfterContentInit,
4
    Component,
5
    ContentChild,
6
    ContentChildren,
7
    ElementRef,
8
    EventEmitter,
9
    HostBinding,
10
    HostListener,
11
    Input,
12
    IterableChangeRecord,
13
    IterableDiffer,
14
    IterableDiffers,
15
    OnDestroy,
16
    Output,
17
    QueryList,
18
    TemplateRef,
19
    ViewChild,
20
    ViewChildren,
21
    booleanAttribute,
22
    inject,
23
    ChangeDetectionStrategy,
24
    ViewEncapsulation
25
} from '@angular/core';
26
import { merge, Subject } from 'rxjs';
27
import { takeUntil } from 'rxjs/operators';
28
import { CarouselResourceStringsEN, ICarouselResourceStrings, isLeftToRight } from 'igniteui-angular/core';
29
import { first, IBaseEventArgs, IgxTouchManager, last, PlatformUtil } from 'igniteui-angular/core';
30
import { CarouselAnimationDirection, IgxCarouselComponentBase } from './carousel-base';
31
import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives';
32
import { IgxSlideComponent } from './slide.component';
33
import { IgxIconComponent } from 'igniteui-angular/icon';
34
import { IgxButtonDirective } from 'igniteui-angular/directives';
35
import { getCurrentResourceStrings, onResourceChangeHandle } from 'igniteui-angular/core';
36
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
37

38
let NEXT_ID = 0;
3✔
39

40
/**
41
 * **Ignite UI for Angular Carousel** -
42
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel.html)
43
 *
44
 * The Ignite UI Carousel is used to browse or navigate through a collection of slides. Slides can contain custom
45
 * content such as images or cards and be used for things such as on-boarding tutorials or page-based interfaces.
46
 * It can be used as a separate fullscreen element or inside another component.
47
 *
48
 * Example:
49
 * ```html
50
 * <igx-carousel>
51
 *   <igx-slide>
52
 *     <h3>First Slide Header</h3>
53
 *     <p>First slide Content</p>
54
 *   <igx-slide>
55
 *   <igx-slide>
56
 *     <h3>Second Slide Header</h3>
57
 *     <p>Second Slide Content</p>
58
 * </igx-carousel>
59
 * ```
60
 */
61
@Component({
62
    selector: 'igx-carousel',
63
    templateUrl: 'carousel.component.html',
64
    styleUrl: 'carousel.component.css',
65
    encapsulation: ViewEncapsulation.None,
66
    changeDetection: ChangeDetectionStrategy.Eager,
67
    imports: [IgxButtonDirective, IgxIconComponent, NgClass, NgTemplateOutlet]
68
})
69
export class IgxCarouselComponent extends IgxCarouselComponentBase implements OnDestroy, AfterContentInit {
3✔
70
    private element = inject(ElementRef);
45✔
71
    private iterableDiffers = inject(IterableDiffers);
45✔
72
    private platformUtil = inject(PlatformUtil);
45✔
73

74

75

76
    /**
77
     * Sets the `id` of the carousel.
78
     * If not set, the `id` of the first carousel component will be `"igx-carousel-0"`.
79
     * ```html
80
     * <igx-carousel id="my-first-carousel"></igx-carousel>
81
     * ```
82
     *
83
     * @memberof IgxCarouselComponent
84
     */
85
    @HostBinding('attr.id')
86
    @Input()
87
    public id = `igx-carousel-${NEXT_ID++}`;
45✔
88
    /**
89
     * Returns the `role` attribute of the carousel.
90
     * ```typescript
91
     * let carouselRole =  this.carousel.role;
92
     * ```
93
     *
94
     * @memberof IgxCarouselComponent
95
     */
96
    @HostBinding('attr.role') public role = 'region';
45✔
97

98
    /** @hidden */
99
    @HostBinding('attr.aria-roledescription')
100
    public roleDescription = 'carousel';
45✔
101

102
    /** @hidden */
103
    @HostBinding('attr.aria-labelledby')
104
    public get labelId() {
105
        return this.showIndicatorsLabel ? `${this.id}-label` : null;
410✔
106
    }
107

108
    /** @hidden */
109
    @HostBinding('class.igx-carousel--vertical')
110
    public get isVertical(): boolean {
111
        return this.vertical;
814✔
112
    }
113

114
    /**
115
     * Returns the class of the carousel component.
116
     * ```typescript
117
     * let class =  this.carousel.cssClass;
118
     * ```
119
     *
120
     * @memberof IgxCarouselComponent
121
     */
122
    @HostBinding('class.igx-carousel')
123
    public cssClass = 'igx-carousel';
45✔
124

125
    /**
126
     * Gets the `touch-action` style of the `list item`.
127
     * ```typescript
128
     * let touchAction = this.listItem.touchAction;
129
     * ```
130
     */
131
    @HostBinding('style.touch-action')
132
    public get touchAction() {
133
        return this.gesturesSupport ? 'pan-y' : 'auto';
408✔
134
    }
135

136
    /**
137
     * Sets whether the carousel should `loop` back to the first slide after reaching the last slide.
138
     * Default value is `true`.
139
     * ```html
140
     * <igx-carousel [loop]="false"></igx-carousel>
141
     * ```
142
     *
143
     * @memberOf IgxCarouselComponent
144
     */
145
    @Input({ transform: booleanAttribute }) public loop = true;
45✔
146

147
    /**
148
     * Sets whether the carousel will `pause` the slide transitions on user interactions.
149
     * Default value is `true`.
150
     * ```html
151
     *  <igx-carousel [pause]="false"></igx-carousel>
152
     * ```
153
     *
154
     * @memberOf IgxCarouselComponent
155
     */
156
    @Input({ transform: booleanAttribute }) public pause = true;
45✔
157

158
    /**
159
     * Controls whether the carousel should render the left/right `navigation` buttons.
160
     * Default value is `true`.
161
     * ```html
162
     * <igx-carousel [navigation]="false"></igx-carousel>
163
     * ```
164
     *
165
     * @memberOf IgxCarouselComponent
166
     */
167
    @Input({ transform: booleanAttribute }) public navigation = true;
45✔
168

169
    /**
170
     * Controls whether the carousel should render the indicators.
171
     * Default value is `true`.
172
     * ```html
173
     * <igx-carousel [indicators]="false"></igx-carousel>
174
     * ```
175
     *
176
     * @memberOf IgxCarouselComponent
177
     */
178
    @Input({ transform: booleanAttribute }) public indicators = true;
45✔
179

180

181
    /**
182
     * Controls whether the carousel has vertical alignment.
183
     * Default value is `false`.
184
     * ```html
185
     * <igx-carousel [vertical]="true"></igx-carousel>
186
     * ```
187
     *
188
     * @memberOf IgxCarouselComponent
189
     */
190
    @Input({ transform: booleanAttribute }) public override vertical = false;
45✔
191

192
    /**
193
     * Controls whether the carousel should support gestures.
194
     * Default value is `true`.
195
     * ```html
196
     * <igx-carousel [gesturesSupport]="false"></igx-carousel>
197
     * ```
198
     *
199
     * @memberOf IgxCarouselComponent
200
     */
201
    @Input({ transform: booleanAttribute }) public gesturesSupport = true;
45✔
202

203
    /**
204
     * Controls the maximum indexes that can be shown.
205
     * Default value is `10`.
206
     * ```html
207
     * <igx-carousel [maximumIndicatorsCount]="5"></igx-carousel>
208
     * ```
209
     *
210
     * @memberOf IgxCarouselComponent
211
     */
212
    @Input() public maximumIndicatorsCount = 10;
45✔
213

214
    /**
215
     * Gets/sets the display mode of carousel indicators. It can be `start` or `end`.
216
     * Default value is `end`.
217
     * ```html
218
     * <igx-carousel indicatorsOrientation="start">
219
     * <igx-carousel>
220
     * ```
221
     *
222
     * @memberOf IgxCarouselComponent
223
     */
224
    @Input() public indicatorsOrientation: CarouselIndicatorsOrientation = CarouselIndicatorsOrientation.end;
45✔
225

226
    /**
227
     * Gets/sets the animation type of carousel.
228
     * Default value is `slide`.
229
     * ```html
230
     * <igx-carousel animationType="none">
231
     * <igx-carousel>
232
     * ```
233
     *
234
     * @memberOf IgxCarouselComponent
235
     */
236
    @Input() public override animationType: CarouselAnimationType = CarouselAnimationType.slide;
45✔
237

238
    /**
239
     * The custom template, if any, that should be used when rendering carousel indicators
240
     *
241
     * ```typescript
242
     * // Set in typescript
243
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
244
     * myComponent.carousel.indicatorTemplate = myCustomTemplate;
245
     * ```
246
     * ```html
247
     * <!-- Set in markup -->
248
     *  <igx-carousel #carousel>
249
     *      ...
250
     *      <ng-template igxCarouselIndicator let-slide>
251
     *         <igx-icon *ngIf="slide.active">brightness_7</igx-icon>
252
     *         <igx-icon *ngIf="!slide.active">brightness_5</igx-icon>
253
     *      </ng-template>
254
     *  </igx-carousel>
255
     * ```
256
     */
257
    @ContentChild(IgxCarouselIndicatorDirective, { read: TemplateRef, static: false })
258
    public indicatorTemplate: TemplateRef<any> = null;
45✔
259

260
    /**
261
     * The custom template, if any, that should be used when rendering carousel next button
262
     *
263
     * ```typescript
264
     * // Set in typescript
265
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
266
     * myComponent.carousel.nextButtonTemplate = myCustomTemplate;
267
     * ```
268
     * ```html
269
     * <!-- Set in markup -->
270
     *  <igx-carousel #carousel>
271
     *      ...
272
     *      <ng-template igxCarouselNextButton let-disabled>
273
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
274
     *              <igx-icon name="add"></igx-icon>
275
     *          </button>
276
     *      </ng-template>
277
     *  </igx-carousel>
278
     * ```
279
     */
280
    @ContentChild(IgxCarouselNextButtonDirective, { read: TemplateRef, static: false })
281
    public nextButtonTemplate: TemplateRef<any> = null;
45✔
282

283
    /**
284
     * The custom template, if any, that should be used when rendering carousel previous button
285
     *
286
     * ```typescript
287
     * // Set in typescript
288
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
289
     * myComponent.carousel.prevButtonTemplate = myCustomTemplate;
290
     * ```
291
     * ```html
292
     * <!-- Set in markup -->
293
     *  <igx-carousel #carousel>
294
     *      ...
295
     *      <ng-template igxCarouselPrevButton let-disabled>
296
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
297
     *              <igx-icon name="remove"></igx-icon>
298
     *          </button>
299
     *      </ng-template>
300
     *  </igx-carousel>
301
     * ```
302
     */
303
    @ContentChild(IgxCarouselPrevButtonDirective, { read: TemplateRef, static: false })
304
    public prevButtonTemplate: TemplateRef<any> = null;
45✔
305

306
    /**
307
     * The collection of `slides` currently in the carousel.
308
     * ```typescript
309
     * let slides: QueryList<IgxSlideComponent> = this.carousel.slides;
310
     * ```
311
     *
312
     * @memberOf IgxCarouselComponent
313
     */
314
    @ContentChildren(IgxSlideComponent)
315
    public slides: QueryList<IgxSlideComponent>;
316

317
    /**
318
     * An event that is emitted after a slide transition has happened.
319
     * Provides references to the carousel and slide components as event arguments.
320
     * ```html
321
     * <igx-carousel (slideChanged)="slideChanged($event)"></igx-carousel>
322
     * ```
323
     *
324
     * @memberOf IgxCarouselComponent
325
     */
326
    @Output() public slideChanged = new EventEmitter<ISlideEventArgs>();
45✔
327

328
    /**
329
     * An event that is emitted after a slide has been added to the carousel.
330
     * Provides references to the carousel and slide components as event arguments.
331
     * ```html
332
     * <igx-carousel (slideAdded)="slideAdded($event)"></igx-carousel>
333
     * ```
334
     *
335
     * @memberOf IgxCarouselComponent
336
     */
337
    @Output() public slideAdded = new EventEmitter<ISlideEventArgs>();
45✔
338

339
    /**
340
     * An event that is emitted after a slide has been removed from the carousel.
341
     * Provides references to the carousel and slide components as event arguments.
342
     * ```html
343
     * <igx-carousel (slideRemoved)="slideRemoved($event)"></igx-carousel>
344
     * ```
345
     *
346
     * @memberOf IgxCarouselComponent
347
     */
348
    @Output() public slideRemoved = new EventEmitter<ISlideEventArgs>();
45✔
349

350
    /**
351
     * An event that is emitted after the carousel has been paused.
352
     * Provides a reference to the carousel as an event argument.
353
     * ```html
354
     * <igx-carousel (carouselPaused)="carouselPaused($event)"></igx-carousel>
355
     * ```
356
     *
357
     * @memberOf IgxCarouselComponent
358
     */
359
    @Output() public carouselPaused = new EventEmitter<IgxCarouselComponent>();
45✔
360

361
    /**
362
     * An event that is emitted after the carousel has resumed transitioning between `slides`.
363
     * Provides a reference to the carousel as an event argument.
364
     * ```html
365
     * <igx-carousel (carouselPlaying)="carouselPlaying($event)"></igx-carousel>
366
     * ```
367
     *
368
     * @memberOf IgxCarouselComponent
369
     */
370
    @Output() public carouselPlaying = new EventEmitter<IgxCarouselComponent>();
45✔
371

372
    @ViewChild('defaultIndicator', { read: TemplateRef, static: true })
373
    private defaultIndicator: TemplateRef<any>;
374

375
    @ViewChild('defaultNextButton', { read: TemplateRef, static: true })
376
    private defaultNextButton: TemplateRef<any>;
377

378
    @ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
379
    private defaultPrevButton: TemplateRef<any>;
380

381
    @ViewChildren('indicators', { read: ElementRef })
382
    private _indicators: QueryList<ElementRef<HTMLDivElement>>;
383

384
    /**
385
     * @hidden
386
     * @internal
387
     */
388
    public stoppedByInteraction: boolean;
389
    protected override currentItem: IgxSlideComponent;
390
    protected override previousItem: IgxSlideComponent;
391
    private _interval: number;
392
    private _resourceStrings: ICarouselResourceStrings = null;
45✔
393
    private _defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN);
45✔
394
    private lastInterval: any;
395
    private playing: boolean;
396
    private destroyed: boolean;
397
    private destroy$ = new Subject<any>();
45✔
398
    private differ: IterableDiffer<IgxSlideComponent> | null = null;
45✔
399
    private incomingSlide: IgxSlideComponent;
400
    private _hasKeyboardFocusOnIndicators = false;
45✔
401

402
    /**
403
     * An accessor that sets the resource strings.
404
     * By default it uses EN resources.
405
     */
406
    @Input()
407
    public set resourceStrings(value: ICarouselResourceStrings) {
408
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
409
    }
410

411
    /**
412
     * An accessor that returns the resource strings.
413
     */
414
    public get resourceStrings(): ICarouselResourceStrings {
415
        return this._resourceStrings || this._defaultResourceStrings;
3,984✔
416
    }
417

418
    /** @hidden */
419
    public get getIndicatorTemplate(): TemplateRef<any> {
420
        if (this.indicatorTemplate) {
1,589✔
421
            return this.indicatorTemplate;
24✔
422
        }
423
        return this.defaultIndicator;
1,565✔
424
    }
425

426
    /** @hidden */
427
    public get getNextButtonTemplate(): TemplateRef<any> {
428
        if (this.nextButtonTemplate) {
402✔
429
            return this.nextButtonTemplate;
6✔
430
        }
431

432
        return this.defaultNextButton
396✔
433
    }
434

435
    /** @hidden */
436
    public get getPrevButtonTemplate(): TemplateRef<any> {
437
        if (this.prevButtonTemplate) {
402✔
438
            return this.prevButtonTemplate;
6✔
439
        }
440

441
        return this.defaultPrevButton
396✔
442
    }
443

444
    /** @hidden */
445
    public get indicatorsClass() {
446
        return {
404✔
447
            'igx-carousel-indicators': true,
448
            ['igx-carousel-indicators--focused']: this._hasKeyboardFocusOnIndicators,
449
            [`igx-carousel-indicators--${this.getIndicatorsClass()}`]: true,
450
            'igx-carousel-indicators--vertical': this.isVertical
451
        };
452
    }
453

454
    /** @hidden */
455
    public get showIndicators(): boolean {
456
        return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
408✔
457
    }
458

459
    /** @hidden */
460
    public get showIndicatorsLabel(): boolean {
461
        return this.indicators && this.total > this.maximumIndicatorsCount;
818✔
462
    }
463

464
    /** @hidden */
465
    public get getCarouselLabel() {
466
        return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
2✔
467
    }
468

469
    /**
470
     * Returns the total number of `slides` in the carousel.
471
     * ```typescript
472
     * let slideCount =  this.carousel.total;
473
     * ```
474
     *
475
     * @memberOf IgxCarouselComponent
476
     */
477
    public get total(): number {
478
        return this.slides?.length;
10,538✔
479
    }
480

481
    /**
482
     * The index of the slide being currently shown.
483
     * ```typescript
484
     * let currentSlideNumber =  this.carousel.current;
485
     * ```
486
     *
487
     * @memberOf IgxCarouselComponent
488
     */
489
    public get current(): number {
490
        return !this.currentItem ? 0 : this.currentItem.index;
3,020✔
491
    }
492

493
    /**
494
     * Returns a boolean indicating if the carousel is playing.
495
     * ```typescript
496
     * let isPlaying =  this.carousel.isPlaying;
497
     * ```
498
     *
499
     * @memberOf IgxCarouselComponent
500
     */
501
    public get isPlaying(): boolean {
502
        return this.playing;
36✔
503
    }
504

505
    /**
506
     * Returns а boolean indicating if the carousel is destroyed.
507
     * ```typescript
508
     * let isDestroyed =  this.carousel.isDestroyed;
509
     * ```
510
     *
511
     * @memberOf IgxCarouselComponent
512
     */
513
    public get isDestroyed(): boolean {
514
        return this.destroyed;
1✔
515
    }
516
    /**
517
     * Returns a reference to the carousel element in the DOM.
518
     * ```typescript
519
     * let nativeElement =  this.carousel.nativeElement;
520
     * ```
521
     *
522
     * @memberof IgxCarouselComponent
523
     */
524
    public get nativeElement(): any {
525
        return this.element.nativeElement;
27✔
526
    }
527

528
    /**
529
     * Returns the time `interval` in milliseconds before the slide changes.
530
     * ```typescript
531
     * let timeInterval = this.carousel.interval;
532
     * ```
533
     *
534
     * @memberof IgxCarouselComponent
535
     */
536
    @Input()
537
    public get interval(): number {
538
        return this._interval;
3,444✔
539
    }
540

541
    /**
542
     * Sets the time `interval` in milliseconds before the slide changes.
543
     * If not set, the carousel will not change `slides` automatically.
544
     * ```html
545
     * <igx-carousel [interval]="1000"></igx-carousel>
546
     * ```
547
     *
548
     * @memberof IgxCarouselComponent
549
     */
550
    public set interval(value: number) {
551
        this._interval = +value;
29✔
552
        this.restartInterval();
29✔
553
    }
554

555
    constructor() {
556
        super();
45✔
557
        this.differ = this.iterableDiffers.find([]).create(null);
45✔
558
        onResourceChangeHandle(this.destroy$, () => {
45✔
559
            this._defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN, false);
×
560
        }, this);
561
    }
562

563
    /** @hidden */
564
    public onTap(event) {
565
        // Play/pause only when the tap lands on a slide (or its content),
566
        // not on the navigation buttons or indicators.
567
        const slide = (event.target as Element)?.closest?.('.igx-slide');
6✔
568

569
        if (slide) {
6✔
570
            if (this.isPlaying) {
6✔
571
                if (this.pause) {
2✔
572
                    this.stoppedByInteraction = true;
2✔
573
                }
574
                this.stop();
2✔
575
            } else if (this.stoppedByInteraction) {
4✔
576
                this.play();
2✔
577
            }
578
        }
579
    }
580

581
    /** @hidden */
582
    @HostListener('mouseenter')
583
    public onMouseEnter() {
584
        if (this.pause && this.isPlaying) {
2✔
585
            this.stoppedByInteraction = true;
1✔
586
        }
587
        this.stop();
2✔
588
    }
589

590
    /** @hidden */
591
    @HostListener('mouseleave')
592
    public onMouseLeave() {
593
        if (this.stoppedByInteraction) {
2✔
594
            this.play();
1✔
595
        }
596
    }
597

598
    /** @hidden */
599
    public onPanLeft(event) {
600
        if (!this.vertical) {
7✔
601
            this.pan(event);
5✔
602
        }
603
    }
604

605
    /** @hidden */
606
    public onPanRight(event) {
607
        if (!this.vertical) {
7✔
608
            this.pan(event);
5✔
609
        }
610
    }
611

612
    /** @hidden */
613
    public onPanUp(event) {
614
        if (this.vertical) {
5✔
615
            this.pan(event);
3✔
616
        }
617
    }
618

619
    /** @hidden */
620
    public onPanDown(event) {
621
        if (this.vertical) {
5✔
622
            this.pan(event);
3✔
623
        }
624
    }
625

626
    /**
627
     * @hidden
628
     */
629
    public onPanEnd(event) {
630
        if (!this.gesturesSupport) {
24✔
631
            return;
2✔
632
        }
633
        event.preventDefault();
22✔
634

635
        const slideSize = this.vertical
22✔
636
            ? this.currentItem.nativeElement.offsetHeight
637
            : this.currentItem.nativeElement.offsetWidth;
638
        const panOffset = (slideSize / 1000);
22✔
639
        const eventDelta = this.vertical ? event.deltaY : event.deltaX;
22✔
640
        const delta = Math.abs(eventDelta) + panOffset < slideSize ? Math.abs(eventDelta) : slideSize - panOffset;
22!
641
        const velocity = Math.abs(event.velocity);
22✔
642
        this.resetSlideStyles(this.currentItem);
22✔
643
        if (this.incomingSlide) {
22✔
644
            this.resetSlideStyles(this.incomingSlide);
12✔
645
            if (slideSize / 2 < delta || velocity > 1) {
12✔
646
                this.incomingSlide.direction = eventDelta < 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
8✔
647
                this.incomingSlide.previous = false;
8✔
648

649
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
8!
650
                    delta / slideSize : (slideSize - delta) / slideSize;
651

652
                if (velocity > 1) {
8✔
653
                    this.newDuration = this.defaultAnimationDuration / velocity;
4✔
654
                }
655
                this.incomingSlide.active = true;
8✔
656
            } else {
657
                this.currentItem.direction = eventDelta > 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
4✔
658
                this.previousItem = this.incomingSlide;
4✔
659
                this.previousItem.previous = true;
4✔
660
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
4!
661
                    Math.abs((slideSize - delta) / slideSize) : delta / slideSize;
662
                this.playAnimations();
4✔
663
            }
664
        }
665

666
        if (this.stoppedByInteraction) {
22!
667
            this.play();
×
668
        }
669
    }
670

671
    /** @hidden */
672
    public ngAfterContentInit() {
673
        this.slides.changes
45✔
674
            .pipe(takeUntil(this.destroy$))
675
            .subscribe((change: QueryList<IgxSlideComponent>) => this.initSlides(change));
14✔
676

677
        this.initSlides(this.slides);
45✔
678
        this.registerGestureEvents();
45✔
679
    }
680

681
    /** @hidden */
682
    public override ngOnDestroy() {
683
        super.ngOnDestroy();
47✔
684
        this.destroy$.next(true);
47✔
685
        this.destroy$.complete();
47✔
686
        this.destroyed = true;
47✔
687
        if (this.lastInterval) {
47✔
688
            clearInterval(this.lastInterval);
25✔
689
        }
690
        this._gestures?.destroy();
47✔
691
    }
692

693
    /** @hidden */
694
    public handleKeydownPrev(event: KeyboardEvent): void {
695
        if (this.platformUtil.isActivationKey(event)) {
2✔
696
            event.preventDefault();
2✔
697
            this.prev();
2✔
698
        }
699
    }
700

701
    /** @hidden */
702
    public handleKeydownNext(event: KeyboardEvent): void {
703
        if (this.platformUtil.isActivationKey(event)) {
2✔
704
            event.preventDefault();
2✔
705
            this.next();
2✔
706
        }
707
    }
708

709
    /** @hidden */
710
    public handleKeyUp(event: KeyboardEvent): void {
711
        if (event.key === this.platformUtil.KEYMAP.TAB) {
6✔
712
            this._hasKeyboardFocusOnIndicators = true;
6✔
713
        }
714
    }
715

716
    /** @hidden */
717
    public handleFocusOut(event: FocusEvent): void {
718
        const target = event.relatedTarget as HTMLElement;
14✔
719

720
        if (!target || !target.classList.contains('igx-carousel-indicators__indicator')) {
14✔
721
            this._hasKeyboardFocusOnIndicators = false;
1✔
722
        }
723
    }
724

725
    /** @hidden */
726
    public handleClick(): void {
727
        this._hasKeyboardFocusOnIndicators = false;
1✔
728
    }
729

730
    /** @hidden */
731
    public handleKeydown(event: KeyboardEvent): void {
732
        const { key } = event;
16✔
733
        const slides = this.slides.toArray();
16✔
734
        const isRTL = !isLeftToRight(this.nativeElement);
16✔
735

736
        switch (key) {
16✔
737
            case this.platformUtil.KEYMAP.ARROW_LEFT:
738
                isRTL ? this.next() : this.prev();
4✔
739
                break;
4✔
740
            case this.platformUtil.KEYMAP.ARROW_RIGHT:
741
                isRTL ? this.prev() : this.next();
6✔
742
                break;
6✔
743
            case this.platformUtil.KEYMAP.HOME:
744
                event.preventDefault();
3✔
745
                this.select(isRTL ? last(slides) : first(slides));
3✔
746
                break;
3✔
747
            case this.platformUtil.KEYMAP.END:
748
                event.preventDefault();
3✔
749
                this.select(isRTL ? first(slides) : last(slides));
3✔
750
                break;
3✔
751
        }
752

753
        this.indicatorsElements[this.current].nativeElement.focus();
16✔
754
    }
755

756
    /**
757
     * Returns the slide corresponding to the provided `index` or null.
758
     * ```typescript
759
     * let slide1 =  this.carousel.get(1);
760
     * ```
761
     *
762
     * @memberOf IgxCarouselComponent
763
     */
764
    public get(index: number): IgxSlideComponent {
765
        return this.slides.find((slide) => slide.index === index);
6,428✔
766
    }
767

768
    /**
769
     * Adds a new slide to the carousel.
770
     * ```typescript
771
     * this.carousel.add(newSlide);
772
     * ```
773
     *
774
     * @memberOf IgxCarouselComponent
775
     */
776
    public add(slide: IgxSlideComponent) {
777
        const newSlides = this.slides.toArray();
3✔
778
        newSlides.push(slide);
3✔
779
        this.slides.reset(newSlides);
3✔
780
        this.slides.notifyOnChanges();
3✔
781
    }
782

783
    /**
784
     * Removes a slide from the carousel.
785
     * ```typescript
786
     * this.carousel.remove(slide);
787
     * ```
788
     *
789
     * @memberOf IgxCarouselComponent
790
     */
791
    public remove(slide: IgxSlideComponent) {
792
        if (slide && slide === this.get(slide.index)) { // check if the requested slide for delete is present in the carousel
4✔
793
            const newSlides = this.slides.toArray();
4✔
794
            newSlides.splice(slide.index, 1);
4✔
795
            this.slides.reset(newSlides);
4✔
796
            this.slides.notifyOnChanges();
4✔
797
        }
798
    }
799

800
    /**
801
     * Switches to the passed-in slide with a given `direction`.
802
     * ```typescript
803
     * const slide = this.carousel.get(2);
804
     * this.carousel.select(slide, CarouselAnimationDirection.NEXT);
805
     * ```
806
     *
807
     * @memberOf IgxCarouselComponent
808
     */
809
    public select(slide: IgxSlideComponent, direction?: CarouselAnimationDirection): void;
810
    /**
811
     * Switches to slide by index with a given `direction`.
812
     * ```typescript
813
     * this.carousel.select(2, CarouselAnimationDirection.NEXT);
814
     * ```
815
     *
816
     * @memberOf IgxCarouselComponent
817
     */
818
    public select(index: number, direction?: CarouselAnimationDirection): void;
819
    public select(slideOrIndex: IgxSlideComponent | number, direction: CarouselAnimationDirection = CarouselAnimationDirection.NONE): void {
16✔
820
        const slide = typeof slideOrIndex === 'number'
2,753✔
821
            ? this.get(slideOrIndex)
822
            : slideOrIndex;
823

824
        if (slide && slide !== this.currentItem) {
2,753✔
825
            slide.direction = direction;
2,750✔
826
            slide.active = true;
2,750✔
827
        }
828
    }
829

830
    /**
831
     * Transitions to the next slide in the carousel.
832
     * ```typescript
833
     * this.carousel.next();
834
     * ```
835
     *
836
     * @memberOf IgxCarouselComponent
837
     */
838
    public next() {
839
        const index = this.getNextIndex();
2,726✔
840

841
        if (index === 0 && !this.loop) {
2,726✔
842
            this.stop();
1✔
843
            return;
1✔
844
        }
845
        return this.select(this.get(index), CarouselAnimationDirection.NEXT);
2,725✔
846
    }
847

848
    /**
849
     * Transitions to the previous slide in the carousel.
850
     * ```typescript
851
     * this.carousel.prev();
852
     * ```
853
     *
854
     * @memberOf IgxCarouselComponent
855
     */
856
    public prev() {
857
        const index = this.getPrevIndex();
13✔
858

859
        if (!this.loop && index === this.total - 1) {
13✔
860
            this.stop();
1✔
861
            return;
1✔
862
        }
863
        return this.select(this.get(index), CarouselAnimationDirection.PREV);
12✔
864
    }
865

866
    /**
867
     * Resumes playing of the carousel if in paused state.
868
     * No operation otherwise.
869
     * ```typescript
870
     * this.carousel.play();
871
     * }
872
     * ```
873
     *
874
     * @memberOf IgxCarouselComponent
875
     */
876
    public play() {
877
        if (!this.playing) {
66✔
878
            this.playing = true;
50✔
879
            this.carouselPlaying.emit(this);
50✔
880
            this.restartInterval();
50✔
881
            this.stoppedByInteraction = false;
50✔
882
        }
883
    }
884

885
    /**
886
     * Stops slide transitions if the `pause` option is set to `true`.
887
     * No operation otherwise.
888
     * ```typescript
889
     *  this.carousel.stop();
890
     * }
891
     * ```
892
     *
893
     * @memberOf IgxCarouselComponent
894
     */
895
    public stop() {
896
        if (this.pause) {
10✔
897
            this.playing = false;
10✔
898
            this.carouselPaused.emit(this);
10✔
899
            this.resetInterval();
10✔
900
        }
901
    }
902

903
    protected getPreviousElement(): HTMLElement {
904
        return this.previousItem.nativeElement;
3✔
905
    }
906

907
    protected getCurrentElement(): HTMLElement {
908
        return this.currentItem.nativeElement;
4✔
909
    }
910

911
    private _gestures: IgxTouchManager | null = null;
45✔
912

913
    private registerGestureEvents() {
914
        if (!this.gesturesSupport || !this.platformUtil.isBrowser) {
45!
915
            return;
×
916
        }
917

918
        this._gestures = new IgxTouchManager(this.element.nativeElement, {
45✔
919
            tap: (event) => this.onTap(event),
×
920
            panMove: (event) => this.onPan(event),
×
921
            panEnd: (event) => this.onPanEnd(event)
×
922
        }, { tapThreshold: 5 });
923
    }
924

925
    /**
926
     * Routes a pan gesture to the orientation-specific handler so that only
927
     * gestures matching the carousel's axis affect the active slide.
928
     *
929
     * @hidden
930
     */
931
    private onPan(event) {
932
        if (Math.abs(event.deltaX) >= Math.abs(event.deltaY)) {
×
933
            if (event.deltaX < 0) {
×
934
                this.onPanLeft(event);
×
935
            } else {
936
                this.onPanRight(event);
×
937
            }
938
        } else {
939
            if (event.deltaY < 0) {
×
940
                this.onPanUp(event);
×
941
            } else {
942
                this.onPanDown(event);
×
943
            }
944
        }
945
    }
946

947
    private resetInterval() {
948
        if (this.lastInterval) {
143✔
949
            clearInterval(this.lastInterval);
70✔
950
            this.lastInterval = null;
70✔
951
        }
952
    }
953

954
    private restartInterval() {
955
        this.resetInterval();
133✔
956

957
        if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
133✔
958
            this.lastInterval = setInterval(() => {
97✔
959
                const tick = +this.interval;
2,707✔
960
                if (this.playing && this.total && !isNaN(tick) && tick > 0) {
2,707!
961
                    this.next();
2,707✔
962
                } else {
963
                    this.stop();
×
964
                }
965
            }, this.interval);
966
        }
967
    }
968

969
    /** @hidden */
970
    public get nextButtonDisabled() {
971
        return !this.loop && this.current === (this.total - 1);
804✔
972
    }
973

974
    /** @hidden */
975
    public get prevButtonDisabled() {
976
        return !this.loop && this.current === 0;
804✔
977
    }
978

979
    private get indicatorsElements() {
980
        return this._indicators.toArray();
16✔
981
    }
982

983
    private getIndicatorsClass(): string {
984
        switch (this.indicatorsOrientation) {
404!
985
            case CarouselIndicatorsOrientation.top:
986
                return CarouselIndicatorsOrientation.start;
×
987
            case CarouselIndicatorsOrientation.bottom:
988
                return CarouselIndicatorsOrientation.end;
×
989
            default:
990
                return this.indicatorsOrientation;
404✔
991
        }
992
    }
993

994
    private getNextIndex(): number {
995
        return (this.current + 1) % this.total;
2,734✔
996
    }
997

998
    private getPrevIndex(): number {
999
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
21✔
1000
    }
1001

1002
    private resetSlideStyles(slide: IgxSlideComponent) {
1003
        slide.nativeElement.style.transform = '';
38✔
1004
        slide.nativeElement.style.opacity = '';
38✔
1005
    }
1006

1007
    private pan(event) {
1008
        const slideSize = this.vertical
16✔
1009
            ? this.currentItem.nativeElement.offsetHeight
1010
            : this.currentItem.nativeElement.offsetWidth;
1011
        const panOffset = (slideSize / 1000);
16✔
1012
        const delta = this.vertical ? event.deltaY : event.deltaX;
16✔
1013
        const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
16✔
1014
        const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
16✔
1015

1016
        if (!this.gesturesSupport || Math.abs(delta) + panOffset >= slideSize) {
16✔
1017
            return;
2✔
1018
        }
1019

1020
        if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
14✔
1021
            this.incomingSlide = null;
2✔
1022
            return;
2✔
1023
        }
1024

1025
        event.preventDefault();
12✔
1026
        if (this.isPlaying) {
12!
1027
            this.stoppedByInteraction = true;
×
1028
            this.stop();
×
1029
        }
1030

1031
        if (this.previousItem && this.previousItem.previous) {
12✔
1032
            this.previousItem.previous = false;
4✔
1033
        }
1034
        this.finishAnimations();
12✔
1035

1036
        if (this.incomingSlide) {
12✔
1037
            if (index !== this.incomingSlide.index) {
8✔
1038
                this.resetSlideStyles(this.incomingSlide);
4✔
1039
                this.incomingSlide.previous = false;
4✔
1040
                this.incomingSlide = this.get(index);
4✔
1041
            }
1042
        } else {
1043
            this.incomingSlide = this.get(index);
4✔
1044
        }
1045
        this.incomingSlide.previous = true;
12✔
1046

1047
        if (this.animationType === CarouselAnimationType.fade) {
12!
1048
            this.currentItem.nativeElement.style.opacity = `${Math.abs(offset) / slideSize}`;
×
1049
        } else {
1050
            this.currentItem.nativeElement.style.transform = this.vertical
12✔
1051
                ? `translateY(${delta}px)`
1052
                : `translateX(${delta}px)`;
1053
            this.incomingSlide.nativeElement.style.transform = this.vertical
12✔
1054
                ? `translateY(${offset}px)`
1055
                : `translateX(${offset}px)`;
1056
        }
1057
    }
1058

1059
    private unsubscriber(slide: IgxSlideComponent) {
1060
        return merge(this.destroy$, slide.isDestroyed);
190✔
1061
    }
1062

1063
    private onSlideActivated(slide: IgxSlideComponent) {
1064
        if (slide.active && slide !== this.currentItem) {
105✔
1065
            if (slide.direction === CarouselAnimationDirection.NONE) {
54✔
1066
                const newIndex = slide.index;
13✔
1067
                slide.direction = newIndex > this.current ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
13✔
1068
            }
1069

1070
            if (this.currentItem) {
54✔
1071
                if (this.previousItem && this.previousItem.previous) {
42!
1072
                    this.previousItem.previous = false;
×
1073
                }
1074
                this.currentItem.direction = slide.direction;
42✔
1075
                this.currentItem.active = false;
42✔
1076

1077
                this.previousItem = this.currentItem;
42✔
1078
                this.currentItem = slide;
42✔
1079
                this.triggerAnimations();
42✔
1080
            } else {
1081
                this.currentItem = slide;
12✔
1082
            }
1083
            this.slideChanged.emit({ carousel: this, slide });
54✔
1084
            this.restartInterval();
54✔
1085
            this.cdr.markForCheck();
54✔
1086
        }
1087
    }
1088

1089

1090
    private finishAnimations() {
1091
        if (this.animationStarted(this.leaveAnimationPlayer)) {
12!
1092
            this.leaveAnimationPlayer.finish();
×
1093
        }
1094

1095
        if (this.animationStarted(this.enterAnimationPlayer)) {
12!
1096
            this.enterAnimationPlayer.finish();
×
1097
        }
1098
    }
1099

1100
    private initSlides(change: QueryList<IgxSlideComponent>) {
1101
        const diff = this.differ.diff(change.toArray());
59✔
1102
        if (diff) {
59✔
1103
            this.slides.reduce((_any, c, ind) => c.index = ind, 0); // reset slides indexes
225✔
1104
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
59✔
1105
                const slide = record.item;
190✔
1106
                slide.total = this.total;
190✔
1107
                this.slideAdded.emit({ carousel: this, slide });
190✔
1108
                if (slide.active) {
190✔
1109
                    this.currentItem = slide;
18✔
1110
                }
1111
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
190✔
1112
            });
1113

1114
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
59✔
1115
                const slide = record.item;
14✔
1116
                this.slideRemoved.emit({ carousel: this, slide });
14✔
1117
                if (slide.active) {
14✔
1118
                    slide.active = false;
4✔
1119
                    if (this.currentItem === slide) { // Only fall back if nothing better was found.
4✔
1120
                        this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
4✔
1121
                    }
1122
                }
1123
            });
1124

1125
            this.updateSlidesSelection();
59✔
1126
        }
1127
    }
1128

1129
    private updateSlidesSelection() {
1130
        if (this.platformUtil.isBrowser) {
59✔
1131
            requestAnimationFrame(() => {
59✔
1132
                if (this.currentItem) {
59✔
1133
                    this.currentItem.active = true;
34✔
1134
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
133✔
1135
                    activeSlides.forEach(slide => slide.active = false);
34✔
1136
                } else if (this.total) {
25✔
1137
                    this.slides.first.active = true;
24✔
1138
                }
1139
                this.play();
59✔
1140
                this.cdr.markForCheck();
59✔
1141
            });
1142
        }
1143
    }
1144
}
1145

1146
export interface ISlideEventArgs extends IBaseEventArgs {
1147
    carousel: IgxCarouselComponent;
1148
    slide: IgxSlideComponent;
1149
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc