Vue-router: Allow VueRouter to watch reactive data

Created on 5 Mar 2017  ·  7Comments  ·  Source: vuejs/vue-router

I prefer to have all routing configuration logic in one file, so I check if component can be opened in global beforeEach hook. I also need react to property changes fe. isAuthenticated state (vuex) property and redirect according if it's true or false. So I can watch for those changes in:

  • component: by watching property in App.vue root component, but then my routing logic is scattered which is problematic in bigger apps,
  • router configuration file: with store.watch (better, but using store.watch in router configuration it's not right place in my opinion),
  • router configuration file: but as mentioned in topic, make VueRouter able to watch for property change and take appropriate action. This would make also vue router little more data-driven (store could be injected to vue router also?).

This way we could have all routing logic in one place which is very desired I think.

Pseudo code example:

const router = new VueRouter({
  watch: {
    'store.auth.isAuthenticated': function (isAuthenticated, router) {
      if (isAuthenticated) {
        router.push({ name: 'home' })
      } else {
        router.push({ name: 'login' })
      }
    }
  },
  store,
  routes
})
2.x feature request

Most helpful comment

Vue can work with vuex and without it, vue router couldnt work similiar?

Both vue and vuex can handle state, while router only handles routing, so it's not supposed to be used by itself, so it's either vue+router or vue+vuex+router.
For this authentication case, where routing is dependent on state, it's better handled directly by the root instance (which can directly access both the $router and $store). So if you want to keep this auth logic in one place, you can just put it inside app.vue

{
  watch: {
    '$store.(...).isAuthenticated'(isAuthenticated) {
       if (isAuthenticated) {
        ...
       } else {
        ...
       }
     },
  },
  created() {
     this.$router.beforeEach(....)
  }
}

This also avoids coupling store and router by importing store statically inside router/index.js or vice versa. router and store instances can now stay decoupled.

All 7 comments

component: by watching property in App.vue root component, but then my routing logic is scattered which is problematic in bigger apps,

You can simply import it from a Routing dir or similar to keep it on the same place

router configuration file: with store.watch (better, but using store.watch in router configuration it's not right place in my opinion),

It'll be better to create a Vuex plugin (eg https://github.com/vuejs/vuex-router-sync) to handle that. If you're linking vue-router and vuex together, that's definitely a better place to handle it. You can still split up the code however you want.

We cannot link vue with vue-router because you should be able to use them separately. And adding the watch mechanism to the Router is redundant because it already exist in Components and you can use it in the root component, and as you mentioned. Your routing logic doesn't have to be there, you are free to put it somewhere else and import it in your root component.

@fnlctrl WDYT?

Thank's for response.

You can simply import it from a Routing dir or similar to keep it on the same place

To better explain what I mean about to keep routing logic in one place I pasted below pseudo code for two remaining scenarios:

  • Component scenario:

App.vue

   watch: {
     isAuthenticated: function (isAuthenticated) {
       if (isAuthenticated) {
         this.$router.push({ name: 'home' })
         this.$nextTick(function () {
           this.attachSidebarEvents()
         })
       } else {
         this.$router.push({ name: 'login' })
         this.hideSidebar()
       }
     }

router/index.js:

const routes = [
...
]
const router = new VueRouter({
  routes
})
router.beforeEach((to, from, next) => {
  if (authRequired) {
    if (isAuthenticated) {
...
    }
  }
}

Routing logic is scattered between router configuration and App.vue component

  • router configuration file with store.watch scenario:
const routes = [
...
]

const router = new VueRouter({
  routes
})

router.beforeEach((to, from, next) => {
  if (authRequired) {
    if (isAuthenticated) {
...
    }
  }
})

store.watch(
  (state, getters) => state.auth.isAuthenticated,
  function (isAuthenticated) {
    if (isAuthenticated) {
      router.push({ name: 'home' })
    } else {
      router.push({ name: 'login' })
    }
  }
)

But here we mix up store and vue router, which is not good separation..

and the last propostion you already know.

It'll be better to create a Vuex plugin (eg https://github.com/vuejs/vuex-router-sync) to handle that. If you're linking vue-router and vuex together, that's definitely a better place to handle it. You can still split up the code however you want.

Ok, sounds interesting, I will check it..

We cannot link vue with vue-router because you should be able to use them separately

Vue can work with vuex and without it, vue router couldnt work similiar?

Third proposed option allow declaring data driven configuration for vue-router.

Having that on the app component looks good to me

Third proposed option allow declaring data driven configuration for vue-router.

Using a watch is not data driven 😅

Vue can work with vuex and without it, vue router couldnt work similiar?

Both vue and vuex can handle state, while router only handles routing, so it's not supposed to be used by itself, so it's either vue+router or vue+vuex+router.
For this authentication case, where routing is dependent on state, it's better handled directly by the root instance (which can directly access both the $router and $store). So if you want to keep this auth logic in one place, you can just put it inside app.vue

{
  watch: {
    '$store.(...).isAuthenticated'(isAuthenticated) {
       if (isAuthenticated) {
        ...
       } else {
        ...
       }
     },
  },
  created() {
     this.$router.beforeEach(....)
  }
}

This also avoids coupling store and router by importing store statically inside router/index.js or vice versa. router and store instances can now stay decoupled.

@fnlctrl Yes routing logic will be in one place but not the whole vue-router configuration (like router initialization with routes declaration, code is still scattered). Also putting this in App.vue which have template and application logic is not what I wanted to achieve- I dont want to mix those things. So I would have to create another wrapper like AppConfig.vue on the top (to separate it from app and template logic) to setup the routing logic (your example code) which seems to me little overcomplicated (I mean creating component on top) at this state imo only for router configuration (I would preffer to have plain.js file than vue for that). But going further I could put VueRouter initialization with routes declaration (to have it all in one place) in that wrapper but then I guess I would need to inject router manually..

What was most important for me writing in this thread was getting to know if vue router could watch for reactive data changes, in my case it seems to be the best solution imo, but I understand your arguments :)

Currently I use store watch solution, but I was experimenting little today and I end up with this:

router/index.js

const routes = [
...
]

const router = new VueRouter({
  routes
})

router.beforeEach((to, from, next) => {
  if (authRequired) {
    if (isAuthenticated) {
      ...
    }
  }
})

export default Vue.extend({ router, 
  watch: {
    '$store.state.auth.isAuthenticated': function (v) {
      if (isAuthenticated) {
        router.push({ name: 'home' })
      } else {
        router.push({ name: 'login' })
      }
    }
  } 
})

main.js

import RoutingBootstrapper from './router/index.js'

new RoutingBootstrapper(Vue.util.extend({
  store,
}, App)).$mount('#app')

Thanks for your help, time and interesting propositions :+1:

You can simply exports plain objects and merge them or use mixins too.
@fnlctrl Do you think we should close this too? I don't think watching is a mechanism that is going to land in vue router as it already exists in Vue

Yeah.. closing as it's better handled in userland 😄

Was this page helpful?
0 / 5 - 0 ratings