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

IgniteUI / igniteui-angular / 29572405564

17 Jul 2026 10:07AM UTC coverage: 90.116% (-0.02%) from 90.135%
29572405564

Pull #15125

github

web-flow
Merge 637b7229e into 8ebabbb38
Pull Request #15125: refactor(*): bundle styles with components

14920 of 17397 branches covered (85.76%)

Branch coverage included in aggregate %.

30030 of 32483 relevant lines covered (92.45%)

34495.14 hits per line

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

94.25
/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, 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 { HammerGesturesManager } from 'igniteui-angular/core';
37
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
38

39
let NEXT_ID = 0;
3✔
40

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

77

78

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

101
    /** @hidden */
102
    @HostBinding('attr.aria-roledescription')
103
    public roleDescription = 'carousel';
43✔
104

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

111
    /** @hidden */
112
    @HostBinding('class.igx-carousel--vertical')
113
        public get isVertical(): boolean {
114
                return this.vertical;
778✔
115
        }
116

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

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

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

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

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

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

183

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

381
    @ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
382
    private defaultPrevButton: TemplateRef<any>;
383

384
    @ViewChildren('indicators', { read: ElementRef })
385
    private _indicators: QueryList<ElementRef<HTMLDivElement>>;
386

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

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

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

421
    /** @hidden */
422
    public get getIndicatorTemplate(): TemplateRef<any> {
423
        if (this.indicatorTemplate) {
1,522✔
424
            return this.indicatorTemplate;
24✔
425
        }
426
        return this.defaultIndicator;
1,498✔
427
    }
428

429
    /** @hidden */
430
    public get getNextButtonTemplate(): TemplateRef<any> {
431
        if (this.nextButtonTemplate) {
384✔
432
            return this.nextButtonTemplate;
6✔
433
        }
434

435
        return this.defaultNextButton
378✔
436
    }
437

438
    /** @hidden */
439
    public get getPrevButtonTemplate(): TemplateRef<any> {
440
        if (this.prevButtonTemplate) {
384✔
441
            return this.prevButtonTemplate;
6✔
442
        }
443

444
        return this.defaultPrevButton
378✔
445
    }
446

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

457
    /** @hidden */
458
    public get showIndicators(): boolean {
459
        return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
390✔
460
    }
461

462
    /** @hidden */
463
    public get showIndicatorsLabel(): boolean {
464
        return this.indicators && this.total > this.maximumIndicatorsCount;
782✔
465
    }
466

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

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

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

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

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

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

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

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

566
    /** @hidden */
567
    public onTap(event) {
568
        // play pause only when tap on slide
569
        if (event.target && event.target.classList.contains('igx-slide')) {
4✔
570
            if (this.isPlaying) {
4✔
571
                if (this.pause) {
1✔
572
                    this.stoppedByInteraction = true;
1✔
573
                }
574
                this.stop();
1✔
575
            } else if (this.stoppedByInteraction) {
3✔
576
                this.play();
1✔
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
43✔
674
            .pipe(takeUntil(this.destroy$))
675
            .subscribe((change: QueryList<IgxSlideComponent>) => this.initSlides(change));
13✔
676

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

681
    /** @hidden */
682
    public override ngOnDestroy() {
683
        super.ngOnDestroy();
45✔
684
        this.destroy$.next(true);
45✔
685
        this.destroy$.complete();
45✔
686
        this.destroyed = true;
45✔
687
        if (this.lastInterval) {
45✔
688
            clearInterval(this.lastInterval);
24✔
689
        }
690
        this.touchManager.destroy();
45✔
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);
8,314✔
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'
3,600✔
821
            ? this.get(slideOrIndex)
822
            : slideOrIndex;
823

824
        if (slide && slide !== this.currentItem) {
3,600✔
825
            slide.direction = direction;
3,597✔
826
            slide.active = true;
3,597✔
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();
3,573✔
840

841
        if (index === 0 && !this.loop) {
3,573✔
842
            this.stop();
1✔
843
            return;
1✔
844
        }
845
        return this.select(this.get(index), CarouselAnimationDirection.NEXT);
3,572✔
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) {
61✔
878
            this.playing = true;
47✔
879
            this.carouselPlaying.emit(this);
47✔
880
            this.restartInterval();
47✔
881
            this.stoppedByInteraction = false;
47✔
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) {
9✔
897
            this.playing = false;
9✔
898
            this.carouselPaused.emit(this);
9✔
899
            this.resetInterval();
9✔
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 registerGestureEvents() {
912
        if (!this.gesturesSupport || !this.platformUtil.isBrowser) {
43!
913
            return;
×
914
        }
915
        const el = this.element.nativeElement;
43✔
916
        this.touchManager.addEventListener(el, 'tap', (e) => this.onTap(e));
43✔
917
        this.touchManager.addEventListener(el, 'panleft', (e) => this.onPanLeft(e));
43✔
918
        this.touchManager.addEventListener(el, 'panright', (e) => this.onPanRight(e));
43✔
919
        this.touchManager.addEventListener(el, 'panup', (e) => this.onPanUp(e));
43✔
920
        this.touchManager.addEventListener(el, 'pandown', (e) => this.onPanDown(e));
43✔
921
        this.touchManager.addEventListener(el, 'panend', (e) => this.onPanEnd(e));
43✔
922
    }
923

924
    private resetInterval() {
925
        if (this.lastInterval) {
137✔
926
            clearInterval(this.lastInterval);
68✔
927
            this.lastInterval = null;
68✔
928
        }
929
    }
930

931
    private restartInterval() {
932
        this.resetInterval();
128✔
933

934
        if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
128✔
935
            this.lastInterval = setInterval(() => {
94✔
936
                const tick = +this.interval;
3,554✔
937
                if (this.playing && this.total && !isNaN(tick) && tick > 0) {
3,554!
938
                    this.next();
3,554✔
939
                } else {
940
                    this.stop();
×
941
                }
942
            }, this.interval);
943
        }
944
    }
945

946
    /** @hidden */
947
    public get nextButtonDisabled() {
948
        return !this.loop && this.current === (this.total - 1);
768✔
949
    }
950

951
    /** @hidden */
952
    public get prevButtonDisabled() {
953
        return !this.loop && this.current === 0;
768✔
954
    }
955

956
    private get indicatorsElements() {
957
        return this._indicators.toArray();
16✔
958
    }
959

960
    private getIndicatorsClass(): string {
961
        switch (this.indicatorsOrientation) {
386!
962
            case CarouselIndicatorsOrientation.top:
963
                return CarouselIndicatorsOrientation.start;
×
964
            case CarouselIndicatorsOrientation.bottom:
965
                return CarouselIndicatorsOrientation.end;
×
966
            default:
967
                return this.indicatorsOrientation;
386✔
968
        }
969
    }
970

971
    private getNextIndex(): number {
972
        return (this.current + 1) % this.total;
3,581✔
973
    }
974

975
    private getPrevIndex(): number {
976
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
21✔
977
    }
978

979
    private resetSlideStyles(slide: IgxSlideComponent) {
980
        slide.nativeElement.style.transform = '';
38✔
981
        slide.nativeElement.style.opacity = '';
38✔
982
    }
983

984
    private pan(event) {
985
        const slideSize = this.vertical
16✔
986
            ? this.currentItem.nativeElement.offsetHeight
987
            : this.currentItem.nativeElement.offsetWidth;
988
        const panOffset = (slideSize / 1000);
16✔
989
        const delta = this.vertical ? event.deltaY : event.deltaX;
16✔
990
        const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
16✔
991
        const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
16✔
992

993
        if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
16✔
994
            return;
2✔
995
        }
996

997
        if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
14✔
998
            this.incomingSlide = null;
2✔
999
            return;
2✔
1000
        }
1001

1002
        event.preventDefault();
12✔
1003
        if (this.isPlaying) {
12!
1004
            this.stoppedByInteraction = true;
×
1005
            this.stop();
×
1006
        }
1007

1008
        if (this.previousItem && this.previousItem.previous) {
12✔
1009
            this.previousItem.previous = false;
4✔
1010
        }
1011
        this.finishAnimations();
12✔
1012

1013
        if (this.incomingSlide) {
12✔
1014
            if (index !== this.incomingSlide.index) {
8✔
1015
                this.resetSlideStyles(this.incomingSlide);
4✔
1016
                this.incomingSlide.previous = false;
4✔
1017
                this.incomingSlide = this.get(index);
4✔
1018
            }
1019
        } else {
1020
            this.incomingSlide = this.get(index);
4✔
1021
        }
1022
        this.incomingSlide.previous = true;
12✔
1023

1024
        if (this.animationType === CarouselAnimationType.fade) {
12!
1025
            this.currentItem.nativeElement.style.opacity = `${Math.abs(offset) / slideSize}`;
×
1026
        } else {
1027
            this.currentItem.nativeElement.style.transform = this.vertical
12✔
1028
                ? `translateY(${delta}px)`
1029
                : `translateX(${delta}px)`;
1030
            this.incomingSlide.nativeElement.style.transform = this.vertical
12✔
1031
                ? `translateY(${offset}px)`
1032
                : `translateX(${offset}px)`;
1033
        }
1034
    }
1035

1036
    private unsubscriber(slide: IgxSlideComponent) {
1037
        return merge(this.destroy$, slide.isDestroyed);
179✔
1038
    }
1039

1040
    private onSlideActivated(slide: IgxSlideComponent) {
1041
        if (slide.active && slide !== this.currentItem) {
103✔
1042
            if (slide.direction === CarouselAnimationDirection.NONE) {
53✔
1043
                const newIndex = slide.index;
13✔
1044
                slide.direction = newIndex > this.current ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
13✔
1045
            }
1046

1047
            if (this.currentItem) {
53✔
1048
                if (this.previousItem && this.previousItem.previous) {
42!
1049
                    this.previousItem.previous = false;
×
1050
                }
1051
                this.currentItem.direction = slide.direction;
42✔
1052
                this.currentItem.active = false;
42✔
1053

1054
                this.previousItem = this.currentItem;
42✔
1055
                this.currentItem = slide;
42✔
1056
                this.triggerAnimations();
42✔
1057
            } else {
1058
                this.currentItem = slide;
11✔
1059
            }
1060
            this.slideChanged.emit({ carousel: this, slide });
53✔
1061
            this.restartInterval();
53✔
1062
            this.cdr.markForCheck();
53✔
1063
        }
1064
    }
1065

1066

1067
    private finishAnimations() {
1068
        if (this.animationStarted(this.leaveAnimationPlayer)) {
12!
1069
            this.leaveAnimationPlayer.finish();
×
1070
        }
1071

1072
        if (this.animationStarted(this.enterAnimationPlayer)) {
12!
1073
            this.enterAnimationPlayer.finish();
×
1074
        }
1075
    }
1076

1077
    private initSlides(change: QueryList<IgxSlideComponent>) {
1078
        const diff = this.differ.diff(change.toArray());
56✔
1079
        if (diff) {
56✔
1080
            this.slides.reduce((_any, c, ind) => c.index = ind, 0); // reset slides indexes
214✔
1081
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
56✔
1082
                const slide = record.item;
179✔
1083
                slide.total = this.total;
179✔
1084
                this.slideAdded.emit({ carousel: this, slide });
179✔
1085
                if (slide.active) {
179✔
1086
                    this.currentItem = slide;
17✔
1087
                }
1088
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
179✔
1089
            });
1090

1091
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
56✔
1092
                const slide = record.item;
10✔
1093
                this.slideRemoved.emit({ carousel: this, slide });
10✔
1094
                if (slide.active) {
10✔
1095
                    slide.active = false;
3✔
1096
                    this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
3✔
1097
                }
1098
            });
1099

1100
            this.updateSlidesSelection();
56✔
1101
        }
1102
    }
1103

1104
    private updateSlidesSelection() {
1105
        if (this.platformUtil.isBrowser) {
56✔
1106
            requestAnimationFrame(() => {
56✔
1107
                if (this.currentItem) {
56✔
1108
                    this.currentItem.active = true;
32✔
1109
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
126✔
1110
                    activeSlides.forEach(slide => slide.active = false);
32✔
1111
                } else if (this.total) {
24✔
1112
                    this.slides.first.active = true;
23✔
1113
                }
1114
                this.play();
56✔
1115
            });
1116
        }
1117
    }
1118
}
1119

1120
export interface ISlideEventArgs extends IBaseEventArgs {
1121
    carousel: IgxCarouselComponent;
1122
    slide: IgxSlideComponent;
1123
}
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