AmazingDreams / vue-matomo

Vue plugin for Matomo Analytics
MIT License
282 stars 61 forks source link

Composition API Provide/inject not ready in time #117

Open bamboechop opened 2 years ago

bamboechop commented 2 years ago

Hello,

I'm currently rewriting my vue-matomo implementation to allow the user to consent to the data tracking. For this reason I want to show a dialog on page load that informs the user about the intended usage of Matomo and asks for his consent.

When looking through the source code I found that vue-matomo provides the Matomo instance during setup.

I'm using the Quasar framework and there I created a boot file that sets up vue-matomo for me. So far, so good.

When I now try to grab the provided Matomo instance via the matomoKey (btw. the whole provide/inject part is missing in the documentation, is this still a WIP?) by injecting it in my dialog component I receive a Vue warning.

[Vue warn]: injection "Matomo" not found. 

When I delay my dialog component by a few seconds the injection works as expected. Now the boot files in Quasar run before the root Vue app instance is instantiated and therefore are the ideal place to do this. I suspect that due to the async nature of what's going on during the install function of vue-matomo (loadScript async, piwikExists check async) the initial app.use(VueMatomo, { ... }) call finishes without waiting for all the async stuff to really be done. This then allows Quasar to continue with rendering the app as the boot file is "done", rendering my dialog who tries to inject Matomo and fails due to Matomo not being provided yet.

I could of course switch to the window variables and use those instead but since you already provide the Matomo instance I'd prefer to use the "correct" way here and inject it whenever I need it. Is there anything that I'm currently overlooking and doing wrong or is the app.use(VueMatomo, { ... }); at the moment indeed not aware of the async stuff happening within the install function?

AmazingDreams commented 2 years ago

Yes, Matomo is not provided until the script is loaded. I'm not familiar with Quasar, but I don't think there's much we can do about that.

bamboechop commented 2 years ago

What is the point of even providing an injection then though if there is no way for vue-matomo to delay initialization of a Vue app. Everything that injects vue-matomo won't get anything because it ain't ready anyways.

adrianwix commented 2 years ago

Maybe related to this topic but is there a way to access $matomo through the composition API? When using Vue.use(VueMatomo) it is not possible to access $matomo inside the composition API

bamboechop commented 2 years ago

Maybe related to this topic but is there a way to access $matomo through the composition API? When using Vue.use(VueMatomo) it is not possible to access $matomo inside the composition API

https://github.com/AmazingDreams/vue-matomo/blob/master/src/index.js#L81

  if (version > 2) {
    Vue.config.globalProperties.$piwik = Matomo
    Vue.config.globalProperties.$matomo = Matomo
    Vue.provide(matomoKey, Matomo)
  }

Since the globalProperties are only an legacy escape hatch and NOT for the setup function you could use getCurrentInstance. But that is not intended for userland, only for internal access and was documented in the v3 docs by mistake. https://stackoverflow.com/a/72940466/7447314

The alternative is to inject Matomo in the components that you want to use it within which then leads to the problem I reported here. Since vue-matomo doesn't wait for Matomo to be loaded your Vue application starts up and some time later Matomo becomes available. Any component that injects Matomo before it is loaded will receive undefined instead and injects are not reactive.

So with Composition API you basically have no way of using the provided Matomo instance, you have to rely on window._paq completely.

Edit: You might be able to use Matomo by injecting when you hide your whole app behind a loading spinner that only goes away once Matomo is loaded and window._paq is available.

<template>
  <div>
    <template v-if="!pageLoading">
      [your app components]
    </template>
    <template v-if="pageLoading">
      [loading spinner component]
    </template>
  </div>
</template>

<script setup>
  const pageLoading = ref(true);

  onMounted() {
    checkMatomoAvailablility();
  }

  function checkMatomoAvailability() {
    if(window._paq) {
      this.pageLoading = false;
    } else {
      window.setTimeout(checkMatomoAvailability(), 1000);
    }
  }
</script>

Then your components should receive Matomo when injecting it. Didn't test the code, just thought about it. :thinking: