angularbindingbootstrap-5carouselngfor

Angular - bootstrap carousel - How to bind active item


How to achieve this :
When I click on right arrow of the carousel, I want to trigger a method with the item index as parameter.
Some thing like <button ... (...)="eventHandler(index)" ..>

Below the code to be modified to achieve passing index param (the code is working)

<div id="carouselId" class="carousel slide">
  <div class="carousel-inner" >
    <div class="carousel-item active">      
        <img [src]="videoFirstItem.safeUrl"> 
    </div>
    <div class="carousel-item" *ngFor="let item of videoItems">
      <img [src]="item.safeUrl">
    </div>
  </div>
  <button class="carousel-control-prev" type="button" data-bs-target="#carouselId" data-bs-slide="prev">
    <span class="carousel-control-prev-icon" aria-hidden="true"></span>
    <span class="visually-hidden">Previous</span>
  </button>
  <button class="carousel-control-next" type="button" data-bs-target="#carouselId" data-bs-slide="next">
    <span class="carousel-control-next-icon" aria-hidden="true"></span>
    <span class="visually-hidden">Next</span>
  </button>
</div>
const PAGE_INDEX:number=0
const NB_ITEMS_PER_PAGE:number=4

@Component({
  selector: 'app-image-carousel',
  templateUrl: './image-carousel.component.html',
  styleUrls: ['./image-carousel.component.css']
})
export class ImageCarouselComponent implements OnInit {

  videoFirstItem:DisplayableVideo=new DisplayableVideo
  videoItems:DisplayableVideo[]=new Array<DisplayableVideo>

  constructor(
    private backendVideRetrieverService: BackendVideRetrieverService
  ){}

  ngOnInit(): void {
    this.backendVideRetrieverService.getVideosByPage(PAGE_INDEX, NB_ITEMS_PER_PAGE).subscribe(
      (displayableVideos:DisplayableVideo[]) => {
        this.videoFirstItem = displayableVideos[0]
        this.videoItems=displayableVideos.slice(1)
      }
    )
  }
}

Below the code that is not working but gives an idea of what I want to do

<div class="carousel-item" *ngFor="let item of videoItems; let index = index">
....
<button class="carousel-control-next" ... data-bs-slide="next" (clilk)="eventHandler(index)">
eventHandler(index:number){
    if(index == lastItemIndex){
      getMoreVideo()
    }
  }


Solution

  • The problem is you cannot access index outside ngFor context.

    One way to access index is via Bootstrap's Carousel instance which tracks active element: so, assign index to each carousel item, and then add click handler on prev/next, and get carousel instance and active element, and then read the index you stored:

    store index on carousel item:

    <div class="carousel-item" *ngFor="let item of videoItems let index = index" [attr.data-index]="index">
    

    add click handler (no arguments needed, because it's outside of scope, it's just to check index from carousel's active element):

    <button class="carousel-control-next" type="button" data-bs-target="#carouselId" data-bs-slide="next" (click)="eventHandler($event)">
    

    on prev/next click, get carousel instance with Bootstrap, and then implement handler to read carousel's active element, and then get its index value you stored in data attribute:

    // import Bootstrap's carousel
    import { Carousel } from 'bootstrap';
    
    // instance
    carousel!: Carousel & { _activeElement?: null | HTMLElement };
    
    ngAfterViewInit(): void {
        // instantiate when rendering is done
        this.carousel = new Carousel('#carouselId');
        // or with .getOrCreateInstance method
        //this.carousel = Carousel.getOrCreateInstance('#carouselId'))
    }   
    
    
    eventHandler(event:Event){
        // get active element and read its index
        const index = this.carousel._activeElement.dataset.index;
        console.log('index', index);
        // do something with index...
        if(index == lastItemIndex){
            getMoreVideo()
        }
    }
    

    (if you're interested only in detecting last item, you could use Angular's last and add some flag only to the last element, for example:

    <div class="carousel-item" *ngFor="let item of videoItems; let last = last;" [attr.data-islast]="last ? 'true': null">
    

    edit:

    You can also access active element via carousel's slide event's relatedTarget:

    add slide.bs.carousel listener to carousel container (note that it will be called whenever the carousel slides, for example, if autoplaying):

    <div id="carouselId" class="carousel slide" (slide.bs.carousel)="onSlide($event)">
    

    slide handler recieves bootstrap's event and then its relatedTarget is the item to be active:

    onSlide(event: any) { // Carousel.Event
    
      // event.relatedTarget is the DOM element that is being slid into place as the active item
      const activeItem = event.relatedTarget; // as HTMLElement;
      const index = activeItem.dataset.index;
      console.log('index', index);
      //...
    }
    

    see:

    Events

    Bootstrap’s carousel class exposes two events for hooking into carousel functionality. Both events have the following additional properties:

    • direction: The direction in which the carousel is sliding (either "left" or "right").
    • relatedTarget: The DOM element that is being slid into place as the active item.
    • from: The index of the current item
    • to: The index of the next item

    Events