Search code examples
angularcarouselangular2-templateangular-bootstrapangular-content-projection

How to wrap ngb-carousel?


I have an instance of ngb-carousel which is currently used like this:

<ngb-carousel>
    <ng-template ngbSlide>Hello</ng-template>
    <ng-template ngbSlide>World</ng-template>
</ngb-carousel>

I would like to wrap it into my custom carousel component providing some extra features, to use it like this:

<!-- my-carousel.component.html -->
<div class="my-carousel">
    <ngb-carousel>
        <ng-content></ng-content>
    </ngb-carousel>
    <div>Some text here</div>
</my-carousel>

<!-- usage -->
<my-carousel>
    <ng-template ngbSlide>Hello</ng-template> 
    <ng-template ngbSlide>World</ng-template>
</my-carousel>

However, this naive approach does not work, as the slides are not being displayed.

I have tried to use <ng-content select="ng-template[ngbSlide]" ngProjectAs="ng-template[ngbSlide]"> and other combinations to no avail.

What am I missing? Which is the proper way to do it?


Solution

    • We need to create templates with a template reference slide.

    • Then we can take a ContentChildren to get all the slides through content projection

    • after getting the slides, the key is to use a ng-template with the attribute ngbSlide for the ngb-carousel to recognize it as a slide

    • We can use @for to loop through the slides and render them inside a ng-template using ngTemplateOutlet

    Working example below

    parent html

    @if (images) {
    <my-carousel>
      <ng-template #slide>
        <div class="picsum-img-wrapper">
          <img [src]="images[0]" alt="Random first slide" />
        </div>
        <div class="carousel-caption">
          <h3>First slide label</h3>
          <p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
        </div>
      </ng-template>
      <ng-template #slide>
        <div class="picsum-img-wrapper">
          <img [src]="images[1]" alt="Random second slide" />
        </div>
        <div class="carousel-caption">
          <h3>Second slide label</h3>
          <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
        </div>
      </ng-template>
      <ng-template #slide>
        <div class="picsum-img-wrapper">
          <img [src]="images[2]" alt="Random third slide" />
        </div>
        <div class="carousel-caption">
          <h3>Third slide label</h3>
          <p>Praesent commodo cursus magna, vel scelerisque nisl consectetur.</p>
        </div>
      </ng-template>
    </my-carousel>
    }
    

    parent ts

    import { Component } from '@angular/core';
    import { NgbCarouselModule } from '@ng-bootstrap/ng-bootstrap';
    import { MyCarouselComponent } from './my-carousel/my-carousel.component';
    
    @Component({
      selector: 'ngbd-carousel-basic',
      standalone: true,
      imports: [NgbCarouselModule, MyCarouselComponent],
      templateUrl: './carousel-basic.html',
    })
    export class NgbdCarouselBasic {
      images = [944, 1011, 984].map((n) => `https://picsum.photos/id/${n}/900/500`);
    }
    

    my carousel ts

    import { CommonModule } from '@angular/common';
    import {
      Component,
      ContentChildren,
      OnInit,
      QueryList,
      TemplateRef,
    } from '@angular/core';
    import { NgbCarouselModule } from '@ng-bootstrap/ng-bootstrap';
    
    @Component({
      selector: 'my-carousel',
      template: `
      <div class="my-carousel">
        <ngb-carousel>
          @for(slide of slides; track slide; let i = $index) {
          <ng-template ngbSlide >
            <ng-template
             [ngTemplateOutlet]="slide"></ng-template>
          </ng-template>
          }
        </ngb-carousel>
        <div>Some text here</div>
      </div>
      `,
      standalone: true,
      imports: [NgbCarouselModule, CommonModule],
    })
    export class MyCarouselComponent implements OnInit {
      @ContentChildren('slide', { read: TemplateRef }) slides: QueryList<
        TemplateRef<any>
      >;
      constructor() {}
    
      ngOnInit() {}
    
      ngAfterContentInit() {
        console.log(this.slides);
      }
    }
    

    stackblitz