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?
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>
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>
Related
I have looked at tutorials and read the papers but I don’t get it why my setup with eventbus does not work.
In main.js
I create a new instance of Vue
/* create a eventbus*/
export const Bus = new Vue();
In page1
import { Bus } from "../main";
I then have a click event that’s triggers a method
methods: {
moveData(inValue) {
let valueToSend = inValue;
console.log("valueToSend");
console.log(valueToSend);
Bus.$emit("emitAlbumTitle", valueToSend);
},
},
And console.log tells there nothing wrong with the method moveData().
In page 2.
I try to listen to the busemit.
import { Bus } from "../main";
data() {
return {
id: this.$route.params.idAlbum,
photoData: [],
albumTitle: "",
};
},
In tried in created(), I have some other things going on there as you see, like an api-call but that should not affect this I think.
async created() {
try {
this.photoData = await CallApi.getPosts(url + this.id);
this.number = this.photoData.length;
Bus.$on("emitAlbumTitle", (data) => {
this.albumTitle = data;
console.log("in the $bus");
console.log(data);
});
} catch (err) {
this.error = err.message;
}
},
But nothing in the console.logs in the Bus.$on starts, so that eventbus never starts?
I also have tried in mounted() hook
mounted() {
Bus.$on("emitAlbumTitle", (data) => {
this.albumTitle = data;
console.log("in the $bus");
console.log(data);
});
},
But same result.
What am I missing here?
The problem is your Receiver component is not created until you click the link, at which point the event has already been emitted from Sender.
One solution is to delay the event emitted until the next macro tick (using setTimeout without a delay), as the Receiver component would be created in the current macro tick:
export default {
methods: {
async emitValue() {
// wait til next macro tick
await new Promise(r => setTimeout(r));
EventBus.$emit("string-send", this.sendString);
},
}
}
demo
Try same as this works for me. Data will show in console on button click.
Here is the main.js file.
import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';
Vue.config.productionTip = false
export const eventBus = new Vue();
new Vue({
vuetify,
render: h => h(App)
}).$mount('#app')
It is App.vue file.
<template>
<v-app>
<div id="app">
{{albumTitle}}
<br>
<button class="primary" #click="dataSend">send</button>
</div>
</v-app>
</template>
<script>
import {eventBus} from '#/main'
export default {
name: "App",
data: () => ({
albumTitle: null
}),
created(){
eventBus.$on("emitAlbumTitle", (data) => {
this.albumTitle = data;
console.log("in the $bus");
console.log(data);
});
},
methods: {
dataSend(){
eventBus.$emit("emitAlbumTitle", "some data")
}
},
};
</script>
I want to use my main vuejs instance to manage sockets.io connection and events. I have this code that works, but I have some problems to pass events from component to parent instance. The code is inside a chrome extension that use vuex, but I'm not familiar with vuex at the moment. How I can pass events between my main instance and child component? Someone has suggested me to use vuex, but it's divided in three files and I'm not able to understand for now how to obtain what I want.
<script>
// child component
export default {
data() {
return {
isRegistered: false,
isConnected: false
}
},
mounted() {
this.$on('connected', function(event) {
console.log(event)
})
},
methods: {
initRoom() {
console.log('clicked!')
this.$emit('openConnection')
}
}
}
</script>
// main instance
import Vue from 'vue'
import App from './App'
import store from '../store'
import router from './router'
import VueSocketIOExt from 'vue-socket.io-extended';
import io from 'socket.io-client';
const socket = io('https://lost-conn.herokuapp.com', {
autoConnect: false
});
Vue.use(VueSocketIOExt, socket, { store });
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
router,
render: h => h(App),
mounted() {
this.$on('openConnection', function() {
socket.open()
alert('k')
})
},
data: {
isRegistered: false,
isConnected: false,
message: ''
},
sockets: {
connect() {
console.log('socket connected')
this.$emit('connected', 'socket connected')
},
},
methods: {}
})
So, you can try vuex but it seems kind of heavy if all you want is a basic event listener. One option might be to go with the eventBus route and set up an emitter and a listener event. in main.js you can add
export const eventBus = new Vue()
Then in your code you could swap this.$emit('connected', 'socket connected')
with eventBus.$emit('connected', true_or_any_other_value_here)
Then in your component that you're listening for the event. Import eventBus from main.js and add:
data: ( => ({ bus: eventBus }),
created() {
this.bus.$on('connected', ($event) => myCallbackFunction($event) )
},
I think this should do the trick, I haven't yet tried the callback function and passing data as I usually do my check on the front end but if there is data object you need to store please specify and I might be able to help you through using vuex.
My first Vue project and I want to run a loading effect on every router call.
I made a Loading component:
<template>
<b-loading :is-full-page="isFullPage" :active.sync="isLoading" :can-cancel="true"></b-loading>
</template>
<script>
export default {
data() {
return {
isLoading: false,
isFullPage: true
}
},
methods: {
openLoading() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 10 * 1000)
}
}
}
</script>
And I wanted to place inside the router like this:
router.beforeEach((to, from, next) => {
if (to.name) {
Loading.openLoading()
}
next()
}
But I got this error:
TypeError: "_components_includes_Loading__WEBPACK_IMPORTED_MODULE_9__.default.openLoading is not a function"
What should I do?
Vuex is a good point. But for simplicity you can watch $route in your component, and show your loader when the $route changed, like this:
...
watch: {
'$route'() {
this.openLoading()
},
},
...
I think it's fast and short solution.
I don't think you can access a component method inside a navigation guard (beforeEach) i would suggest using Vuex which is a vue plugin for data management and then making isLoading a global variable so before each route navigation you would do the same ... here is how you can do it :
Of course you need to install Vuex first with npm i vuex ... after that :
on your main file where you are initializing your Vue instance :
import Vue from 'vue'
import Vuex from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
isLoading: false,
},
mutations: {
openLoading(state) {
state.isLoading = true
setTimeout(() => {
state.isLoading = false
}, 10000)
},
},
})
// if your router is on a separated file just export the store and import it there
const router = new VueRouter({
routes: [
{
// ...
},
],
})
router.beforeEach((to, from, next) => {
if (to.name) {
store.commit('openLoading')
}
next()
})
new Vue({
/// ....
router,
store,
})
In your component:
<b-loading :is-full-page="isFullPage" :active.sync="$store.state.isLoading" :can-cancel="true"></b-loading>
I'm new to Vuejs and still figuring out how to get the latest updated data.
app.js - preload the data after calling 'global/checkUserSignedIn'
import Vue from 'vue'
import axios from 'axios'
import router from './router'
import store from './store'
import { sync } from 'vuex-router-sync'
import App from 'components/app-root'
import { mapGetters, mapActions } from 'vuex'
Vue.prototype.$http = axios
sync(store, router)
const app = new Vue({
store,
router,
...App,
created() {
this.preload()
},
methods: {
preload: function () {
this.$store.dispatch('global/checkUserSignedIn')
}
}
})
export { app, router, store }
global.js (a vuex store)
/// actions, mutations here
const actions = {
checkUserSignedIn({ commit, state }) {
commonApi.isUserSignedIn().then(function (data) {
state.usersignedin = data
})
},
}
const getters = {
isUserSignedIn: state=>state.usersignedin
}
export default {
state,
getters,
actions,
mutations,
namespaced: true
}
On a component, I call:
export default {
computed: {
...mapGetters('global', ['isUserSignedIn']),
},
watch: {
isUserSignedIn: function (newVal, oldVal) {
if (newVal !== undefined) {
console.log('ok, correct result')
}
}
},
updated() {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been re-rendered
console.log('updated: ' + this.isUserSignedIn)
})
},
created() {
console.log('created :' this.isUserSignedIn)
}
}
}
On both Updated and Created, I don't get the last updated data isUserSignedIn which is True if a user has been logged in though if I put {{isUserSignedIn }} on a template, it shows correctly.
Is there any way to get it to work other than having to use a Watcher here.
Updated: Maybe using Watcher is the only way to get the lastest updated data.
I have a dumb question I think but I need your help.
I am creating a Notification components which always get noti by Axios Real Time (Reload everytime) but I'm confusing to make it.
My Notification Components:
<template>
<ul class="tab-content">
<notification-item></notification-item>
</ul>
</template>
<script>
import ItemNotification from '~/components/header/NotificationItem.vue'
export default {
components: {
'notification-item': ItemNotification
},
created () {
this.$store.dispatch('getNotification')
}
}
</script>
Modules Notification: /store/notification.js:
import api from '~/plugins/axios'
const state = () => {
return {
notifications: null
}
}
const actions = {
getNotification ({commit}, config) {
api.get(`/notifications/`, config)
.then(response => {
commit('GET_NOTIFICATION', response.data)
})
}
}
const getters = {}
const mutations = {
GET_NOTIFICATION (state, notifications) {
state.notifications = notifications
}
}
export default {
state,
actions,
getters,
mutations
}
This line this.$store.dispatch('getNotification') doesn't work? How can I do it in the best way or do you guys have example project in Github show me. Please help me !!!
You are using nuxt.js which is server side rendered.
mounted() lifecycle hook is not called during server-side rendering.
So dispatch the action in created() hook
created () {
this.$store.dispatch('getNotification')
}
EDIT:
You can setup a watcher on $route property which will be called whenever the route changes as follows:
watch: {
'$route' (to, from) {
// react to route changes...
this.$store.dispatch('getNotification')
}
}