0

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;
}
tony19
  • 125,647
  • 18
  • 229
  • 307
Cody
  • 1,801
  • 3
  • 28
  • 53

1 Answers1

1

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

tony19
  • 125,647
  • 18
  • 229
  • 307
  • This compiles successfully but I never see any log message printed to the console in your demo app. Can you confirm that your listener is successfully triggered? – Cody Jul 03 '21 at 04:48
  • I don't see it either but I'm not familiar with the events from that library (beyond the scope of the question). – tony19 Jul 03 '21 at 04:51
  • Gotcha. That event in particular might be bugged at the lib level, i'm able to see `change` events just fine in the console with the following code. Thanks @tony19 ` (this.$refs.flickity as any).on('change', ( index: number ) => { console.log('Slide changed to index ' + index) })` – Cody Jul 03 '21 at 04:52
  • 1
    If you're going to be using `this.$refs.flickity` frequently in that parent component, I recommend switching to the Composition API to avoid the repetitive type assertions. – tony19 Jul 03 '21 at 04:53