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

IgniteUI / igniteui-angular / 20191011486

13 Dec 2025 10:50AM UTC coverage: 91.504% (+0.003%) from 91.501%
20191011486

push

github

web-flow
refactor(*): New localization implementation. (#16034)

14236 of 16778 branches covered (84.85%)

378 of 419 new or added lines in 61 files covered. (90.21%)

1 existing line in 1 file now uncovered.

28649 of 31309 relevant lines covered (91.5%)

34896.51 hits per line

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

95.29
/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts
1
import { NgClass, NgTemplateOutlet } from '@angular/common';
2
import { AfterContentInit, Component, ContentChild, ContentChildren, ElementRef, EventEmitter, HostBinding, HostListener, Injectable, Input, IterableChangeRecord, IterableDiffer, IterableDiffers, OnDestroy, Output, QueryList, TemplateRef, ViewChild, ViewChildren, booleanAttribute, DOCUMENT, inject } from '@angular/core';
3
import { HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
4
import { merge, Subject } from 'rxjs';
5
import { takeUntil } from 'rxjs/operators';
6
import { CarouselResourceStringsEN, ICarouselResourceStrings, ɵIgxDirectionality } from 'igniteui-angular/core';
7
import { first, IBaseEventArgs, last, PlatformUtil } from 'igniteui-angular/core';
8
import { CarouselAnimationDirection, IgxCarouselComponentBase } from './carousel-base';
9
import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives';
10
import { IgxSlideComponent } from './slide.component';
11
import { IgxIconComponent } from 'igniteui-angular/icon';
12
import { IgxButtonDirective } from 'igniteui-angular/directives';
13
import { getCurrentResourceStrings, onResourceChangeHandle } from 'igniteui-angular/core';
14
import { HammerGesturesManager } from 'igniteui-angular/core';
15
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
16

17
let NEXT_ID = 0;
3✔
18

19

20
@Injectable()
21
export class CarouselHammerConfig extends HammerGestureConfig {
3✔
22
    public override overrides = {
×
23
        pan: { direction: HammerGesturesManager.Hammer?.DIRECTION_HORIZONTAL }
24
    };
25
}
26
/**
27
 * **Ignite UI for Angular Carousel** -
28
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel.html)
29
 *
30
 * The Ignite UI Carousel is used to browse or navigate through a collection of slides. Slides can contain custom
31
 * content such as images or cards and be used for things such as on-boarding tutorials or page-based interfaces.
32
 * It can be used as a separate fullscreen element or inside another component.
33
 *
34
 * Example:
35
 * ```html
36
 * <igx-carousel>
37
 *   <igx-slide>
38
 *     <h3>First Slide Header</h3>
39
 *     <p>First slide Content</p>
40
 *   <igx-slide>
41
 *   <igx-slide>
42
 *     <h3>Second Slide Header</h3>
43
 *     <p>Second Slide Content</p>
44
 * </igx-carousel>
45
 * ```
46
 */
47
@Component({
48
    providers: [
49
        {
50
            provide: HAMMER_GESTURE_CONFIG,
51
            useClass: CarouselHammerConfig
52
        }
53
    ],
54
    selector: 'igx-carousel',
55
    templateUrl: 'carousel.component.html',
56
    styles: [`
57
    :host {
58
        display: block;
59
        outline-style: none;
60
    }`],
61
    imports: [IgxButtonDirective, IgxIconComponent, NgClass, NgTemplateOutlet]
62
})
63
export class IgxCarouselComponent extends IgxCarouselComponentBase implements OnDestroy, AfterContentInit {
3✔
64
    private element = inject(ElementRef);
43✔
65
    private iterableDiffers = inject(IterableDiffers);
43✔
66
    private platformUtil = inject(PlatformUtil);
43✔
67
    private dir = inject(ɵIgxDirectionality);
43✔
68
    private document = inject(DOCUMENT);
43✔
69

70

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

93
    /** @hidden */
94
    @HostBinding('attr.aria-roledescription')
95
    public roleDescription = 'carousel';
43✔
96

97
    /** @hidden */
98
    @HostBinding('attr.aria-labelledby')
99
    public get labelId() {
100
        return this.showIndicatorsLabel ? `${this.id}-label` : null;
392✔
101
    }
102

103
    /** @hidden */
104
    @HostBinding('class.igx-carousel--vertical')
105
        public get isVertical(): boolean {
106
                return this.vertical;
390✔
107
        }
108

109
    /**
110
     * Returns the class of the carousel component.
111
     * ```typescript
112
     * let class =  this.carousel.cssClass;
113
     * ```
114
     *
115
     * @memberof IgxCarouselComponent
116
     */
117
    @HostBinding('class.igx-carousel')
118
    public cssClass = 'igx-carousel';
43✔
119

120
    /**
121
     * Gets the `touch-action` style of the `list item`.
122
     * ```typescript
123
     * let touchAction = this.listItem.touchAction;
124
     * ```
125
     */
126
    @HostBinding('style.touch-action')
127
    public get touchAction() {
128
        return this.gesturesSupport ? 'pan-y' : 'auto';
390✔
129
    }
130

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

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

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

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

175

176
    /**
177
     * Controls whether the carousel has vertical alignment.
178
     * Default value is `false`.
179
     * ```html
180
     * <igx-carousel [vertical]="true"></igx-carousel>
181
     * ```
182
     *
183
     * @memberOf IgxCarouselComponent
184
     */
185
    @Input({ transform: booleanAttribute }) public override vertical = false;
43✔
186

187
    /**
188
     * Controls whether the carousel should support gestures.
189
     * Default value is `true`.
190
     * ```html
191
     * <igx-carousel [gesturesSupport]="false"></igx-carousel>
192
     * ```
193
     *
194
     * @memberOf IgxCarouselComponent
195
     */
196
    @Input({ transform: booleanAttribute }) public gesturesSupport = true;
43✔
197

198
    /**
199
     * Controls the maximum indexes that can be shown.
200
     * Default value is `10`.
201
     * ```html
202
     * <igx-carousel [maximumIndicatorsCount]="5"></igx-carousel>
203
     * ```
204
     *
205
     * @memberOf IgxCarouselComponent
206
     */
207
    @Input() public maximumIndicatorsCount = 10;
43✔
208

209
    /**
210
     * Gets/sets the display mode of carousel indicators. It can be `start` or `end`.
211
     * Default value is `end`.
212
     * ```html
213
     * <igx-carousel indicatorsOrientation="start">
214
     * <igx-carousel>
215
     * ```
216
     *
217
     * @memberOf IgxCarouselComponent
218
     */
219
    @Input() public indicatorsOrientation: CarouselIndicatorsOrientation = CarouselIndicatorsOrientation.end;
43✔
220

221
    /**
222
     * Gets/sets the animation type of carousel.
223
     * Default value is `slide`.
224
     * ```html
225
     * <igx-carousel animationType="none">
226
     * <igx-carousel>
227
     * ```
228
     *
229
     * @memberOf IgxCarouselComponent
230
     */
231
    @Input() public override animationType: CarouselAnimationType = CarouselAnimationType.slide;
43✔
232

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

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

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

301
    /**
302
     * The collection of `slides` currently in the carousel.
303
     * ```typescript
304
     * let slides: QueryList<IgxSlideComponent> = this.carousel.slides;
305
     * ```
306
     *
307
     * @memberOf IgxCarouselComponent
308
     */
309
    @ContentChildren(IgxSlideComponent)
310
    public slides: QueryList<IgxSlideComponent>;
311

312
    /**
313
     * An event that is emitted after a slide transition has happened.
314
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
315
     * ```html
316
     * <igx-carousel (slideChanged)="slideChanged($event)"></igx-carousel>
317
     * ```
318
     *
319
     * @memberOf IgxCarouselComponent
320
     */
321
    @Output() public slideChanged = new EventEmitter<ISlideEventArgs>();
43✔
322

323
    /**
324
     * An event that is emitted after a slide has been added to the carousel.
325
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
326
     * ```html
327
     * <igx-carousel (slideAdded)="slideAdded($event)"></igx-carousel>
328
     * ```
329
     *
330
     * @memberOf IgxCarouselComponent
331
     */
332
    @Output() public slideAdded = new EventEmitter<ISlideEventArgs>();
43✔
333

334
    /**
335
     * An event that is emitted after a slide has been removed from the carousel.
336
     * Provides references to the `IgxCarouselComponent` and `IgxSlideComponent` as event arguments.
337
     * ```html
338
     * <igx-carousel (slideRemoved)="slideRemoved($event)"></igx-carousel>
339
     * ```
340
     *
341
     * @memberOf IgxCarouselComponent
342
     */
343
    @Output() public slideRemoved = new EventEmitter<ISlideEventArgs>();
43✔
344

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

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

367
    @ViewChild('defaultIndicator', { read: TemplateRef, static: true })
368
    private defaultIndicator: TemplateRef<any>;
369

370
    @ViewChild('defaultNextButton', { read: TemplateRef, static: true })
371
    private defaultNextButton: TemplateRef<any>;
372

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

376
    @ViewChildren('indicators', { read: ElementRef })
377
    private _indicators: QueryList<ElementRef<HTMLDivElement>>;
378

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

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

406
    /**
407
     * An accessor that returns the resource strings.
408
     */
409
    public get resourceStrings(): ICarouselResourceStrings {
410
        return this._resourceStrings || this._defaultResourceStrings;
3,814✔
411
    }
412

413
    /** @hidden */
414
    public get getIndicatorTemplate(): TemplateRef<any> {
415
        if (this.indicatorTemplate) {
1,522✔
416
            return this.indicatorTemplate;
24✔
417
        }
418
        return this.defaultIndicator;
1,498✔
419
    }
420

421
    /** @hidden */
422
    public get getNextButtonTemplate(): TemplateRef<any> {
423
        if (this.nextButtonTemplate) {
384✔
424
            return this.nextButtonTemplate;
6✔
425
        }
426

427
        return this.defaultNextButton
378✔
428
    }
429

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

436
        return this.defaultPrevButton
378✔
437
    }
438

439
    /** @hidden */
440
    public get indicatorsClass() {
441
        return {
386✔
442
            ['igx-carousel-indicators--focused']: this._hasKeyboardFocusOnIndicators,
443
            [`igx-carousel-indicators--${this.getIndicatorsClass()}`]: true
444
        };
445
    }
446

447
    /** @hidden */
448
    public get showIndicators(): boolean {
449
        return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
390✔
450
    }
451

452
    /** @hidden */
453
    public get showIndicatorsLabel(): boolean {
454
        return this.indicators && this.total > this.maximumIndicatorsCount;
782✔
455
    }
456

457
    /** @hidden */
458
    public get getCarouselLabel() {
459
        return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
2✔
460
    }
461

462
    /**
463
     * Returns the total number of `slides` in the carousel.
464
     * ```typescript
465
     * let slideCount =  this.carousel.total;
466
     * ```
467
     *
468
     * @memberOf IgxCarouselComponent
469
     */
470
    public get total(): number {
471
        return this.slides?.length;
10,084✔
472
    }
473

474
    /**
475
     * The index of the slide being currently shown.
476
     * ```typescript
477
     * let currentSlideNumber =  this.carousel.current;
478
     * ```
479
     *
480
     * @memberOf IgxCarouselComponent
481
     */
482
    public get current(): number {
483
        return !this.currentItem ? 0 : this.currentItem.index;
2,898✔
484
    }
485

486
    /**
487
     * Returns a boolean indicating if the carousel is playing.
488
     * ```typescript
489
     * let isPlaying =  this.carousel.isPlaying;
490
     * ```
491
     *
492
     * @memberOf IgxCarouselComponent
493
     */
494
    public get isPlaying(): boolean {
495
        return this.playing;
31✔
496
    }
497

498
    /**
499
     * Returns а boolean indicating if the carousel is destroyed.
500
     * ```typescript
501
     * let isDestroyed =  this.carousel.isDestroyed;
502
     * ```
503
     *
504
     * @memberOf IgxCarouselComponent
505
     */
506
    public get isDestroyed(): boolean {
507
        return this.destroyed;
1✔
508
    }
509
    /**
510
     * Returns a reference to the carousel element in the DOM.
511
     * ```typescript
512
     * let nativeElement =  this.carousel.nativeElement;
513
     * ```
514
     *
515
     * @memberof IgxCarouselComponent
516
     */
517
    public get nativeElement(): any {
518
        return this.element.nativeElement;
11✔
519
    }
520

521
    /**
522
     * Returns the time `interval` in milliseconds before the slide changes.
523
     * ```typescript
524
     * let timeInterval = this.carousel.interval;
525
     * ```
526
     *
527
     * @memberof IgxCarouselComponent
528
     */
529
    @Input()
530
    public get interval(): number {
531
        return this._interval;
3,298✔
532
    }
533

534
    /**
535
     * Sets the time `interval` in milliseconds before the slide changes.
536
     * If not set, the carousel will not change `slides` automatically.
537
     * ```html
538
     * <igx-carousel [interval]="1000"></igx-carousel>
539
     * ```
540
     *
541
     * @memberof IgxCarouselComponent
542
     */
543
    public set interval(value: number) {
544
        this._interval = +value;
28✔
545
        this.restartInterval();
28✔
546
    }
547

548
    constructor() {
549
        super();
43✔
550
        this.differ = this.iterableDiffers.find([]).create(null);
43✔
551
        onResourceChangeHandle(this.destroy$, () => {
43✔
NEW
552
            this._defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN, false);
×
553
        }, this);
554
    }
555

556
    /** @hidden */
557
    @HostListener('tap', ['$event'])
558
    public onTap(event) {
559
        // play pause only when tap on slide
560
        if (event.target && event.target.classList.contains('igx-slide')) {
4✔
561
            if (this.isPlaying) {
4✔
562
                if (this.pause) {
1✔
563
                    this.stoppedByInteraction = true;
1✔
564
                }
565
                this.stop();
1✔
566
            } else if (this.stoppedByInteraction) {
3✔
567
                this.play();
1✔
568
            }
569
        }
570
    }
571

572
    /** @hidden */
573
    @HostListener('mouseenter')
574
    public onMouseEnter() {
575
        if (this.pause && this.isPlaying) {
2✔
576
            this.stoppedByInteraction = true;
1✔
577
        }
578
        this.stop();
2✔
579
    }
580

581
    /** @hidden */
582
    @HostListener('mouseleave')
583
    public onMouseLeave() {
584
        if (this.stoppedByInteraction) {
2✔
585
            this.play();
1✔
586
        }
587
    }
588

589
    /** @hidden */
590
    @HostListener('panleft', ['$event'])
591
    public onPanLeft(event) {
592
        if (!this.vertical) {
7✔
593
            this.pan(event);
5✔
594
        }
595
    }
596

597
    /** @hidden */
598
    @HostListener('panright', ['$event'])
599
    public onPanRight(event) {
600
        if (!this.vertical) {
7✔
601
            this.pan(event);
5✔
602
        }
603
    }
604

605
    /** @hidden */
606
    @HostListener('panup', ['$event'])
607
    public onPanUp(event) {
608
        if (this.vertical) {
5✔
609
            this.pan(event);
3✔
610
        }
611
    }
612

613
    /** @hidden */
614
    @HostListener('pandown', ['$event'])
615
    public onPanDown(event) {
616
        if (this.vertical) {
5✔
617
            this.pan(event);
3✔
618
        }
619
    }
620

621
    /**
622
     * @hidden
623
     */
624
    @HostListener('panend', ['$event'])
625
    public onPanEnd(event) {
626
        if (!this.gesturesSupport) {
24✔
627
            return;
2✔
628
        }
629
        event.preventDefault();
22✔
630

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

645
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
8!
646
                    delta / slideSize : (slideSize - delta) / slideSize;
647

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

662
        if (this.stoppedByInteraction) {
22!
663
            this.play();
×
664
        }
665
    }
666

667
    /** @hidden */
668
    public ngAfterContentInit() {
669
        this.slides.changes
43✔
670
            .pipe(takeUntil(this.destroy$))
671
            .subscribe((change: QueryList<IgxSlideComponent>) => this.initSlides(change));
13✔
672

673
        this.initSlides(this.slides);
43✔
674
    }
675

676
    /** @hidden */
677
    public override ngOnDestroy() {
678
        super.ngOnDestroy();
45✔
679
        this.destroy$.next(true);
45✔
680
        this.destroy$.complete();
45✔
681
        this.destroyed = true;
45✔
682
        if (this.lastInterval) {
45✔
683
            clearInterval(this.lastInterval);
24✔
684
        }
685
    }
686

687
    /** @hidden */
688
    public handleKeydownPrev(event: KeyboardEvent): void {
689
        if (this.platformUtil.isActivationKey(event)) {
2✔
690
            event.preventDefault();
2✔
691
            this.prev();
2✔
692
        }
693
    }
694

695
    /** @hidden */
696
    public handleKeydownNext(event: KeyboardEvent): void {
697
        if (this.platformUtil.isActivationKey(event)) {
2✔
698
            event.preventDefault();
2✔
699
            this.next();
2✔
700
        }
701
    }
702

703
    /** @hidden */
704
    public handleKeyUp(event: KeyboardEvent): void {
705
        if (event.key === this.platformUtil.KEYMAP.TAB) {
6✔
706
            this._hasKeyboardFocusOnIndicators = true;
6✔
707
        }
708
    }
709

710
    /** @hidden */
711
    public handleFocusOut(event: FocusEvent): void {
712
        const target = event.relatedTarget as HTMLElement;
14✔
713

714
        if (!target || !target.classList.contains('igx-carousel-indicators__indicator')) {
14✔
715
            this._hasKeyboardFocusOnIndicators = false;
1✔
716
        }
717
    }
718

719
    /** @hidden */
720
    public handleClick(): void {
721
        this._hasKeyboardFocusOnIndicators = false;
1✔
722
    }
723

724
    /** @hidden */
725
    public handleKeydown(event: KeyboardEvent): void {
726
        const { key } = event;
16✔
727
        const slides = this.slides.toArray();
16✔
728

729
        switch (key) {
16✔
730
            case this.platformUtil.KEYMAP.ARROW_LEFT:
731
                this.dir.rtl ? this.next() : this.prev();
4✔
732
                break;
4✔
733
            case this.platformUtil.KEYMAP.ARROW_RIGHT:
734
                this.dir.rtl ? this.prev() : this.next();
6✔
735
                break;
6✔
736
            case this.platformUtil.KEYMAP.HOME:
737
                event.preventDefault();
3✔
738
                this.select(this.dir.rtl ? last(slides) : first(slides));
3✔
739
                break;
3✔
740
            case this.platformUtil.KEYMAP.END:
741
                event.preventDefault();
3✔
742
                this.select(this.dir.rtl ? first(slides) : last(slides));
3✔
743
                break;
3✔
744
        }
745

746
        this.indicatorsElements[this.current].nativeElement.focus();
16✔
747
    }
748

749
    /**
750
     * Returns the slide corresponding to the provided `index` or null.
751
     * ```typescript
752
     * let slide1 =  this.carousel.get(1);
753
     * ```
754
     *
755
     * @memberOf IgxCarouselComponent
756
     */
757
    public get(index: number): IgxSlideComponent {
758
        return this.slides.find((slide) => slide.index === index);
6,145✔
759
    }
760

761
    /**
762
     * Adds a new slide to the carousel.
763
     * ```typescript
764
     * this.carousel.add(newSlide);
765
     * ```
766
     *
767
     * @memberOf IgxCarouselComponent
768
     */
769
    public add(slide: IgxSlideComponent) {
770
        const newSlides = this.slides.toArray();
3✔
771
        newSlides.push(slide);
3✔
772
        this.slides.reset(newSlides);
3✔
773
        this.slides.notifyOnChanges();
3✔
774
    }
775

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

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

817
        if (slide && slide !== this.currentItem) {
2,636✔
818
            slide.direction = direction;
2,633✔
819
            slide.active = true;
2,633✔
820
        }
821
    }
822

823
    /**
824
     * Transitions to the next slide in the carousel.
825
     * ```typescript
826
     * this.carousel.next();
827
     * ```
828
     *
829
     * @memberOf IgxCarouselComponent
830
     */
831
    public next() {
832
        const index = this.getNextIndex();
2,609✔
833

834
        if (index === 0 && !this.loop) {
2,609✔
835
            this.stop();
1✔
836
            return;
1✔
837
        }
838
        return this.select(this.get(index), CarouselAnimationDirection.NEXT);
2,608✔
839
    }
840

841
    /**
842
     * Transitions to the previous slide in the carousel.
843
     * ```typescript
844
     * this.carousel.prev();
845
     * ```
846
     *
847
     * @memberOf IgxCarouselComponent
848
     */
849
    public prev() {
850
        const index = this.getPrevIndex();
13✔
851

852
        if (!this.loop && index === this.total - 1) {
13✔
853
            this.stop();
1✔
854
            return;
1✔
855
        }
856
        return this.select(this.get(index), CarouselAnimationDirection.PREV);
12✔
857
    }
858

859
    /**
860
     * Resumes playing of the carousel if in paused state.
861
     * No operation otherwise.
862
     * ```typescript
863
     * this.carousel.play();
864
     * }
865
     * ```
866
     *
867
     * @memberOf IgxCarouselComponent
868
     */
869
    public play() {
870
        if (!this.playing) {
61✔
871
            this.playing = true;
47✔
872
            this.carouselPlaying.emit(this);
47✔
873
            this.restartInterval();
47✔
874
            this.stoppedByInteraction = false;
47✔
875
        }
876
    }
877

878
    /**
879
     * Stops slide transitions if the `pause` option is set to `true`.
880
     * No operation otherwise.
881
     * ```typescript
882
     *  this.carousel.stop();
883
     * }
884
     * ```
885
     *
886
     * @memberOf IgxCarouselComponent
887
     */
888
    public stop() {
889
        if (this.pause) {
9✔
890
            this.playing = false;
9✔
891
            this.carouselPaused.emit(this);
9✔
892
            this.resetInterval();
9✔
893
        }
894
    }
895

896
    protected getPreviousElement(): HTMLElement {
897
        return this.previousItem.nativeElement;
3✔
898
    }
899

900
    protected getCurrentElement(): HTMLElement {
901
        return this.currentItem.nativeElement;
4✔
902
    }
903

904
    private resetInterval() {
905
        if (this.lastInterval) {
137✔
906
            clearInterval(this.lastInterval);
68✔
907
            this.lastInterval = null;
68✔
908
        }
909
    }
910

911
    private restartInterval() {
912
        this.resetInterval();
128✔
913

914
        if (!isNaN(this.interval) && this.interval > 0 && this.platformUtil.isBrowser) {
128✔
915
            this.lastInterval = setInterval(() => {
94✔
916
                const tick = +this.interval;
2,590✔
917
                if (this.playing && this.total && !isNaN(tick) && tick > 0) {
2,590!
918
                    this.next();
2,590✔
919
                } else {
920
                    this.stop();
×
921
                }
922
            }, this.interval);
923
        }
924
    }
925

926
    /** @hidden */
927
    public get nextButtonDisabled() {
928
        return !this.loop && this.current === (this.total - 1);
768✔
929
    }
930

931
    /** @hidden */
932
    public get prevButtonDisabled() {
933
        return !this.loop && this.current === 0;
768✔
934
    }
935

936
    private get indicatorsElements() {
937
        return this._indicators.toArray();
16✔
938
    }
939

940
    private getIndicatorsClass(): string {
941
        switch (this.indicatorsOrientation) {
386!
942
            case CarouselIndicatorsOrientation.top:
943
                return CarouselIndicatorsOrientation.start;
×
944
            case CarouselIndicatorsOrientation.bottom:
945
                return CarouselIndicatorsOrientation.end;
×
946
            default:
947
                return this.indicatorsOrientation;
386✔
948
        }
949
    }
950

951
    private getNextIndex(): number {
952
        return (this.current + 1) % this.total;
2,617✔
953
    }
954

955
    private getPrevIndex(): number {
956
        return this.current - 1 < 0 ? this.total - 1 : this.current - 1;
21✔
957
    }
958

959
    private resetSlideStyles(slide: IgxSlideComponent) {
960
        slide.nativeElement.style.transform = '';
38✔
961
        slide.nativeElement.style.opacity = '';
38✔
962
    }
963

964
    private pan(event) {
965
        const slideSize = this.vertical
16✔
966
            ? this.currentItem.nativeElement.offsetHeight
967
            : this.currentItem.nativeElement.offsetWidth;
968
        const panOffset = (slideSize / 1000);
16✔
969
        const delta = this.vertical ? event.deltaY : event.deltaX;
16✔
970
        const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex();
16✔
971
        const offset = delta < 0 ? slideSize + delta : -slideSize + delta;
16✔
972

973
        if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) {
16✔
974
            return;
2✔
975
        }
976

977
        if (!this.loop && ((this.current === 0 && delta > 0) || (this.current === this.total - 1 && delta < 0))) {
14✔
978
            this.incomingSlide = null;
2✔
979
            return;
2✔
980
        }
981

982
        event.preventDefault();
12✔
983
        if (this.isPlaying) {
12!
984
            this.stoppedByInteraction = true;
×
985
            this.stop();
×
986
        }
987

988
        if (this.previousItem && this.previousItem.previous) {
12✔
989
            this.previousItem.previous = false;
4✔
990
        }
991
        this.finishAnimations();
12✔
992

993
        if (this.incomingSlide) {
12✔
994
            if (index !== this.incomingSlide.index) {
8✔
995
                this.resetSlideStyles(this.incomingSlide);
4✔
996
                this.incomingSlide.previous = false;
4✔
997
                this.incomingSlide = this.get(index);
4✔
998
            }
999
        } else {
1000
            this.incomingSlide = this.get(index);
4✔
1001
        }
1002
        this.incomingSlide.previous = true;
12✔
1003

1004
        if (this.animationType === CarouselAnimationType.fade) {
12!
1005
            this.currentItem.nativeElement.style.opacity = `${Math.abs(offset) / slideSize}`;
×
1006
        } else {
1007
            this.currentItem.nativeElement.style.transform = this.vertical
12✔
1008
                ? `translateY(${delta}px)`
1009
                : `translateX(${delta}px)`;
1010
            this.incomingSlide.nativeElement.style.transform = this.vertical
12✔
1011
                ? `translateY(${offset}px)`
1012
                : `translateX(${offset}px)`;
1013
        }
1014
    }
1015

1016
    private unsubscriber(slide: IgxSlideComponent) {
1017
        return merge(this.destroy$, slide.isDestroyed);
179✔
1018
    }
1019

1020
    private onSlideActivated(slide: IgxSlideComponent) {
1021
        if (slide.active && slide !== this.currentItem) {
103✔
1022
            if (slide.direction === CarouselAnimationDirection.NONE) {
53✔
1023
                const newIndex = slide.index;
13✔
1024
                slide.direction = newIndex > this.current ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
13✔
1025
            }
1026

1027
            if (this.currentItem) {
53✔
1028
                if (this.previousItem && this.previousItem.previous) {
42!
1029
                    this.previousItem.previous = false;
×
1030
                }
1031
                this.currentItem.direction = slide.direction;
42✔
1032
                this.currentItem.active = false;
42✔
1033

1034
                this.previousItem = this.currentItem;
42✔
1035
                this.currentItem = slide;
42✔
1036
                this.triggerAnimations();
42✔
1037
            } else {
1038
                this.currentItem = slide;
11✔
1039
            }
1040
            this.slideChanged.emit({ carousel: this, slide });
53✔
1041
            this.restartInterval();
53✔
1042
            this.cdr.markForCheck();
53✔
1043
        }
1044
    }
1045

1046

1047
    private finishAnimations() {
1048
        if (this.animationStarted(this.leaveAnimationPlayer)) {
12!
1049
            this.leaveAnimationPlayer.finish();
×
1050
        }
1051

1052
        if (this.animationStarted(this.enterAnimationPlayer)) {
12!
1053
            this.enterAnimationPlayer.finish();
×
1054
        }
1055
    }
1056

1057
    private initSlides(change: QueryList<IgxSlideComponent>) {
1058
        const diff = this.differ.diff(change.toArray());
56✔
1059
        if (diff) {
56✔
1060
            this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
214✔
1061
            diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
56✔
1062
                const slide = record.item;
179✔
1063
                slide.total = this.total;
179✔
1064
                this.slideAdded.emit({ carousel: this, slide });
179✔
1065
                if (slide.active) {
179✔
1066
                    this.currentItem = slide;
17✔
1067
                }
1068
                slide.activeChange.pipe(takeUntil(this.unsubscriber(slide))).subscribe(() => this.onSlideActivated(slide));
179✔
1069
            });
1070

1071
            diff.forEachRemovedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
56✔
1072
                const slide = record.item;
10✔
1073
                this.slideRemoved.emit({ carousel: this, slide });
10✔
1074
                if (slide.active) {
10✔
1075
                    slide.active = false;
3✔
1076
                    this.currentItem = this.get(slide.index < this.total ? slide.index : this.total - 1);
3✔
1077
                }
1078
            });
1079

1080
            this.updateSlidesSelection();
56✔
1081
        }
1082
    }
1083

1084
    private updateSlidesSelection() {
1085
        if (this.platformUtil.isBrowser) {
56✔
1086
            requestAnimationFrame(() => {
56✔
1087
                if (this.currentItem) {
56✔
1088
                    this.currentItem.active = true;
32✔
1089
                    const activeSlides = this.slides.filter(slide => slide.active && slide.index !== this.currentItem.index);
126✔
1090
                    activeSlides.forEach(slide => slide.active = false);
32✔
1091
                } else if (this.total) {
24✔
1092
                    this.slides.first.active = true;
23✔
1093
                }
1094
                this.play();
56✔
1095
            });
1096
        }
1097
    }
1098
}
1099

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

© 2026 Coveralls, Inc