22

I'm actually migrating to Angular 15 and saw that swiper 9 was out.

It's written that a simple npm i swiper and that it should work, since

Custom elements are supported in all major browser and by almost every framework.

But I'm a bit lost since I cannot import it in the module anymore

Does somebody knows how to use the latest v9.0.0^ swiper version with angular ?

Raphaël Balet
  • 6,334
  • 6
  • 41
  • 78

8 Answers8

22

In AppModule add:

import {register} from 'swiper/element/bundle';

register();

Create a Directive

import {AfterViewInit, Directive, ElementRef, Input} from '@angular/core';
import {SwiperOptions} from "swiper";

@Directive({
  selector: '[fmSwiper]',
  standalone: true,
})
export class SwiperDirective implements AfterViewInit {

  private readonly swiperElement: HTMLElement;

  @Input('config')
  config?: SwiperOptions;

  constructor(private el: ElementRef<HTMLElement>) {
    this.swiperElement = el.nativeElement;
  }

  ngAfterViewInit() {
    Object.assign(this.el.nativeElement, this.config);
    
    // @ts-ignore
    this.el.nativeElement.initialize();
  }
}

In your Component ts File add

schemas: [CUSTOM_ELEMENTS_SCHEMA]

Set your Swiper configuration.

Example:

import {Component, CUSTOM_ELEMENTS_SCHEMA, ViewEncapsulation} from '@angular/core';
import {CommonModule} from '@angular/common';
import {MainHeadingComponent} from "../main-heading/main-heading.component";
import {StreamItemComponent} from "./stream-item/stream-item.component";
import {A11y, Mousewheel, Navigation, Pagination, SwiperOptions} from 'swiper';
import {SwiperDirective} from "../../directives/swiper.directive";

@Component({
  selector: 'fm-streams-swiper',
  standalone: true,
  encapsulation: ViewEncapsulation.None,
  imports: [
    CommonModule,
    MainHeadingComponent,
    StreamItemComponent,
    SwiperDirective
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  templateUrl: './streams-swiper.component.html',
})
export class StreamsSwiperComponent {

  sliders: string[] = [
    'Test 1',
    'Test 2',
    'Test 3',
    'Test 4',
    'Test 5',
    'Test 6',
    'Test 7',
    'Test 8',
    'Test 9',
  ]

  public config: SwiperOptions = {
    modules: [Navigation, Pagination, A11y, Mousewheel],
    autoHeight: true,
    spaceBetween: 20,
    navigation: false,
    pagination: {clickable: true, dynamicBullets: true},
    slidesPerView: 1,
    centeredSlides: true,
    breakpoints: {
      400: {
        slidesPerView: "auto",
        centeredSlides: false
      },
    }
  }
}

And the HMTL File:

  <swiper-container fmSwiper [config]="config" init="false" class="w-full">
    <swiper-slide class="w-[310px] sm:w-[450px] pb-6"
         *ngFor="let slider of sliders">
      <fm-stream-item></fm-stream-item>
    </swiper-slide>
  </swiper-container>

This is my solution for the moment. Happy to hear better ways to implement the new Version of Swiper in Angular :-)

Swiper Element: Core Version & Modules

Just Pilot
  • 474
  • 3
  • 11
  • Thx a lot for this proposal, I'll try this out. Wondering if this directive will be published over on `npm` – Raphaël Balet Feb 05 '23 at 17:38
  • 3
    Thank you! Works almost perfect for me. There seems to be a problem when re-initializing the component, maybe only ionic-related. But it seems to be solved by simply adding a 0ms timeout to this.el.nativeElement.initialize(); – Sebiworld Mar 06 '23 at 22:26
  • Thanks for the solution. I still had issues with VirtualSlides, but other than that everything seems to be working really nicely. – selcuk-sahin Mar 08 '23 at 11:32
  • 2
    in order to remove the // @ts-ignore you may change the line to: constructor(private el: ElementRef void }>) { – Muhammad Assar Apr 01 '23 at 00:59
  • Thank you, sweet solution. Short feedback: Is `swiperElement` even necessary? It's never been used. – insertusernamehere Jul 25 '23 at 09:09
  • @MuhammadAssar You can be even more specific with this: `private readonly elementRef: ElementRef`. – insertusernamehere Jul 25 '23 at 10:45
11

Add the CUSTOM_ELEMENTS_SCHEMA to the @NgModule decorator for the AppModule. This ensures that Angular compiles and ignores the unknown swiper-container and swiper-slide custom elements and compiles without errors.

import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AppRoutingModule],
  providers: [],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}

It then becomes possible to add the swiper-container and swiper-slide elements to the template, however the code won't do much. The initialize the slide we need to call the register function as described in the documentation. If you look into to the source code Swiper, it shows that this function requires the DOM to be initialized. This means that you should place it in the component and in the ngAfterViewInit lifecycle hook. This gives us the following code (please note that the parameters can be passed in kebab-case):

import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
import {register} from 'swiper/element/bundle';

@Component({
  selector: 'app-root',
  template: `
    <swiper-container initial-slide="0" slides-per-view="1">
      <swiper-slide>
        <h1>Slide 1</h1>
      </swiper-slide>
      <swiper-slide>
        <h1>Slide 2</h1>
      </swiper-slide>
      <swiper-slide>
        <h1>Slide 3</h1>
      </swiper-slide>
    </swiper-container>
  `,
})
export class AppComponent implements AfterViewInit {

  ngAfterViewInit(): void {
    register();
  }
}

Access to the Swiper instance can be achieved through the ViewChild decorator. To demonstrate this, I've included a function that prints the index of the current slide to the console each time that the slide changes. Not that all the events described in the documentation are available but must be written in lowercase.

import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
import {register} from 'swiper/element/bundle';
import {Swiper} from 'swiper/types';

@Component({
  selector: 'app-root',
  template: `
    <swiper-container #swiperRef initial-slide="0" (activeindexchange)="onActiveIndexChange()" slides-per-view="1">
      <swiper-slide>
        <h1>Slide 1</h1>
      </swiper-slide>
      <swiper-slide>
        <h1>Slide 2</h1>
      </swiper-slide>
      <swiper-slide>
        <h1>Slide 3</h1>
      </swiper-slide>
    </swiper-container>
  `,
})
export class AppComponent implements AfterViewInit {
  @ViewChild('swiperRef')
  swiperRef: ElementRef | undefined;
  swiper?: Swiper;

  ngAfterViewInit(): void {
    register();
    this.swiper = this.swiperRef?.nativeElement.swiper;
  }

  onActiveIndexChange() {
    console.log(this.swiper?.activeIndex);
  }
}
Raphaël Balet
  • 6,334
  • 6
  • 41
  • 78
SebastiaanH
  • 111
  • 3
  • Thx for you answer, small question though. But wouldn't it be better to set the swiper variable through the `(swiper)` output from the `swiper-container` element ? `` ? This we we could avoid having to declare it in the `ngAfterViewInit()` whish will create an error in our console – Raphaël Balet Apr 04 '23 at 12:08
  • Also, if you which to let it in the front-end, you could have added `@ViewChild('swiperRef', { static: true }) private _swiperRef: ElementRef | undefined` ---> `static: true` make you able to use it in an `ngOnInit` – Raphaël Balet Apr 04 '23 at 12:47
  • `Events MUST be written in lowercase.` Holy crap. Thank you!!!!!!!! How the hell did you figure that out? – Eliezer Berlin Jul 12 '23 at 13:11
4

swiper9 made it simple to use just you have to mention "schemas: [CUSTOM_ELEMENTS_SCHEMA]," in your module.ts file where your component is imported

//below steps are for module.ts file
// 1 step : 
import { CUSTOM_ELEMENTS_SCHEMA,} from '@angular/core';

// 2nd step: 
mention "schemas: [CUSTOM_ELEMENTS_SCHEMA]," inside @NgModule 

// 3rd step : 
import { register } from 'swiper/element/bundle';
              
// and  
register();

now your code works as expected "add below code is in html file"

<swiper-container #swiper initial-slide="0">
  <swiper-slide>Slide 1</swiper-slide>
  <swiper-slide>Slide 2</swiper-slide>
  <swiper-slide>Slide 3</swiper-slide>
  <swiper-slide>Slide 4</swiper-slide>
  <swiper-slide>Slide 5</swiper-slide>
  <swiper-slide>Slide 6</swiper-slide>
  <swiper-slide>Slide 7</swiper-slide>
  <swiper-slide>Slide 8</swiper-slide>
  <swiper-slide>Slide 9</swiper-slide>
</swiper-container>

without any errors

Raphaël Balet
  • 6,334
  • 6
  • 41
  • 78
4

Although I've accepted this answer as being the solution, I actually had to do the following. Which takes a bunch of Ideas I've red & found over the swiper.js doc

This is the most optimized & cleaner version that I could find.

app.component.ts

import { register } from 'swiper/element/bundle'

constructor() {
  register()
}
  1. register() should be called only once so the app.component.ts is the place to go.

component.html

<swiper-container
  #swiperRef
  init="false"
>
  <swiper-slide *ngFor="let i of items; trackBy: trackByFn">
    <!-- slide -->
  </swiper-slide>
</swiper-container>
  1. init="false" will let you prepare the options, if you wish it to. Will also let you subscribe on changes like slideChange.

module.ts

import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'

@NgModule({
  // ...
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})

Note: you have to declare it in the component that will be using the swiper element Do not declare it globally

component.ts

@ViewChild('swiperRef', { static: true })
  protected _swiperRef: ElementRef | undefined
swiper?: Swiper

ngOnInit() {
  this._initSwiper()
}

private _initSwiper() {
  const options: SwiperOptions = {
    pagination: { clickable: true },
    slidesPerView: 1,
    breakpoints: this._getBreakpoints(), // In case you wish to calculate base on the `items` length
  }

  const swiperEl = this._swiperRef.nativeElement
  Object.assign(swiperEl, options)

  swiperEl.initialize()

  if (this.swiper) this.swiper.currentBreakpoint = false // Breakpoint fixes
  this.swiper = this._swiperRef.nativeElement.swiper

  this.swiper.off('slideChange') // Avoid multiple subscription, in case you wish to call the `_initSwiper()` multiple time

  this.swiper.on('slideChange', () => { // Any change subscription you wish
    this.infinitLoad?.triggerOnScroll()
  })
}

Note: the swiper-container shall not be in an *ngIf. if it is, change the following in the .component.ts file.

Work arround for *ngIf

@ViewChild('swiperRef', { static: false }) // Change to "false"
  protected _swiperRef: ElementRef | undefined

itemsLoaded() { // Shall not be used in `ngOnInit` because it isn't rendered yet
  this._initSwiper()
}
  1. Create a component that does contain the swiper-container to avoid using *ngIf
  2. Await the DOM to be rendered (I didn't found any clean way to do it, feel welcome to update that part if you get any good Idea ;)
Raphaël Balet
  • 6,334
  • 6
  • 41
  • 78
  • You could place `itemsLoaded()` within lifecycle hook `AfterContentChecked` or `AfterViewChecked`. The downside of this approach is that it will fire multiple times as it's constantly checking for things. [From the docs](https://angular.io/guide/lifecycle-hooks) `Called after ngAfterContentInit() and every subsequent ngDoCheck()`. Maybe wrapping everything within it's own component, and place this component inside `ngif`. Have to test this one. – celsomtrindade May 22 '23 at 15:35
2

In the case you want to initialize manually (with init="false"), you can do so using these typings.

in themplate.html

<swiper-container init="false" class="pager" #swiper>
    <swiper-slide *ngFor="[...]">
    </swiper-slide>
</swiper-container>

in component

@Component({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
// [...]
})
export class NewsPagerComponent implements AfterViewInit, OnInit {
  @ViewChild('swiper') swiperRef: ElementRef<HTMLElement & { swiper?: Swiper } & { initialize: () => void }> | undefined;
  swiper?: Swiper;
  
  ngOnInit(): void {
    register();
  }

  ngAfterViewInit(): void {
    const swiperEl = Object.assign(this.swiperRef.nativeElement, {
      modules: [Navigation],
      navigation: true
    });
    swiperEl.initialize();

    this.swiper = this.swiperRef.nativeElement.swiper;
  }
}

And now you have the tools to follow the official docs of swiper-element for anything else.

2

Create a Directive ng generate directive SwiperDirective

import {AfterViewInit, Directive, ElementRef, Input} from '@angular/core';
import {SwiperOptions} from "swiper";

@Directive({
  selector: '[appSwiperDirective]'
})
export class SwiperDirectiveDirective implements AfterViewInit{

 swiperElement: HTMLElement;

  @Input('config')
  config?: SwiperOptions;

  constructor(private el: ElementRef<HTMLElement>) {
    this.swiperElement = el.nativeElement;
  }

  ngAfterViewInit() {
    Object.assign(this.el.nativeElement, this.config);
  }

}

Add schemas: [CUSTOM_ELEMENTS_SCHEMA] and import Directive in your App Module

import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { SharedModule } from './shared.module';
import { SwiperDirectiveDirective } from './swiper-directive.directive';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  declarations: [
    AppComponent,
    SwiperDirectiveDirective
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    FontAwesomeModule,
    SharedModule,
    BrowserAnimationsModule
  
  ],
  providers: [],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {
}

Add register(); Globally in app.component.ts file

import { Component, OnInit, AfterViewInit } from '@angular/core';
import { register } from 'swiper/element/bundle';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, AfterViewInit {
  constructor() { }

  ngOnInit(): void {

  }

  ngAfterViewInit(): void {

    register();

  }
}

enter code here

Set your Swiper configuration in your component.ts file here

import { Component } from '@angular/core';
import { A11y, Mousewheel, SwiperOptions } from 'swiper';


@Component({
  selector: 'app-partner-area',
  templateUrl: './partner-area.component.html',
  styleUrls: ['./partner-area.component.scss'],

})

export class PartnerAreaComponent {

  public config: SwiperOptions = {
    modules: [ A11y, Mousewheel],
    autoHeight: true,
    spaceBetween: 20,
    slidesPerView: 3,
    breakpoints: {
      1200: {
        slidesPerView: 6,
        centeredSlides: false
      },
    }
  }

}

Add the HTML file

<section class="partner-area ptb-100">
    <swiper-container appSwiperDirective [config]="config" class="container">
        <swiper-slide class="single-item">
            <div class="partner-area__single">
                <img src="https://picsum.photos/500" alt="partner">
            </div>
        </swiper-slide>
        <swiper-slide class="single-item">
            <div class="partner-area__single">
                <img src="https://picsum.photos/500" alt="partner">
            </div>
        </swiper-slide>
    </swiper-container>
</section>

Project Link

1

I just upgraded a project from Swiper 8 to Swiper 9 and faced the same initial confusion. Apparently they did away with the Angular components in Swiper and want us to the Swiper Elements.

https://swiperjs.com/element#swiper-custom-elements-from-cdn

It is not an ideal "Angular" solution, but I expect they made this decision to be more compatible with as many frameworks as possible.

To make this work just create a div layout with the classes "swiper", "swiper-wrapper", and "swiper-slide" with the wrapper inside swiper and the slides inside the wrapper. In the ts file import Swiper and create a new instance of the class and direct it to file ".swiper".

My code looks like this:

this.swiper = new Swiper('.swiper', {
            modules: [Navigation, Pagination, Autoplay],
            speed: 4000,
            effect: 'flip',
            spaceBetween: 0,
            navigation: false,
            autoplay: {
              delay: 3500,
              disableOnInteraction: true
            },
            slidesPerView: 1,
            pagination: { clickable: true },
            scrollbar: { draggable: true }
          });
David Bray
  • 19
  • 2
0

I followed first answer

but had to change ngAfterViewInit() so that swiper initalization runs outside ngZone. Otherwise I'm getting NG0506 error

I use SSR

type SwiperHtmlElement = HTMLElement & { initialize: () => void } 

constructor(private el: ElementRef, private ngZone: NgZone) { 
    this.swiperElement = el.nativeElement as SwiperHtmlElement; 
}

ngAfterViewInit() {
  Object.assign(this.swiperElement, this.config);
  this.ngZone.runOutsideAngular(() => {
    setTimeout(() => {
      this.swiperElement.initialize();
    });
  })
}