Vue lifecycle events triggers on every route - vuejs2

According to the documentation https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram all events from beforeCreate till mounted should be called once. But they are being triggered on every vue-router path navigated
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App },
created: function () {
this.$http.get('/user/get').then(response => {
if (response.data.error) {
console.log(response.data.error)
} else {
User.set(response.data.user)
router.go('/dashboard') // this does force looping
}
}, response => {
router.go('/')
})
}
})
This is App.vue
<template>
<div id="app">
<topmenu></topmenu>
<router-view></router-view>
</div>
</template>
<script>
import Topmenu from '#/components/topmenu'
export default {
name: 'app',
components: {
topmenu: Topmenu
}
}
</script>
The Router.vue
Vue.use(Router)
let route = new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/user/signup',
name: 'Signup',
component: Signup
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard
}
]
})
route.beforeEach((to, from, next) => {
if (to.path.match(/^(\/|\/signup)$/)) {
return next()
}
if (User.valid()) {
return next()
}
route.push('/')
})
export default route
How to avoid this events from being triggered on every route switched?

Related

Vue - Keep default Router-View alive when change another named view

Situation:
I use, beside of the default route-view, a named route-view. I want to keep the DEFAULT route-view alive when I call the ArticleComponent, but as you can see, you can call the ArticleComponent from 2 different routes/components. You can find a fiddle link under the code snippet.
What I want to do:
If I open the ArticleComponent from ListingComponent, then ListingComponent should stay alive in the default route-view.
If I call the ArticleComponent from the FeedComponent, then the FeedComponent should stay alive in the default route-view.
My code:
const HomeComponent = {
template: '<h4>Home</h4>'
};
const FeedComponent = {
template: `<div>
<h4>FeedComponent</h4>
<router-link to="/article/1">Article 1</router-link> -
<router-link to="/article/2">Article 2</router-link>
</div>`
};
const ListingComponent = {
template: `<div>
<h4>ListingComponent</h4>
<router-link to="/article/1">Article 1</router-link> -
<router-link to="/article/2">Article 2</router-link> -
<router-link to="/article/3">Article 3</router-link>
</div>`
};
const ArticleComponent = {
template: `<h4>Article {{ $route.params.id }}</h4>`
};
const routes = [
{
path: '/',
component: HomeComponent
},
{
path: '/feed',
component: FeedComponent
},
{
path: '/listing',
component: ListingComponent
},
{
path: '/article/:id?',
components: {
default: FeedComponent, // <--- dynamically
secondary: ArticleComponent
}
}
];
const router = new VueRouter({
routes
});
new Vue({
el: '#app',
router
});
Fiddle:
https://jsfiddle.net/kvnvooo/b589uvLt/9/
You can use Navigation guards to alter default component dynamically...
{
path: '/article/:id?',
components: {
default: FeedComponent,
secondary: ArticleComponent
},
beforeEnter: (to, from, next) => {
if(from.fullPath === '/listing') {
to.matched[0].components.default = ListingComponent
} else if(from.fullPath === '/feed') {
to.matched[0].components.default = FeedComponent
}
next();
}
}
https://jsfiddle.net/dhmLby6f/7/

VueJS dynamic routes and components

Using cue-cli 3. Is it possible to do this (router.js):
axios.get( `${process.env.VUE_APP_API_DOMAIN}/wp-json/api/v1/routes`).then( r => r.data ).then(routes => {
routes.pages.forEach( (e) => {
router.addRoutes([
{
path: `/${e.slug}`,
component: e.template,
},
]);
});
});
e.template is a string 'Default' and of course VueJS says:
route config "component" for path: /privacy-policy cannot be a string id. Use an actual component instead. Tried with Vue.component(e.template) no luck.
What I want to do here is create dynamic routes based on XHR response.
Here is all router.js code:
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Default from './views/Default.vue'
import Test from './views/Test.vue'
import axios from "axios";
Vue.use(Router);
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
]
});
axios.get( `${process.env.VUE_APP_API_DOMAIN}/wp-json/api/v1/routes`).then( r => r.data ).then(routes => {
routes.pages.forEach( (e) => {
router.addRoutes([
{
path: `/${e.slug}`,
component: e.template,
},
]);
});
});
export default router;
Currently I ended up with this solution:
function getComponent(name) {
let component = null;
switch(name)
{
case 'Default':
component = Default;
break;
case 'Test':
component = Test;
break;
}
return component;
}
axios.get( `${process.env.VUE_APP_API_DOMAIN}/wp-json/api/v1/routes`).then( r => r.data ).then(routes => {
routes.pages.forEach( (e) => {
router.addRoutes([
{
path: `/${e.slug}`,
component: getComponent(e.template),
},
]);
});
});
Another one more cleaner solution:
const components = { Default, Test }
axios.get( `${process.env.VUE_APP_API_DOMAIN}/wp-json/api/v1/routes`).then( r => r.data ).then(routes => {
routes.pages.forEach( (e) => {
router.addRoutes([
{
path: `/${e.slug}`,
component: components[e.template],
},
]);
});
});
If e.template stores the template string,
You should wrap it as one options object like {template: e.template, props: {}, data: function () {} }, then call Vue.extend to construct the component.
or you can ignore Vue.extend because Vue will call Vue.extend to construct the component automatically.
Check the usage at Vue Guide: Vue.component
Edit as the OP states e.tempate is one component name:
if e.template is the name of component, uses Vue.component(e.template).
Vue.config.productionTip = false
const router = new VueRouter({
routes: [
]
})
Vue.component('test', {
template: '<div>I am Predefined component -> {{index}}</div>',
props: ['index']
})
let routerIndex = 1
setInterval(()=> {
let newComponent = routerIndex%2 ? {template: '<div>I am User -> {{index}}</div>', props: ['index']} : Vue.component('test')
router.addRoutes([{
path: '/Test' + routerIndex,
name: 'Test' + routerIndex,
component: newComponent,
props: { index: routerIndex }
}])
console.log('add route = ', '/Test' + routerIndex, ' by ', routerIndex%2 ? 'options object' : 'Vue.component')
routerIndex++
}, 2000)
Vue.use(VueRouter)
app = new Vue({
el: "#app",
router,
data: {
routeIndex: 0
},
watch: {
routeIndex: function (newVal) {
this.$router.push({'name': 'Test'+newVal})
}
}
})
div.as-console-wrapper {
height: 100px;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<p>Current Route Index: {{routeIndex}}</p>
Test Route: <input v-model="routeIndex" type="number">
<router-view></router-view>
</div>

How can I pass data from a component to another component on vue?

I have 2 components
My first component like this :
<template>
...
<b-form-input type="text" class="rounded-0" v-model="keyword"></b-form-input>
<b-btn variant="warning" #click="search"><i class="fa fa-search text-white mr-1"></i>Search</b-btn>
...
</template>
<script>
export default {
data () {
return {
keyword: ''
}
},
methods: {
search() {
this.$root.$emit('keywordEvent', this.keyword)
location.href = '/#/products/products'
}
}
}
</script>
My second component like this :
<template>
...
</template>
<script>
export default {
data () {
return{
keyword: ''
}
},
mounted: function () {
this.$root.$on('keywordEvent', (keyword) => {
this.keyword = keyword
})
this.getItems()
},
methods: {
getItems() {
console.log(this.keyword)
....
}
}
}
</script>
I using emit to pass value between components
I want to pass value of keyword to second component
/#/products/products is second component
I try console.log(this.keyword) in the second component. But there is no result
How can I solve this problem?
Update :
I have index.js which contains vue router like this :
import Vue from 'vue'
import Router from 'vue-router'
...
const Products = () => import('#/views/products/Products')
Vue.use(Router)
export default new Router({
mode: 'hash', // https://router.vuejs.org/api/#mode
linkActiveClass: 'open active',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: '/',
redirect: '/pages/login',
name: 'Home',
component: DefaultContainer,
children: [
{
path: 'products',
redirect: '/products/sparepart',
name: 'Products',
component: {
render (c) { return c('router-view') }
},
children : [
...
{
path: 'products',
name: 'products',
component: Products,
props:true
}
]
},
]
},
{
path: '/products/products',
name: 'ProductsProducts', // just guessing
component: {
render (c) { return c('router-view') }
},
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
]
})
From this code...
location.href = '/#/products/products'
I'm assuming /#/products/products maps to your "second" component via vue-router, I would define the keyword as a query parameter for the route. For example
{
path: 'products',
name: 'products',
component: Products,
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
Then, in your component, define keyword as a string prop (and remove it from data)
props: {
keyword: String
}
and instead of directly setting location.href, use
this.$router.push({name: 'products', query: { keyword: this.keyword }})
There are some ways to do it in Vue.
Use EventBus with $emit like you did;
event-bus.js
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
component1.vue :
import EventBus from './event-bus';
...
methods: {
my() {
this.someData++;
EventBus.$emit('invoked-event', this.someData);
}
}
component2.vue
import EventBus from './event-bus';
...
data(){return {changedValue:""}},
...
mounted(){
EventBus.$on('invoked-event', payLoad => {
this.changedValue = payload
});
}
Use Vuex store, will be accessible at any component, at any page; (my favorite way)
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = () =>
new Vuex.Store({
store: {myStore:{value:0}},
actions:{["actionName"]:function({commit}, data){commit("actionName", data);}}, // I usualy using special constant for actions/mutations names, so I can use that here in code, and also in components
mutations:{["actionName"]:function(state, data){state.myStore.value = data;}},
getters:{myStoreValue: state => !!state.myStore.value}
})
component1.vue
...
methods:{
change:function(){
this.$store.dispatch("actionName", this.someData); //nuxt syntax, for simple vue you have to import store from "./../store" also
}
}
component2.vue
...
data(){return {changedValue:""}},
...
mounted(){
this.changedValue = this.$store.getters.myStoreValue;
}
Use props like #Phil said.

Vue.js App Element update data

I am trying to have a template in App.vue which is main component of my app and it contains navigation bar. However I would like to hide this bar when in login page, but I cannot force App.vue to update. Any help please? :)
App.vue - here I would like to have a flag if I should show toolbar and I want to use it in template. The main problem is that currentRoute.path doesn't get updated automatically. I also tried adding router.afterEach, but didn't manage to make it work.
<script>
export default {
name: 'app',
data() {
return {
msg: 'initial',
showToolbar: router.currentRoute.path !== '/login'
}
},
}
</script>
main.js
firebase.auth().onAuthStateChanged(function (user) {
if (!app) {
/* eslint-disable no-new */
app = new Vue({
el: '#app',
router,
components: {App},
template: '<App/>',
})
}
});
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '#/components/Home'
import Login from '#/components/Login'
import VueMaterial from 'vue-material'
import 'vue-material/dist/vue-material.css'
Vue.use(VueMaterial);
Vue.use(VueRouter);
let router = new VueRouter({
routes: [
{
path: '*',
redirect: '/'
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/',
name: 'Home',
component: Home,
meta: {
requiresAuth: true
}
}
]
});
export default router;
You're currently setting up the initial value of showToolbar, but not setting it up to watch for route changes. For that to work, move showToolbar to the computed section of you App.vue vm:
export default {
name: 'app',
data() {
return {
msg: 'initial',
}
},
computed: {
showToolbar() { return this.$router.currentRoute.path !== '/login' }
}
}
Also, since you are not explicitly importing router in App.vue, you access it in App.vue vm like this.$router, not like router.

vue-router neither watch route or navigation guards firing

Using vue-router in a single page application with the code below, the watch $route function in not firing when redirecting to mycomponent.
Also the beforeRouteUpdate in mycomponent is also not firing.
How can I detect when a variable has been tagged on to a route during component load?
App.vue
<template>
<router-view></router-view>
</template>
<script>
import Vue from 'vue'
export default {
name: 'app'
}
</script>
index.js
import Vue from 'vue'
import Router from 'vue-router'
import MyView from '#/views/MyView'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect: '/home',
name: 'Home',
children: [
{
path: '/mycomponent',
name: 'MyComponent',
component: MyComponentView
},
{
path: '/mycomponent/:id',
component: MyComponentView,
props: true
}
]}]})
mycomponent.vue
<template>
<component :is="activeComponent" :id="id"></component>
</template>
<script>
export default {
name: 'MyComponentView',
components: {
...
},
mounted: function() {
#this logs path in browser
console.log('>>mounted route: ' + this.$route.path)
},
watch: {
'$route': function () {
#this does not fire
console.log('route watcher: ' + this.$route.path)
}
},
beforeRouteUpdate (to, from, next) {
#this does not fire
console.log('>>beforeRouteUpdate')
},
data () {
return {
activeComponent: 'somecomponent'
}
}
}
</script>
component1.vue
...
mounted: function() {
Event.$on('vue-tables.row-click', function(data) {
#this logs correct information in browser
console.log('data.row.id: ' + data.row.id)
router.push({path: 'mycomponent', query: {id: data.row.id}})
})
},
...
It doesn't work because beforeRouteUpdate is in component which is going to reload (Look at Life cycle of Vue). When you change the route, watch & beforeRouteUpdate is terminated and you won't see any results. In this scenario you should provide something like this:
MainRouterView.vue
<template>
<router-view/>
</template>
<script>
name: 'MainRouterView',
beforeRouteUpdate (to, from, next) {
console.log('beforeRouteUpdate')
},
</script>
router.js
export default new Router({
routes: [
{
{
path: '/mycomponent',
name: 'MainRouterView',
component: MainRouterView,
children: [
{
path: '/mycomponent/:id',
component: SecondComponent,
}
]
},
}]})
But if you want to stick up with your structure and check the status of the current route, you can replace beforeRouteUpdate to beforeRouteEnter or beforeRouteLeave in the component. You can use global guard beforeEach in router as well.
To better understand how beforeRouteUpdate works, check out this snippet: http://jsfiddle.net/yraqs4cb/