How to use swiper 9 with angular
Asked Answered
H

11

24

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 ?

Humphreys answered 2/2, 2023 at 16:50 Comment(0)
S
31

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

Suttles answered 5/2, 2023 at 16:44 Comment(6)
Thx a lot for this proposal, I'll try this out. Wondering if this directive will be published over on npmInvincible
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();Coprophilous
Thanks for the solution. I still had issues with VirtualSlides, but other than that everything seems to be working really nicely.Neologize
in order to remove the // @ts-ignore you may change the line to: constructor(private el: ElementRef<HTMLElement & { initialize: () => void }>) {Gabrila
Thank you, sweet solution. Short feedback: Is swiperElement even necessary? It's never been used.Encephalography
@MuhammadAssar You can be even more specific with this: private readonly elementRef: ElementRef<SwiperContainer>.Encephalography
F
16

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" (swiperactiveindexchange)="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);
  }
}
Forsyth answered 10/2, 2023 at 16:45 Comment(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 ? <swiper-container (swiper)="swiper = $event"> ? This we we could avoid having to declare it in the ngAfterViewInit() whish will create an error in our consoleInvincible
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 ngOnInitInvincible
Events MUST be written in lowercase. Holy crap. Thank you!!!!!!!! How the hell did you figure that out?Hemi
C
7

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

Cornu answered 4/4, 2023 at 8:1 Comment(4)
How is this different from the existing solutions?Emia
Angular 16.1.2 Typescript 5.1.3 swiper 9.4.1 All OkAltazimuth
Angular 16.0.0; TypeScript 5.0.2; and Swiper 10.0.4; no work. The first slide is displayed; and I can swipe through slides, but the content on other slides is blank.Hardison
1. What part of that is 'simple'? 2. This isn't sufficient if you actually want to touch the settings from within typescript.Hemi
H
5

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 ;)
Humphreys answered 5/4, 2023 at 15:26 Comment(1)
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 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.Clavier
C
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.

Clark answered 28/3, 2023 at 10:59 Comment(0)
R
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

Routh answered 25/4, 2023 at 13:2 Comment(0)
S
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 }
          });
Serdab answered 2/2, 2023 at 18:2 Comment(0)
W
1

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();
    });
  })
}
Woodley answered 10/6, 2023 at 7:40 Comment(1)
Thx for you answer, but why not just updating the first answer then ?Invincible
S
1

If you are using Angular Routing Module (not apply to standalone components or modules). We need to do those implementations:

In app.component.ts import and change:

import {register} from 'swiper/element/bundle';
    
@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})

export class AppComponent implements OnInit {
    public ngOnInit() {
        register();
    }
}

You may create a directive: swiper.directive.ts

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

@Directive({
    selector: '[directiveSwiper]'
})
export class SwiperDirective implements OnInit {
    private readonly swiperElement: HTMLElement;

    //Se obtiene la configuracion del HTML
    @Input('config') config?: SwiperOptions;

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

    public ngOnInit() {
        Object.assign(this.swiperElement, this.config);
        this.swiperElement.initialize();
    }
}

After that, you have to import it into the component module where you are going to use it, in this example we use example.module.ts, and then we need to add a schema metadata called CUSTOM_ELEMENTS_SCHEMA, so that Angular recognizes the swiper-container html tag:

import {SwiperDirective} from './swiper.directive';

@NgModule({
    declarations: [ExampleComponent, SwiperDirective],
    imports: [
        CommonModule,
        ExampleRoutingModule
    ],
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class ExampleModule {}

In the component example.component.ts, let's configure the Swiper options:

import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {Swiper, SwiperOptions} from 'swiper/types';

@Component({
    selector: 'app-example',
    templateUrl: './example.component.html',
    styleUrls: ['./example.component.scss']
})
export class ExampleComponent implements OnInit {
    public config: SwiperOptions = {
        slidesPerView: 1,
        spaceBetween: 30,
        navigation: true
    };

    public ngOnInit(): void {}
}

Finally, we need to define into example.component.html the swiper-component tag, with the directive directiveSwiper and Swiper options. It's important to implement init=false into swiper-container to be able to apply correctly the Swiper options and initialize Swiper from directive:

<swiper-container directiveSwiper [config]="config" class="mySwiper" init="false">
    <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>

That's all, folks.

Spume answered 30/10, 2023 at 21:21 Comment(3)
Thx for your help, but could you explain me in wish way this is better than the accepted answer ? CheersInvincible
Hi @RaphaëlBalet. The first answer is oriented to standalone components and my answer is oriented to module components with Angular Routing ModulesSpiteful
Tbh Richard, your answer is then the same as the other one but you're declaring it in a module instead of a component. You can lazy load a standalone component and use it as a route if I'm not wrong, so this is adding more complexity for other people that would like to find an answer to the question.. So I appreciate a lot your help, but I'd find it welcome if you could close your answerInvincible
P
0

Although importing Swiper from an external CDN may work in some cases (together with the CUSTOM_ELEMENTS_SCHEMA), you probably won't go too far if you need a more customized solution inside Angular.

I had a lot of headache when trying to create an interactive thumbnail gallery so I took my time to experiment all the suggested settings and tweaks (the accepted answer helped me a lot! Thanks!!). And I finally made Swiper 10 work inside an Angular 16 project!

Sample thumbnail gallery

All the source code is published at StackBlitz:

Sample thumbnail gallery (w/ source code)

For a better explanation of the whole setup please refer to the Medium publication that I made for this:

Swiper 10 & Angular 16 Uncovered (Medium article)

Petrifaction answered 26/9, 2023 at 10:23 Comment(3)
Hi Tuco and thx for the Answer. I but fear that this may have been better added to an already accepted answer or as a comment. Since you're answer doesn't contain any code but link (Which may be corrupt in the future). So please adapt your answer or remove your comment.Invincible
Hi @RaphaëlBalet! Thanks for your response. Sorry, but there's a link with a working project w/ the full source code available at stackblitz.com/edit/stackblitz-starters-keaszp in the answer. Sadly StackOverflow doesn't allow us to embed the StackBlitz sample inside the answer otherwise that would be my first choice. If you prefer to stick with the cold rules instead of offering users the option to see a working sample of a very tricky integration, I'm sorry...Petrifaction
Therefor I do think it would have been better added into an already accepted answer. It would have made the answer even more useful, but thx either way :)Invincible
R
0

With latest doc (v11) and without [CUSTOM_ELEMENTS_SCHEMA] or querySelector :

HTML:

    <div class="swiper">
        <div class="swiper-wrapper">
          @for (slide of slides(); track slide) {
            <div class="swiper-slide">

Ts:

import Swiper from 'swiper';
import { SwiperOptions } from 'swiper/types/swiper-options';

  protected swiper = signal<Swiper>(undefined);
  swiperParams: SwiperOptions = {
    centeredSlides: true,
    slidesPerView: 'auto',
    zoom: true,
  };



 ngAfterViewInit(): void {
    this.swiper.set(
      new Swiper('.swiper', {
        ...this.swiperParams,
        initialSlide: 1,
      })
    );
    this.swiper().on('slideChange', () => {
      // do something
    });
  }

global.scss :

@import '~swiper/swiper-bundle.css';
Rothmuller answered 27/5 at 11:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.