Silently update url without triggering route in vue-router
Asked Answered
L

4

62

I need to update the hash of the url, without actually triggering the vue-router to go to that route. Something in the likes of:

router.replace('my/new/path', {silent:true})

This would update the url to .../#/my/new/path, but the router itself would not route to that path.

Is there an elegant way to achieve this?

Lovesick answered 14/7, 2018 at 9:45 Comment(4)
what happen when you used this code ?Foin
Could this be the xy problem..?Bev
@C2486 this code is just an example of what would be an elegant solution. Unfortunately the second argument in the replace funtion should be an onComplete callback.Lovesick
@Rainb, might be. I need to load certain pages of our old application sandboxed in an iframe within our new application. When navigating within this iframe, the url hash in the top window should be updated, just so the history will work properly. As the navigation is already done within the iframe, the route itself should not be updated. Once the user actually navigates with the address bar or by using back/forward, the component with iframe should be updated with the correct url. I know, some red flags there, but I need to use the iframe to be able to sandbox the old application.Lovesick
D
11

The Vue router is either on or off, so you cannot "make it not detect certain urls". That said, Vue does re-use components if it doesn't need to destroy them and allows you to alias urls. This allows you to have the urls from your iframe application as aliases in your route, and keep the same component alive during that time, preventing your iframe from getting reloaded.

// router.js
import Comp1 from "./components/Comp1";
import Comp2 from "./components/Comp2";

export default [
  {
    path: "/route1",
    component: Comp1,
    alias: ["/route2", "/route3", "/route4"]
  },
  {
    path: "/otherroute",
    component: Comp2
  }
];
// main.js
import Vue from "vue";
import App from "./App";
import VueRouter from "vue-router";
import routes from "./router";

Vue.config.productionTip = false;
Vue.use(VueRouter);

const router = new VueRouter({
  routes
});

/* eslint-disable no-new */
new Vue({
  el: "#app",
  router,
  components: { App },
  template: "<App/>"
});

In this example, route1, route2, route3 and route4 will all be treated as if route1 has to be loaded, causing your component Comp1 to stay alive.

Edit Vue Template

Dealing answered 14/7, 2018 at 16:21 Comment(2)
I do not understand how this answers the question. There is no code for the update of the parent window url if the url inside the iframe changes. There is no code for the update of the iframes router if the parent windows url changes.Heptamerous
@Heptamerous The behaviour OP wanted was: (1) the url of the parent changes, (2) vue router should not update the view. The solution here is to use an alias so Vue router treats multiple urls as the same route. This question is not about how to update a child iframe with that information, but rather how to avoid reloading that iframe with every route change.Dealing
B
60

Without reloading the page or refreshing the DOM, history.pushState can do the job.

For example: add this method in your component or elsewhere to do that:

addHashToLocation(params) {
  history.pushState(
    {},
    null,
    this.$route.path + '#' + encodeURIComponent(params)
  )
}

Then anywhere in your component you can call addHashToLocation('/my/new/path') to push query params to the window.history stack.

To add query parameters to current location without pushing a new history entry, use history.replaceState instead.

Should work with Vue 2.6.10 and Nuxt 2.8.1.   Be careful with this method, as Vue Router does not know that the URL has changed.

Bobby answered 20/6, 2019 at 17:40 Comment(2)
To use this approach with a dynamic URL, you can use the resolve() method: const { href } = this.$router.resolve(route); window.history.pushState({}, null, href);Stenophyllous
Safari throws an Exception if the query is changed multiple times quickly (on scroll for example) > SecurityError: Attempt to use history.pushState() more than 100 times per 30 secondsCanicula
D
11

The Vue router is either on or off, so you cannot "make it not detect certain urls". That said, Vue does re-use components if it doesn't need to destroy them and allows you to alias urls. This allows you to have the urls from your iframe application as aliases in your route, and keep the same component alive during that time, preventing your iframe from getting reloaded.

// router.js
import Comp1 from "./components/Comp1";
import Comp2 from "./components/Comp2";

export default [
  {
    path: "/route1",
    component: Comp1,
    alias: ["/route2", "/route3", "/route4"]
  },
  {
    path: "/otherroute",
    component: Comp2
  }
];
// main.js
import Vue from "vue";
import App from "./App";
import VueRouter from "vue-router";
import routes from "./router";

Vue.config.productionTip = false;
Vue.use(VueRouter);

const router = new VueRouter({
  routes
});

/* eslint-disable no-new */
new Vue({
  el: "#app",
  router,
  components: { App },
  template: "<App/>"
});

In this example, route1, route2, route3 and route4 will all be treated as if route1 has to be loaded, causing your component Comp1 to stay alive.

Edit Vue Template

Dealing answered 14/7, 2018 at 16:21 Comment(2)
I do not understand how this answers the question. There is no code for the update of the parent window url if the url inside the iframe changes. There is no code for the update of the iframes router if the parent windows url changes.Heptamerous
@Heptamerous The behaviour OP wanted was: (1) the url of the parent changes, (2) vue router should not update the view. The solution here is to use an alias so Vue router treats multiple urls as the same route. This question is not about how to update a child iframe with that information, but rather how to avoid reloading that iframe with every route change.Dealing
U
6

if what you are trying to do is update the url to show the page state (perhaps something like ?search=something) then you can do a trick like this. the key idea is using a toggle this.respondToRouteChanges to ignore changes when the url is updated by your code.

when updating the route (eg. in a vue method in your component)

          this.respondToRouteChanges = false;
          this.$router.replace({query: { search: this.search })
              .finally(() => {
                this.respondToRouteChanges = true;
              });

and in the component code that would normally watch/handle changes to the route

    setSearch() {
      if (!this.respondToRouteChanges){
        // console.log('ignoring since route changes ignored')
        return;
      }
      if (this.search !== this.querySearch)
        this.search = this.querySearch;
    },

  watch: {
     // watch for changes in the url
    'querySearch': 'setSearch',

note that respondToRouteChanges is just a boolean defined as data() in the component

  data() {
    return {
      respondToRouteChanges: true,
      ...

querySearch is just a computed value which returns the route

    querySearch() {
      return this.$route.query.search || '';
    },
Unexceptional answered 4/3, 2021 at 22:1 Comment(5)
this.$router.replace({query: { search: this.search }) is not triggering a redirect anyway ... seems only a change in the URL path is.Pert
@Pert - there are no redirects, this is about invocations of vue-router. router.vuejs.org/guide/essentials/…Unexceptional
you are right - thanks for pointing that out. This should be the accepted answer. Maybe replace "in the component code that would normally watch/handle changes route" to be less generic and more a router guard example like "in router.beforeEach / component.beforeRouteUpdate"Pert
it could certainly be a router guard, however, since this is overriding a default behaviour, i think it is probably safer to err on adding it to a specific component (principle of least surprise). if you need to do it for many components, then there would be a stronger argument for putting it in the router.Unexceptional
also, i'm not sure you want a component to control some 'respondToRouteChanges' flag in the router - that seems like it could be a bit messy in terms of state/responsibility.Unexceptional
P
-1

I found how to Silently update url, but route anyway is triggered (but this maybe is not a problem - dependents on situation).

In my case user may create a profile, for example via URL http://localhost:8080/#/user/create/, provide some profile info and once "Save" is clicked user is redirected to his/her created profile with URL for example http://localhost:8080/#/FaFYhTW9gShk/, where the user may continue editing the profile form.

How do I do that?

  1. user/create/ page (ProfileCreate) and FaFYhTW9gShk/ page (ProfileDetails) use the same ProfileUser component.
// router.js
const routes = [
    {
        path: '/user/create',
        component: Profile,
        children: [
            {
                path: '',
                name: 'ProfileCreate',
                component: ProfileUser
            }
        ]
    }
    { path: '/:profileUrl',
        component: Profile,
        children: [
            {
                path: '',
                name: 'ProfileDetails',
                component: ProfileUser
            }
        ]
    }
]

  1. In router.beforeEach I check whether I need to load data from the back end, or I need simply to change URL?
// router.js
router.beforeEach((to, from, next) => {
    if (!('isProfileLoaded' in to.params)) {
        // load profile from backend and update Vuex
    }

  next()
})
  1. When "Save" button is clicked, in action I send data to backend + call router.push
// action.js
import router from '@/router'

// Here I send data to backend
// Some code

// Update-URL-Trick
router.push({
    name: 'ProfileDetails',
    params: { profileUrl: profileUrl isProfileLoaded: true }
})

As far as isProfileLoaded is provided, Vue re-uses profile's data from Vuex + router silently updates URL re-using the same ProfileUser component.

Planometer answered 9/12, 2019 at 13:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.