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

IgniteUI / igniteui-angular / 28013325390

23 Jun 2026 08:34AM UTC coverage: 90.139% (-0.02%) from 90.154%
28013325390

Pull #17324

github

web-flow
Merge 690ff31c5 into 01244911c
Pull Request #17324: fix(skills): omit column widths by default in generated grid code

14880 of 17339 branches covered (85.82%)

Branch coverage included in aggregate %.

29947 of 32392 relevant lines covered (92.45%)

34656.81 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 { AfterContentInit, Component, ContentChild, ContentChildren, ElementRef, EventEmitter, HostBinding, HostListener, Input, IterableChangeRecord, IterableDiffer, IterableDiffers, OnDestroy, Output, QueryList, TemplateRef, ViewChild, ViewChildren, booleanAttribute, inject, ChangeDetectionStrategy } from '@angular/core';
3
import { merge, Subject } from 'rxjs';
4
import { takeUntil } from 'rxjs/operators';
5
import { CarouselResourceStringsEN, ICarouselResourceStrings, isLeftToRight} from 'igniteui-angular/core';
6
import { first, IBaseEventArgs, last, PlatformUtil } from 'igniteui-angular/core';
7
import { CarouselAnimationDirection, IgxCarouselComponentBase } from './carousel-base';
8
import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives';
9
import { IgxSlideComponent } from './slide.component';
10
import { IgxIconComponent } from 'igniteui-angular/icon';
11
import { IgxButtonDirective } from 'igniteui-angular/directives';
12
import { getCurrentResourceStrings, onResourceChangeHandle } from 'igniteui-angular/core';
13
import { HammerGesturesManager } from 'igniteui-angular/core';
14
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
15

16
let NEXT_ID = 0;
3✔
17

18
/**
19
 * **Ignite UI for Angular Carousel** -
20
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel.html)
21
 *
22
 * The Ignite UI Carousel is used to browse or navigate through a collection of slides. Slides can contain custom
23
 * content such as images or cards and be used for things such as on-boarding tutorials or page-based interfaces.
24
 * It can be used as a separate fullscreen element or inside another component.
25
 *
26
 * Example:
27
 * ```html
28
 * <igx-carousel>
29
 *   <igx-slide>
30
 *     <h3>First Slide Header</h3>
31
 *     <p>First slide Content</p>
32
 *   <igx-slide>
33
 *   <igx-slide>
34
 *     <h3>Second Slide Header</h3>
35
 *     <p>Second Slide Content</p>
36
 * </igx-carousel>
37
 * ```
38
 */
39
@Component({
40
    providers: [HammerGesturesManager],
41
    selector: 'igx-carousel',
42
    templateUrl: 'carousel.component.html',
43
    styles: [`
44
    :host {
45
        display: block;
46
        outline-style: none;
47
    }`],
48
    changeDetection: ChangeDetectionStrategy.Eager,
49
    imports: [IgxButtonDirective, IgxIconComponent, NgClass, NgTemplateOutlet]
50
})
51
export class IgxCarouselComponent extends IgxCarouselComponentBase implements OnDestroy, AfterContentInit {
3✔
52
    private element = inject(ElementRef);
43✔
53
    private iterableDiffers = inject(IterableDiffers);
43✔
54
    private platformUtil = inject(PlatformUtil);
43✔
55
    private touchManager = inject(HammerGesturesManager);
43✔
56

57

58

59
    /**
60
     * Sets the `id` of the carousel.
61
     * If not set, the `id` of the first carousel component will be `"igx-carousel-0"`.
62
     * ```html
63
     * <igx-carousel id="my-first-carousel"></igx-carousel>
64
     * ```
65
     *
66
     * @memberof IgxCarouselComponent
67
     */
68
    @HostBinding('attr.id')
69
    @Input()
70
    public id = `igx-carousel-${NEXT_ID++}`;
43✔
71
    /**
72
     * Returns the `role` attribute of the carousel.
73
     * ```typescript
74
     * let carouselRole =  this.carousel.role;
75
     * ```
76
     *
77
     * @memberof IgxCarouselComponent
78
     */
79
    @HostBinding('attr.role') public role = 'region';
43✔
80

81
    /** @hidden */
82
    @HostBinding('attr.aria-roledescription')
83
    public roleDescription = 'carousel';
43✔
84

85
    /** @hidden */
86
    @HostBinding('attr.aria-labelledby')
87
    public get labelId() {
88
        return this.showIndicatorsLabel ? `${this.id}-label` : null;
392✔
89
    }
90

91
    /** @hidden */
92
    @HostBinding('class.igx-carousel--vertical')
93
        public get isVertical(): boolean {
94
                return this.vertical;
390✔
95
        }
96

97
    /**
98
     * Returns the class of the carousel component.
99
     * ```typescript
100
     * let class =  this.carousel.cssClass;
101
     * ```
102
     *
103
     * @memberof IgxCarouselComponent
104
     */
105
    @HostBinding('class.igx-carousel')
106
    public cssClass = 'igx-carousel';
43✔
107

108
    /**
109
     * Gets the `touch-action` style of the `list item`.
110
     * ```typescript
111
     * let touchAction = this.listItem.touchAction;
112
     * ```
113
     */
114
    @HostBinding('style.touch-action')
115
    public get touchAction() {
116
        return this.gesturesSupport ? 'pan-y' : 'auto';
390✔
117
    }
118

119
    /**
120
     * Sets whether the carousel should `loop` back to the first slide after reaching the last slide.
121
     * Default value is `true`.
122
     * ```html
123
     * <igx-carousel [loop]="false"></igx-carousel>
124
     * ```
125
     *
126
     * @memberOf IgxCarouselComponent
127
     */
128
    @Input({ transform: booleanAttribute }) public loop = true;
43✔
129

130
    /**
131
     * Sets whether the carousel will `pause` the slide transitions on user interactions.
132
     * Default value is `true`.
133
     * ```html
134
     *  <igx-carousel [pause]="false"></igx-carousel>
135
     * ```
136
     *
137
     * @memberOf IgxCarouselComponent
138
     */
139
    @Input({ transform: booleanAttribute }) public pause = true;
43✔
140

141
    /**
142
     * Controls whether the carousel should render the left/right `navigation` buttons.
143
     * Default value is `true`.
144
     * ```html
145
     * <igx-carousel [navigation]="false"></igx-carousel>
146
     * ```
147
     *
148
     * @memberOf IgxCarouselComponent
149
     */
150
    @Input({ transform: booleanAttribute }) public navigation = true;
43✔
151

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

163

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

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

186
    /**
187
     * Controls the maximum indexes that can be shown.
188
     * Default value is `10`.
189
     * ```html
190
     * <igx-carousel [maximumIndicatorsCount]="5"></igx-carousel>
191
     * ```
192
     *
193
     * @memberOf IgxCarouselComponent
194
     */
195
    @Input() public maximumIndicatorsCount = 10;
43✔
196

197
    /**
198
     * Gets/sets the display mode of carousel indicators. It can be `start` or `end`.
199
     * Default value is `end`.
200
     * ```html
201
     * <igx-carousel indicatorsOrientation="start">
202
     * <igx-carousel>
203
     * ```
204
     *
205
     * @memberOf IgxCarouselComponent
206
     */
207
    @Input() public indicatorsOrientation: CarouselIndicatorsOrientation = CarouselIndicatorsOrientation.end;
43✔
208

209
    /**
210
     * Gets/sets the animation type of carousel.
211
     * Default value is `slide`.
212
     * ```html
213
     * <igx-carousel animationType="none">
214
     * <igx-carousel>
215
     * ```
216
     *
217
     * @memberOf IgxCarouselComponent
218
     */
219
    @Input() public override animationType: CarouselAnimationType = CarouselAnimationType.slide;
43✔
220

221
    /**
222
     * The custom template, if any, that should be used when rendering carousel indicators
223
     *
224
     * ```typescript
225
     * // Set in typescript
226
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
227
     * myComponent.carousel.indicatorTemplate = myCustomTemplate;
228
     * ```
229
     * ```html
230
     * <!-- Set in markup -->
231
     *  <igx-carousel #carousel>
232
     *      ...
233
     *      <ng-template igxCarouselIndicator let-slide>
234
     *         <igx-icon *ngIf="slide.active">brightness_7</igx-icon>
235
     *         <igx-icon *ngIf="!slide.active">brightness_5</igx-icon>
236
     *      </ng-template>
237
     *  </igx-carousel>
238
     * ```
239
     */
240
    @ContentChild(IgxCarouselIndicatorDirective, { read: TemplateRef, static: false })
241
    public indicatorTemplate: TemplateRef<any> = null;
43✔
242

243
    /**
244
     * The custom template, if any, that should be used when rendering carousel next button
245
     *
246
     * ```typescript
247
     * // Set in typescript
248
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
249
     * myComponent.carousel.nextButtonTemplate = myCustomTemplate;
250
     * ```
251
     * ```html
252
     * <!-- Set in markup -->
253
     *  <igx-carousel #carousel>
254
     *      ...
255
     *      <ng-template igxCarouselNextButton let-disabled>
256
     *          <button type="button" igxButton="fab" igxRipple="white" [disabled]="disabled">
257
     *              <igx-icon name="add"></igx-icon>
258
     *          </button>
259
     *      </ng-template>
260
     *  </igx-carousel>
261
     * ```
262
     */
263
    @ContentChild(IgxCarouselNextButtonDirective, { read: TemplateRef, static: false })
264
    public nextButtonTemplate: TemplateRef<any> = null;
43✔
265

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

289
    /**
290
     * The collection of `slides` currently in the carousel.
291
     * ```typescript
292
     * let slides: QueryList<IgxSlideComponent> = this.carousel.slides;
293
     * ```
294
     *
295
     * @memberOf IgxCarouselComponent
296
     */
297
    @ContentChildren(IgxSlideComponent)
298
    public slides: QueryList<IgxSlideComponent>;
299

300
    /**
301
     * An event that is emitted after a slide transition has happened.
302
     * Provides references to the carousel and slide components as event arguments.
303
     * ```html
304
     * <igx-carousel (slideChanged)="slideChanged($event)"></igx-carousel>
305
     * ```
306
     *
307
     * @memberOf IgxCarouselComponent
308
     */
309
    @Output() public slideChanged = new EventEmitter<ISlideEventArgs>();
43✔
310

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

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

333
    /**
334
     * An event that is emitted after the carousel has been paused.
335
     * Provides a reference to the carousel as an event argument.
336
     * ```html
337
     * <igx-carousel (carouselPaused)="carouselPaused($event)"></igx-carousel>
338
     * ```
339
     *
340
     * @memberOf IgxCarouselComponent
341
     */
342
    @Output() public carouselPaused = new EventEmitter<IgxCarouselComponent>();
43✔
343

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

355
    @ViewChild('defaultIndicator', { read: TemplateRef, static: true })
356
    private defaultIndicator: TemplateRef<any>;
357

358
    @ViewChild('defaultNextButton', { read: TemplateRef, static: true })
359
    private defaultNextButton: TemplateRef<any>;
360

361
    @ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
362
    private defaultPrevButton: TemplateRef<any>;
363

364
    @ViewChildren('indicators', { read: ElementRef })
365
    private _indicators: QueryList<ElementRef<HTMLDivElement>>;
366

367
    /**
368
     * @hidden
369
     * @internal
370
     */
371
    public stoppedByInteraction: boolean;
372
    protected override currentItem: IgxSlideComponent;
373
    protected override previousItem: IgxSlideComponent;
374
    private _interval: number;
375
    private _resourceStrings: ICarouselResourceStrings = null;
43✔
376
    private _defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN);
43✔
377
    private lastInterval: any;
378
    private playing: boolean;
379
    private destroyed: boolean;
380
    private destroy$ = new Subject<any>();
43✔
381
    private differ: IterableDiffer<IgxSlideComponent> | null = null;
43✔
382
    private incomingSlide: IgxSlideComponent;
383
    private _hasKeyboardFocusOnIndicators = false;
43✔
384

385
    /**
386
     * An accessor that sets the resource strings.
387
     * By default it uses EN resources.
388
     */
389
    @Input()
390
    public set resourceStrings(value: ICarouselResourceStrings) {
391
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
392
    }
393

394
    /**
395
     * An accessor that returns the resource strings.
396
     */
397
    public get resourceStrings(): ICarouselResourceStrings {
398
        return this._resourceStrings || this._defaultResourceStrings;
3,814✔
399
    }
400

401
    /** @hidden */
402
    public get getIndicatorTemplate(): TemplateRef<any> {
403
        if (this.indicatorTemplate) {
1,522✔
404
            return this.indicatorTemplate;
24✔
405
        }
406
        return this.defaultIndicator;
1,498✔
407
    }
408

409
    /** @hidden */
410
    public get getNextButtonTemplate(): TemplateRef<any> {
411
        if (this.nextButtonTemplate) {
384✔
412
            return this.nextButtonTemplate;
6✔
413
        }
414

415
        return this.defaultNextButton
378✔
416
    }
417

418
    /** @hidden */
419
    public get getPrevButtonTemplate(): TemplateRef<any> {
420
        if (this.prevButtonTemplate) {
384✔
421
            return this.prevButtonTemplate;
6✔
422
        }
423

424
        return this.defaultPrevButton
378✔
425
    }
426

427
    /** @hidden */
428
    public get indicatorsClass() {
429
        return {
386✔
430
            ['igx-carousel-indicators--focused']: this._hasKeyboardFocusOnIndicators,
431
            [`igx-carousel-indicators--${this.getIndicatorsClass()}`]: true
432
        };
433
    }
434

435
    /** @hidden */
436
    public get showIndicators(): boolean {
437
        return this.indicators && this.total <= this.maximumIndicatorsCount && this.total > 0;
390✔
438
    }
439

440
    /** @hidden */
441
    public get showIndicatorsLabel(): boolean {
442
        return this.indicators && this.total > this.maximumIndicatorsCount;
782✔
443
    }
444

445
    /** @hidden */
446
    public get getCarouselLabel() {
447
        return `${this.current + 1} ${this.resourceStrings.igx_carousel_of} ${this.total}`;
2✔
448
    }
449

450
    /**
451
     * Returns the total number of `slides` in the carousel.
452
     * ```typescript
453
     * let slideCount =  this.carousel.total;
454
     * ```
455
     *
456
     * @memberOf IgxCarouselComponent
457
     */
458
    public get total(): number {
459
        return this.slides?.length;
10,960✔
460
    }
461

462
    /**
463
     * The index of the slide being currently shown.
464
     * ```typescript
465
     * let currentSlideNumber =  this.carousel.current;
466
     * ```
467
     *
468
     * @memberOf IgxCarouselComponent
469
     */
470
    public get current(): number {
471
        return !this.currentItem ? 0 : this.currentItem.index;
3,336✔
472
    }
473

474
    /**
475
     * Returns a boolean indicating if the carousel is playing.
476
     * ```typescript
477
     * let isPlaying =  this.carousel.isPlaying;
478
     * ```
479
     *
480
     * @memberOf IgxCarouselComponent
481
     */
482
    public get isPlaying(): boolean {
483
        return this.playing;
31✔
484
    }
485

486
    /**
487
     * Returns а boolean indicating if the carousel is destroyed.
488
     * ```typescript
489
     * let isDestroyed =  this.carousel.isDestroyed;
490
     * ```
491
     *
492
     * @memberOf IgxCarouselComponent
493
     */
494
    public get isDestroyed(): boolean {
495
        return this.destroyed;
1✔
496
    }
497
    /**
498
     * Returns a reference to the carousel element in the DOM.
499
     * ```typescript
500
     * let nativeElement =  this.carousel.nativeElement;
501
     * ```
502
     *
503
     * @memberof IgxCarouselComponent
504
     */
505
    public get nativeElement(): any {
506
        return this.element.nativeElement;
27✔
507
    }
508

509
    /**
510
     * Returns the time `interval` in milliseconds before the slide changes.
511
     * ```typescript
512
     * let timeInterval = this.carousel.interval;
513
     * ```
514
     *
515
     * @memberof IgxCarouselComponent
516
     */
517
    @Input()
518
    public get interval(): number {
519
        return this._interval;
3,736✔
520
    }
521

522
    /**
523
     * Sets the time `interval` in milliseconds before the slide changes.
524
     * If not set, the carousel will not change `slides` automatically.
525
     * ```html
526
     * <igx-carousel [interval]="1000"></igx-carousel>
527
     * ```
528
     *
529
     * @memberof IgxCarouselComponent
530
     */
531
    public set interval(value: number) {
532
        this._interval = +value;
28✔
533
        this.restartInterval();
28✔
534
    }
535

536
    constructor() {
537
        super();
43✔
538
        this.differ = this.iterableDiffers.find([]).create(null);
43✔
539
        onResourceChangeHandle(this.destroy$, () => {
43✔
540
            this._defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN, false);
×
541
        }, this);
542
    }
543

544
    /** @hidden */
545
    public onTap(event) {
546
        // play pause only when tap on slide
547
        if (event.target && event.target.classList.contains('igx-slide')) {
4✔
548
            if (this.isPlaying) {
4✔
549
                if (this.pause) {
1✔
550
                    this.stoppedByInteraction = true;
1✔
551
                }
552
                this.stop();
1✔
553
            } else if (this.stoppedByInteraction) {
3✔
554
                this.play();
1✔
555
            }
556
        }
557
    }
558

559
    /** @hidden */
560
    @HostListener('mouseenter')
561
    public onMouseEnter() {
562
        if (this.pause && this.isPlaying) {
2✔
563
            this.stoppedByInteraction = true;
1✔
564
        }
565
        this.stop();
2✔
566
    }
567

568
    /** @hidden */
569
    @HostListener('mouseleave')
570
    public onMouseLeave() {
571
        if (this.stoppedByInteraction) {
2✔
572
            this.play();
1✔
573
        }
574
    }
575

576
    /** @hidden */
577
    public onPanLeft(event) {
578
        if (!this.vertical) {
7✔
579
            this.pan(event);
5✔
580
        }
581
    }
582

583
    /** @hidden */
584
    public onPanRight(event) {
585
        if (!this.vertical) {
7✔
586
            this.pan(event);
5✔
587
        }
588
    }
589

590
    /** @hidden */
591
    public onPanUp(event) {
592
        if (this.vertical) {
5✔
593
            this.pan(event);
3✔
594
        }
595
    }
596

597
    /** @hidden */
598
    public onPanDown(event) {
599
        if (this.vertical) {
5✔
600
            this.pan(event);
3✔
601
        }
602
    }
603

604
    /**
605
     * @hidden
606
     */
607
    public onPanEnd(event) {
608
        if (!this.gesturesSupport) {
24✔
609
            return;
2✔
610
        }
611
        event.preventDefault();
22✔
612

613
        const slideSize = this.vertical
22✔
614
            ? this.currentItem.nativeElement.offsetHeight
615
            : this.currentItem.nativeElement.offsetWidth;
616
        const panOffset = (slideSize / 1000);
22✔
617
        const eventDelta = this.vertical ? event.deltaY : event.deltaX;
22✔
618
        const delta = Math.abs(eventDelta) + panOffset < slideSize ? Math.abs(eventDelta) : slideSize - panOffset;
22!
619
        const velocity = Math.abs(event.velocity);
22✔
620
        this.resetSlideStyles(this.currentItem);
22✔
621
        if (this.incomingSlide) {
22✔
622
            this.resetSlideStyles(this.incomingSlide);
12✔
623
            if (slideSize / 2 < delta || velocity > 1) {
12✔
624
                this.incomingSlide.direction = eventDelta < 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
8✔
625
                this.incomingSlide.previous = false;
8✔
626

627
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
8!
628
                    delta / slideSize : (slideSize - delta) / slideSize;
629

630
                if (velocity > 1) {
8✔
631
                    this.newDuration = this.defaultAnimationDuration / velocity;
4✔
632
                }
633
                this.incomingSlide.active = true;
8✔
634
            } else {
635
                this.currentItem.direction = eventDelta > 0 ? CarouselAnimationDirection.NEXT : CarouselAnimationDirection.PREV;
4✔
636
                this.previousItem = this.incomingSlide;
4✔
637
                this.previousItem.previous = true;
4✔
638
                this.animationPosition = this.animationType === CarouselAnimationType.fade ?
4!
639
                    Math.abs((slideSize - delta) / slideSize) : delta / slideSize;
640
                this.playAnimations();
4✔
641
            }
642
        }
643

644
        if (this.stoppedByInteraction) {
22!
645
            this.play();
×
646
        }
647
    }
648

649
    /** @hidden */
650
    public ngAfterContentInit() {
651
        this.slides.changes
43✔
652
            .pipe(takeUntil(this.destroy$))
653
            .subscribe((change: QueryList<IgxSlideComponent>) => this.initSlides(change));
13✔
654

655
        this.initSlides(this.slides);
43✔
656
        this.registerGestureEvents();
43✔
657
    }
658

659
    /** @hidden */
660
    public override ngOnDestroy() {
661
        super.ngOnDestroy();
45✔
662
        this.destroy$.next(true);
45✔
663
        this.destroy$.complete();
45✔
664
        this.destroyed = true;
45✔
665
        if (this.lastInterval) {
45✔
666
            clearInterval(this.lastInterval);
24✔
667
        }
668
        this.touchManager.destroy();
45✔
669
    }
670

671
    /** @hidden */
672
    public handleKeydownPrev(event: KeyboardEvent): void {
673
        if (this.platformUtil.isActivationKey(event)) {
2✔
674
            event.preventDefault();
2✔
675
            this.prev();
2✔
676
        }
677
    }
678

679
    /** @hidden */
680
    public handleKeydownNext(event: KeyboardEvent): void {
681
        if (this.platformUtil.isActivationKey(event)) {
2✔
682
            event.preventDefault();
2✔
683
            this.next();
2✔
684
        }
685
    }
686

687
    /** @hidden */
688
    public handleKeyUp(event: KeyboardEvent): void {
689
        if (event.key === this.platformUtil.KEYMAP.TAB) {
6✔
690
            this._hasKeyboardFocusOnIndicators = true;
6✔
691
        }
692
    }
693

694
    /** @hidden */
695
    public handleFocusOut(event: FocusEvent): void {
696
        const target = event.relatedTarget as HTMLElement;
14✔
697

698
        if (!target || !target.classList.contains('igx-carousel-indicators__indicator')) {
14✔
699
            this._hasKeyboardFocusOnIndicators = false;
1✔
700
        }
701
    }
702

703
    /** @hidden */
704
    public handleClick(): void {
705
        this._hasKeyboardFocusOnIndicators = false;
1✔
706
    }
707

708
    /** @hidden */
709
    public handleKeydown(event: KeyboardEvent): void {
710
        const { key } = event;
16✔
711
        const slides = this.slides.toArray();
16✔
712
        const isRTL = !isLeftToRight(this.nativeElement);
16✔
713

714
        switch (key) {
16✔
715
            case this.platformUtil.KEYMAP.ARROW_LEFT:
716
                isRTL ? this.next() : this.prev();
4✔
717
                break;
4✔
718
            case this.platformUtil.KEYMAP.ARROW_RIGHT:
719
                isRTL ? this.prev() : this.next();
6✔
720
                break;
6✔
721
            case this.platformUtil.KEYMAP.HOME:
722
                event.preventDefault();
3✔
723
                this.select(isRTL ? last(slides) : first(slides));
3✔
724
                break;
3✔
725
            case this.platformUtil.KEYMAP.END:
726
                event.preventDefault();
3✔
727
                this.select(isRTL ? first(slides) : last(slides));
3✔
728
                break;
3✔
729
        }
730

731
        this.indicatorsElements[this.current].nativeElement.focus();
16✔
732
    }
733

734
    /**
735
     * Returns the slide corresponding to the provided `index` or null.
736
     * ```typescript
737
     * let slide1 =  this.carousel.get(1);
738
     * ```
739
     *
740
     * @memberOf IgxCarouselComponent
741
     */
742
    public get(index: number): IgxSlideComponent {
743
        return this.slides.find((slide) => slide.index === index);
7,117✔
744
    }
745

746
    /**
747
     * Adds a new slide to the carousel.
748
     * ```typescript
749
     * this.carousel.add(newSlide);
750
     * ```
751
     *
752
     * @memberOf IgxCarouselComponent
753
     */
754
    public add(slide: IgxSlideComponent) {
755
        const newSlides = this.slides.toArray();
3✔
756
        newSlides.push(slide);
3✔
757
        this.slides.reset(newSlides);
3✔
758
        this.slides.notifyOnChanges();
3✔
759
    }
760

761
    /**
762
     * Removes a slide from the carousel.
763
     * ```typescript
764
     * this.carousel.remove(slide);
765
     * ```
766
     *
767
     * @memberOf IgxCarouselComponent
768
     */
769
    public remove(slide: IgxSlideComponent) {
770
        if (slide && slide === this.get(slide.index)) { // check if the requested slide for delete is present in the carousel
4✔
771
            const newSlides = this.slides.toArray();
4✔
772
            newSlides.splice(slide.index, 1);
4✔
773
            this.slides.reset(newSlides);
4✔
774
            this.slides.notifyOnChanges();
4✔
775
        }
776
    }
777

778
    /**
779
     * Switches to the passed-in slide with a given `direction`.
780
     * ```typescript
781
     * const slide = this.carousel.get(2);
782
     * this.carousel.select(slide, CarouselAnimationDirection.NEXT);
783
     * ```
784
     *
785
     * @memberOf IgxCarouselComponent
786
     */
787
    public select(slide: IgxSlideComponent, direction?: CarouselAnimationDirection): void;
788
    /**
789
     * Switches to slide by index with a given `direction`.
790
     * ```typescript
791
     * this.carousel.select(2, CarouselAnimationDirection.NEXT);
792
     * ```
793
     *
794
     * @memberOf IgxCarouselComponent
795
     */
796
    public select(index: number, direction?: CarouselAnimationDirection): void;
797
    public select(slideOrIndex: IgxSlideComponent | number, direction: CarouselAnimationDirection = CarouselAnimationDirection.NONE): void {
16✔
798
        const slide = typeof slideOrIndex === 'number'
3,074✔
799
            ? this.get(slideOrIndex)
800
            : slideOrIndex;
801

802
        if (slide && slide !== this.currentItem) {
3,074✔
803
            slide.direction = direction;
3,071✔
804
            slide.active = true;
3,071✔
805
        }
806
    }
807

808
    /**
809
     * Transitions to the next slide in the carousel.
810
     * ```typescript
811
     * this.carousel.next();
812
     * ```
813
     *
814
     * @memberOf IgxCarouselComponent
815
     */
816
    public next() {
817
        const index = this.getNextIndex();
3,047✔
818

819
        if (index === 0 && !this.loop) {
3,047✔
820
            this.stop();
1✔
821
            return;
1✔
822
        }
823
        return this.select(this.get(index), CarouselAnimationDirection.NEXT);
3,046✔
824
    }
825

826
    /**
827
     * Transitions to the previous slide in the carousel.
828
     * ```typescript
829
     * this.carousel.prev();
830
     * ```
831
     *
832
     * @memberOf IgxCarouselComponent
833
     */
834
    public prev() {
835
        const index = this.getPrevIndex();
13✔
836

837
        if (!this.loop && index === this.total - 1) {
13✔
838
            this.stop();
1✔
839
            return;
1✔
840
        }
841
        return this.select(this.get(index), CarouselAnimationDirection.PREV);
12✔
842
    }
843

844
    /**
845
     * Resumes playing of the carousel if in paused state.
846
     * No operation otherwise.
847
     * ```typescript
848
     * this.carousel.play();
849
     * }
850
     * ```
851
     *
852
     * @memberOf IgxCarouselComponent
853
     */
854
    public play() {
855
        if (!this.playing) {
61✔
856
            this.playing = true;
47✔
857
            this.carouselPlaying.emit(this);
47✔
858
            this.restartInterval();
47✔
859
            this.stoppedByInteraction = false;
47✔
860
        }
861
    }
862

863
    /**
864
     * Stops slide transitions if the `pause` option is set to `true`.
865
     * No operation otherwise.
866
     * ```typescript
867
     *  this.carousel.stop();
868
     * }
869
     * ```
870
     *
871
     * @memberOf IgxCarouselComponent
872
     */
873
    public stop() {
874
        if (this.pause) {
9✔
875
            this.playing = false;
9✔
876
            this.carouselPaused.emit(this);
9✔
877
            this.resetInterval();
9✔
878
        }
879
    }
880

881
    protected getPreviousElement(): HTMLElement {
882
        return this.previousItem.nativeElement;
3✔
883
    }
884

885
    protected getCurrentElement(): HTMLElement {
886
        return this.currentItem.nativeElement;
4✔
887
    }
888

889
    private registerGestureEvents() {
890
        if (!this.gesturesSupport || !this.platformUtil.isBrowser) {
43!
891
            return;
×
892
        }
893
        const el = this.element.nativeElement;
43✔
894
        this.touchManager.addEventListener(el, 'tap', (e) => this.onTap(e));
43✔
895
        this.touchManager.addEventListener(el, 'panleft', (e) => this.onPanLeft(e));
43✔
896
        this.touchManager.addEventListener(el, 'panright', (e) => this.onPanRight(e));
43✔
897
        this.touchManager.addEventListener(el, 'panup', (e) => this.onPanUp(e));
43✔
898
        this.touchManager.addEventListener(el, 'pandown', (e) => this.onPanDown(e));
43✔
899
        this.touchManager.addEventListener(el, 'panend', (e) => this.onPanEnd(e));
43✔
900
    }
901

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

909
    private restartInterval() {
910
        this.resetInterval();
128✔
911

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

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

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

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

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

949
    private getNextIndex(): number {
950
        return (this.current + 1) % this.total;
3,055✔
951
    }
952

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

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

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

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

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

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

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

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

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

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

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

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

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

1044

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

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

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

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

1078
            this.updateSlidesSelection();
56✔
1079
        }
1080
    }
1081

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

1098
export interface ISlideEventArgs extends IBaseEventArgs {
1099
    carousel: IgxCarouselComponent;
1100
    slide: IgxSlideComponent;
1101
}
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