javascripttypescriptvue.jsvuejs3flickity

Flickity Events + Vue3 + TypeScript: Error - this$refs.flickity.on is not a function


I've created a custom Flickity.vue object for Vue 3 and TypeScript support, following the accepted answer here.

When I try to listen for events on my flickity carousel however, i'm plagued by runtime type errors in my console: this$refs.flickity.on is not a function

What's causing my current approach to break?

DisplayPage.vue

<template>
    <div class="row">
      <div class="col d-block m-auto">
        <flickity ref="flickity" @init="onInit" :options="flickityOptions">
        </flickity>
      </div>
    </div>
</template>

<script lang="ts">
import {defineComponent} from "vue";
import Flickity from "./widgets/Flickity.vue";

export default defineComponent({
  components: {
    Flickity
  },
  data() {
    return {
      flickityOptions: {
        initialIndex: 1,
        prevNextButtons: true,
        pageDots: true,
        wrapAround: false,
      }
    };
  },
  methods: {
    configureBankAccountCarousel() {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (this.$refs.flickity as any).append(this.makeFlickityCell())
    },
    makeFlickityCell() {
      const cell = document.createElement('div')
      cell.className = 'carousel-cell'
      cell.textContent = "Bank Acct"
    }
  },
  mounted() {
    this.configureBankAccountCarousel();
    this.configureBankAccountCarousel();

    this.$nextTick(() => {
      // EVENTS
      (this.$refs.flickity as any).on('ready', function () { //Type ERROR here
        console.log('Flickity is ready!')
      })
    })
  },
});
</script>

Flickity.vue

<template>
  <div ref="root" class="flickity">
    <slot />
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue'
import Flickity from 'flickity'

export default defineComponent({
  props: {
    options: Object,
  },
  setup(props) {
    let flickity: typeof Flickity | null = null
    const root = ref<HTMLElement | null>(null)

    onMounted(() => flickity = new Flickity(root.value as HTMLElement, props.options))
    onUnmounted(() => flickity?.destroy())

    return {
      root,
      append(element: HTMLElement) {
        flickity?.append(element);
        flickity?.select(-1)
      }
    }
  },
})
</script>

<style scoped>
@import '~flickity/dist/flickity.css';

.flickity .carousel {
  background: #EEE;
  margin-bottom: 40px;
}
.flickity::v-deep .carousel-cell {
  height: 200px;
  width: 25%;
  margin: 0 10px;
  background: #6C6;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
}
.carousel-cell {
  background-color: #248742;
  width: 300px; /* full width */
  height: 160px; /* height of carousel */
  margin-right: 10px;
}

/* position dots in carousel */
.flickity-page-dots {
  bottom: 0px;
}
/* white circles */
.flickity-page-dots .dot {
  width: 12px;
  height: 12px;
  opacity: 1;
  background: white;
  border: 2px solid white;
}
/* fill-in selected dot */
.sr-flickity-page-dots .dot.is-selected {
  background: white;
}

/* no circle */
.flickity-button {
  background: transparent;
}
/* big previous & next buttons */
.flickity-prev-next-button {
  width: 100px;
  height: 100px;
}
/* icon color */
.flickity-button-icon {
  fill: white;
}
/* hide disabled button */
.flickity-button:disabled {
  display: none;
}
</style>

flickity.d.ts

interface IFlickity {
    new (el: string | HTMLElement, options?: Record<string, unknown>): this;
    append(element: HTMLElement);
    destroy();
    select(id: string | number);
}

declare module 'flickity' {
    const Flickity: IFlickity;
    export = Flickity;
}

Solution

  • this.$refs.flickity is the Flickity.vue component, and not the underlying flickity instance. Your Flickity.vue component has no on() method, which causes the error you observed.

    You could expose the flickity instance's on() method via a component method in Flickity.vue:

    // Flickity.vue
    export default  defineComponent({
      setup() {
        return {
          on(eventName: string, listener: () => void) {
            flickity?.on(eventName, listener)
          }
        }
      }
    })
    

    And update the type declaration to add on():

    // flickity.d.ts
    interface IFlickity {
        //...
        on(eventName: string, listener: () => void)
    }
    
    declare module 'flickity' {
        const Flickity: IFlickity;
        export = Flickity;
    }
    

    demo