Inform App.vue a Service-Worker Event is Being Called
Asked Answered
B

2

5

I have a project created using Vue CLI 3 with Vue's PWA plugin included. I would like to display a banner prompting the user to click an in-app “Refresh” link as described here in the 'Approach #3' section.

But in my Vue.js app, because the service-worker code is executed in main.js, and my snackbar banner is built into my App.vue component, I'm not sure how to trigger my showRefreshUI() method once the service-worker updated() event has been called.

main.js (applicable portion)

import Vue from 'vue';
import App from './App';
import './registerServiceWorker';

new Vue({
  router,
  render: h => h(App),
}).$mount('#app');

register-service-worker (applicable portion)

import { register } from 'register-service-worker';

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    updated (registration) {
      console.log('New content is available; please refresh.');
      // I'd like to call App.vue's showRefreshUI() method from here.
    },
  });
}

App.vue (applicable portion)

<script>
export default {
  name: 'App',
  mounted () {
    // Alternatively, I'd like to call this.showRefreshUI() from here
    // when the service worker's updated() method is called.
  },

  methods: {
    showRefreshUI () {
      // My code to show the refresh UI banner/snackbar goes here.
    },
  },
};
</script>

If I can't call the showRefreshUI() method from main.js, how might I pass something from the updated() event to App.vue's mounted() lifecycle hook to accomplish the same basic thing?

Barfuss answered 8/12, 2018 at 6:47 Comment(0)
B
10

The final working solution for me was to leave main.js untouched, and instead:

register-service-worker (applicable portion)

import { register } from 'register-service-worker';

const UpdatedEvent = new CustomEvent('swUpdated', { detail: null });

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    updated (registration) {
      console.log('New content is available; please refresh.');
      UpdatedEvent.detail = registration;
      document.dispatchEvent(UpdatedEvent);
    },
  });
}

App.vue (applicable portion)

<script>
export default {
  name: 'App',
  data () {
    return {
      registration: null,
    };
  },
  mounted () {
    document.addEventListener('swUpdated', this.showRefreshUI);
  },
  beforeDestroy () {
    document.removeEventListener('swUpdated', this.showRefreshUI);
  },
  methods: {
    showRefreshUI (e) {
      this.registration = e.detail;
      // My code to show the refresh UI banner/snackbar goes here.
    },
  },
};
</script>
Barfuss answered 9/12, 2018 at 10:47 Comment(0)
N
2

I'm not sure but I think you could use a custom event for this purpose. Something like this might work for you ..

1) Create the custom event in your main.js ..

main.js

import Vue from 'vue';
import App from './App';
import './registerServiceWorker';

const updateEvent = new Event('SWUpdated');

new Vue({
  router,
  render: h => h(App),
}).$mount('#app');

2) Dispatch the custom event when the service worker is updated ..

register-service-worker

import { register } from 'register-service-worker';

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    updated (registration) {
      console.log('New content is available; please refresh.');
      document.dispatchEvent(updateEvent);
    },
  });
}

3) Attach an event listener to the document object in your mounted hook that listens for your custom event. Remove the event listener in the beforeDestroy hook ..

App.vue

<script>
export default {
  name: 'App',
  mounted () {
    document.addEventListener('SWUpdated', this.showRefreshUI);
  },
  beforeDestroy () {
    document.removeEventListener('SWUpdated', this.showRefreshUI);
  },
  methods: {
    showRefreshUI () {
      // My code to show the refresh UI banner/snackbar goes here.
    },
  },
};
</script>
Nondisjunction answered 8/12, 2018 at 9:19 Comment(2)
Well, after a bit of trial and error, this solution worked very nicely! Thank you. I couldn't get it to work when placing the Event Constructor in main.js for some reason. I kept getting updateEvent is not defined error. So I moved that code to register-service-worker and it worked just fine. I discovered I also needed the returned registration object in the updated event over in App.vue so I availed myself of the detail property provided within the custom event syntax as well. So slick. Thanks again.Barfuss
@Doug That's my mistake. I guess you could also just do document.dispatchEvent(new Event('SWUpdated') directly since you're only using it once. Anyway glad things worked out for you in the end :)Nondisjunction

© 2022 - 2024 — McMap. All rights reserved.