Vuejs: Event on route change - vue.js

On my main page I have dropdowns that show v-show=show by clicking on the link #click = "show=!show" and I want to set show=false when I change the route. Please advise me on how to realize this thing.

Setup a watcher on the $route in your component like this:
watch:{
$route (to, from){
this.show = false;
}
}
This observes for route changes and when changed ,sets show to false

If you are using v2.2.0 then there is one more option available to detect changes in $routes.
To react to params changes in the same component, you can watch the $route object:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// react to route changes...
}
}
}
Or, use the beforeRouteUpdate guard introduced in 2.2:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
Reference: https://router.vuejs.org/en/essentials/dynamic-matching.html

Just in case anyone is looking for how to do it in Typescript, here is the solution:
#Watch('$route', { immediate: true, deep: true })
onUrlChange(newVal: Route) {
// Some action
}
And yes as mentioned by #Coops below, please do not forget to include :
import { Watch } from 'vue-property-decorator';
Edit:
Alcalyn made a very good point of using Route type instead of using any:
import { Watch } from 'vue-property-decorator';
import { Route } from 'vue-router';

Watcher with the deep option didn't work for me.
Instead, I use updated() lifecycle hook which gets executed everytime the component's data changes.
Just use it like you do with mounted().
mounted() {
/* to be executed when mounted */
},
updated() {
console.log(this.$route)
}
For your reference, visit the documentation.

UPDATE
As stated by #CHANist, router.listen no longer works, I don't know from which version it stopped working, but the good news (as also stated by #CHANist) is we can use:
this.$router.history.listen((newLocation) => {console.log(newLocation);})
OLD Response
The above responses are the better, but just for completeness, when you are in a component you can access the history object inside the VueRouter with:
this.$router.history.
That means we can listen to changes with:
this.$router.listen((newLocation) => {console.log(newLocation);})
I think this is mainly useful when used along with this.$router.currentRoute.path
You can check what I am talking about placing a debugger
instruction in your code and begin playing with the Chrome DevTools Console.

import { useRouter } from "vue-router";
const router = useRouter();
router.afterEach((to, from) => { });

Using Vue3 and the composition API you can do
<script setup lang="ts">
import { watch } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
// do a `console.log(route)` to see route attributes (fullPath, hash, params, path...)
watch(
() => route.fullPath,
async () => {
console.log("route fullPath updated", route.fullPath);
}
);
</script>
References and examples here: https://router.vuejs.org/guide/advanced/composition-api.html#vue-router-and-the-composition-api

Another solution for typescript user:
import Vue from "vue";
import Component from "vue-class-component";
#Component({
beforeRouteLeave(to, from, next) {
// incase if you want to access `this`
// const self = this as any;
next();
}
})
export default class ComponentName extends Vue {}

using Vue Router is an alternative way, use the beforeRouteLeave after methods in your component like this:
<template>
<button #click="ShowMethod">DisplayButton</button>
</template>
<script>
data() {
return { show: true };
},
methods: {
ShowMethod() {
this.show = false;
}
},
beforeRouteLeave(to, from, next) {
this.show = false;
next();
}
</script>
according to VueJs documentation, it's called Navigation Guards check the link below:
Navigation Guards
The leave guard is usually used to prevent the user from accidentally
leaving the route with unsaved edits. The navigation can be canceled
by calling
In-Component Guards:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
beforeRouteLeave(to, from, next) {
// called when the route that renders this component is about to
// be navigated away from.
// has access to `this` component instance.
}
look at the below link for more information:
In-Component Guards

you can use the beforeEach event which allows any function to occur when the route is changing, just don't forget to call the next() function to proceed next operation, basically it has the same job as the backend expressJS middleWare.
router.beforeEach((to, from, next) => {
store.commit('setError', null); //in this example on each route I'm removing the error noted from the old route
document.title = `${to.meta.title} | HartWork`; //on each route I'm adding a prefix to document title.
next(); //calling next to proceed next functions and operations
})

I hope you doing well,
in vue3 and script setup this work is too easy:
watch(route, () => { fetch()})
be careful you must import before
import { watch } from 'vue';
import { useRoute } from 'vue-router';
and define use route :
const route = useRoute()

Related

Routes.js call function from composable

I am working on my routes.js and I want to check if user has access to module before redirecting them by calling the function hasAccess() from a composable but it's giving me this warning:
[Vue warn]: inject() can only be used inside setup() or functional components.
routes.js
import { createWebHistory, createRouter } from "vue-router";
import useFN from "./composables/FN"
const routes = [
// define routes here
]
const router = createRouter({
history: createWebHistory(),
routes,
})
const { hasAccess } = useFN() // use function from composable
router.beforeEach(async (to, from, next) => {
console.log(await hasAccess()); // call function from composable
next()
}
export default router;
How can I call a function from composable if I don't have setup()?
Usually I don't have any issues using composables I create for my self as they are usually very basic, but according to Vue 3 - inject() can only be used inside setup() or functional components
composable your composable appears to be using provide/inject which is intended within a setup() method
I will like to propose another approach if viable.
You could call a method(action), getter inside your store instead, if you are using one.
I often use that perform checks before navigating to a route for example if a user account is suspended or the authenticated user does not have the intended privilege
import { createRouter, createWebHistory } from 'vue-router'
import store from '#/store'
...
const account = {
'ACTIVE': 1,
'RESTRICTED': 2,
'DECLINED': 0
}
const routes = [
{
path: '/some-path',
component: () => import('../components/...'),
props: route => ({ reference: route.query.reference }),
meta: { requiresAuth: true },
beforeEnter: async (to, from) => {
if (store.getters['Auth/authUser'].account_status == account.RESTRICTED) {
return false
}
return true
},
},
...
]
```

trigger function after mounting any page in nuxt

Is it possible to write middleware in Nuxt to be triggered after mounting any page.
If I use the following middleware
export default function ({ app }) {
if (!process.server) {
app.$somePluginFunction()
}
}
it is triggered when navigating to that page, thus before mounting it. That is not what I am looking for.
I also know you can use the mounted() hook on an individual page, which is what I want, but I don't want to write the same mounted() hook to every page in my app manually. How can I do this?
There is many way to trigger a function before route change:
First use in default layout
// layout/default.vue
export default {
watch: {
$route () {
console.log('route changed', this.$route)
}
},
}
Second use before route:
https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
Third write plugin like this:
how to write global router-function in nuxt.js
And write mixin like this :
Run function AFTER route fully rendered in Nuxt.js

Get API in components of Notification by Real Time - NuxtJS

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')
}
}

Why isn't router.currentRoute.path reactive?

I have an app which is contained in this div:
<div id="app" v-bind:style='{backgroundColor: backgroundColor}'>
... the app ...
</div>
The routing is done following the example in the documentation (this is a webpack project):
import Vue from 'vue/dist/vue.js'
import VueRouter from 'vue-router'
import ComponentOne from './component1.vue'
import ComponentTwo from './component2.vue'
Vue.use(VueRouter)
const routes = [{
path: '/foo',
component: ComponentOne
},
{
path: '/bar',
component: ComponentTwo
}
]
const router = new VueRouter({
routes // short for `routes: routes`
})
const app = new Vue({
router,
data: {
day: "Monday"
},
computed: {
backgroundColor: function () {
console.log(JSON.stringify(router.currentRoute))
if (router.currentRoute.path == "/foo") {
return "green"
} else {
return "blue"
}
}
}
}).$mount('#app')
I wanted the background to be dependent on the current route (router.currentRoute.path).
But, the solution above does not work, because router.currentRoute.path is not detected by the Vue instance as having changed (is not reactive).
What is the correct way to access the dynamic router data from within the Vue instance?
The router object created via new VueRouter is not reactive because Vue has no way to know to watch and update any object outside of its scope.
Passing router in the Vue config object is what allows the current route to be watched, but you need to reference it via this.$route:
if (this.$route.path == "/foo") {
...
}
You can also access the entire router object via this.$router, but its data is not reactive.
And if you are using Vue 2 with composition api setup() approach you can do this:
import { computed } from '#vue/composition-api'
export default {
setup (props, context) {
const params = computed ( () => context.root.$route.params)
const path = computed( () => context.root.$route.path)
I found on Vue's documentation page that tracks the router using watch for transition animations. Not sure if this is a best practice but you can use to.path or from.path to grab the path instead.
// then, in the parent component,
// watch the `$route` to determine the transition to use
watch: {
'$route': (to, from) => {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}

Update VueJs component on route change

Is there a way to re-render a component on route change? I'm using Vue Router 2.3.0, and I'm using the same component in multiple routes. It works fine the first time or if I navigate to a route that doesn't use the component and then go to one that does. I'm passing what's different in props like so
{
name: 'MainMap',
path: '/',
props: {
dataFile: 'all_resv.csv',
mapFile: 'contig_us.geo.json',
mapType: 'us'
},
folder: true,
component: Map
},
{
name: 'Arizona',
path: '/arizona',
props: {
dataFile: 'az.csv',
mapFile: 'az.counties.json',
mapType: 'state'
},
folder: true,
component: Map
}
Then I'm using the props to load a new map and new data, but the map stays the same as when it first loaded. I'm not sure what's going on.
The component looks like this:
data() {
return {
loading: true,
load: ''
}
},
props: ['dataFile', 'mapFile', 'mapType'],
watch: {
load: function() {
this.mounted();
}
},
mounted() {
let _this = this;
let svg = d3.select(this.$el);
d3.queue()
.defer(d3.json, `static/data/maps/${this.mapFile}`)
.defer(d3.csv, `static/data/stations/${this.dataFile}`)
.await(function(error, map, stations) {
// Build Map here
});
}
You may want to add a :key attribute to <router-view> like so:
<router-view :key="$route.fullPath"></router-view>
This way, Vue Router will reload the component once the path changes. Without the key, it won’t even notice that something has changed because the same component is being used (in your case, the Map component).
UPDATE --- 3 July, 2019
I found this thing on vue-router documentation, it's called In Component Guards. By the description of it, it really suits your needs (and mine actually). So the codes should be something like this.
export default () {
beforeRouteUpdate (to, from, next) {
// called when the route that renders this component has changed,
// but this component is reused in the new route.
// For example, for a route with dynamic params `/foo/:id`, when we
// navigate between `/foo/1` and `/foo/2`, the same `Foo` component instance
// will be reused, and this hook will be called when that happens.
// has access to `this` component instance.
const id = to.params.id
this.AJAXRequest(id)
next()
},
}
As you can see, I just add a next() function. Hope this helps you! Good luck!
Below is my older answer.
Only saved for the purpose of "progress"
My solution to this problem was to watch the $route property.
Which will ended up you getting two values, that is to and from.
watch: {
'$route'(to, from) {
const id = to.params.id
this.AJAXRequest(id)
}
},
The alternate solution to this question handles this situation in more cases.
First, you shouldn't really call mounted() yourself. Abstract the things you are doing in mounted into a method that you can call from mounted. Second, Vue will try to re-use components when it can, so your main issue is likely that mounted is only ever fired once. Instead, you might try using the updated or beforeUpdate lifecycle event.
const Map = {
data() {
return {
loading: true,
load: ''
}
},
props: ['dataFile', 'mapFile', 'mapType'],
methods:{
drawMap(){
console.log("do a bunch a d3 stuff")
}
},
updated(){
console.log('updated')
this.drawMap()
},
mounted() {
console.log('mounted')
this.drawMap()
}
}
Here's a little example, not drawing the d3 stuff, but showing how mounted and updated are fired when you swap routes. Pop open the console, and you will see mounted is only ever fired once.
you can use just this code:
watch: {
$route(to, from) {
// react to route changes...
}
}
Yes, I had the same problem and solved by following way;
ProductDetails.vue
data() {
return {
...
productId: this.$route.params.productId,
...
};
},
methods: {
...mapActions("products", ["fetchProduct"]),
...
},
created() {
this.fetchProduct(this.productId);
...
}
The fetchProduct function comes from Vuex store. When an another product is clicked, the route param is changed by productId but component is not re-rendered because created life cycle hook executes at initialization stage.
When I added just key on router-view on parent component app.vue file
app.vue
<router-view :key="this.$route.path"></router-view>
Now it works well for me. Hopefully this will help Vue developers!
I was having the same issue, but slightly different. I just added a watch on the prop and then re-initiated the fetch method on the prop change.
import { ref, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import Page from './content/Page.vue';
import Post from './content/Post.vue';
const props = defineProps({ pageSlug: String });
const pageData = ref(false);
const pageBodyClass = ref('');
function getPostContent() {
let postRestEndPoint = '/wp-json/vuepress/v1/post/' + props.pageSlug;
fetch(postRestEndPoint, { method: 'GET', credentials: 'same-origin' })
.then(res => res.json())
.then(res => {
pageData.value = res;
})
.catch(err => console.log(err));
}
getPostContent();
watch(props, (curVal, oldVal) => {
getPostContent();
});
watch(pageData, (newVal, oldVal) => {
if (newVal.hasOwnProperty('data') === true && newVal.data.status === 404) {
pageData.value = false;
window.location.href = "/404";
}
});
router - index.js
{
path: "/:pageSlug",
name: "Page",
component: Page,
props: true,
},
{
path: "/product/:productSlug",
name: "Product",
component: Product,
},
{
path: "/404",
name: "404",
component: Error404,
}