最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Vue Router - call function after route has loaded - Stack Overflow

programmeradmin1浏览0评论

I'm working on a project where I need to call a function AFTER the route has finished loading. However, when using the 'watch' functionality, it only loads on route change, but does so before route has finished loading. So when I attempt to run a script that targets DOM elements on the page, those elements don't exist yet. Is there any functionality in Vue Router that would allow me to wait until everything is rendered before running the script?

const app = new Vue({
    el: '#app',
    router,
    watch: {
        '$route': function (from, to) {
            function SOMEFUNCTION()
         }   
    },
    data: {
        some data
    },
    template: `
      <router-view/>
  `
})

I'm working on a project where I need to call a function AFTER the route has finished loading. However, when using the 'watch' functionality, it only loads on route change, but does so before route has finished loading. So when I attempt to run a script that targets DOM elements on the page, those elements don't exist yet. Is there any functionality in Vue Router that would allow me to wait until everything is rendered before running the script?

const app = new Vue({
    el: '#app',
    router,
    watch: {
        '$route': function (from, to) {
            function SOMEFUNCTION()
         }   
    },
    data: {
        some data
    },
    template: `
      <router-view/>
  `
})
Share Improve this question asked Sep 23, 2018 at 16:31 RJ McLainRJ McLain 1411 gold badge1 silver badge3 bronze badges 2
  • By adding this to the template itself beforeRouteEnter (to, from, next) { next(vm => { SOMEFUNCTION() }) } That seemed to solve all of my needs – RJ McLain Commented Sep 23, 2018 at 18:10
  • Possible duplicate of Vue.js Router: Run code when component is ready – Michael Mrozek Commented Apr 11, 2019 at 17:44
Add a comment  | 

5 Answers 5

Reset to default 6

You should use Vue.nextTick

In your case this would translate to:

const app = new Vue({
    el: '#app',
    router,
    watch: {
        $route() {
            this.$nextTick(this.routeLoaded);
         }   
    },
    data() {
        return {};
    },
    methods: {
        routeLoaded() {
           //Dom for the current route is loaded
        }
    },
    mounted() {
/* The route will not be ready in the mounted hook if it's component is async
so we use $router.onReady to make sure it is.
it will fire right away if the router was already loaded, so catches all the cases.
Just understand that the watcher will also trigger in the case of an async component on mount
because the $route will change and the function will be called twice in this case,
it can easily be worked around with a local variable if necessary
*/
       this.$router.onReady(() => this.routeLoaded());
    },
    template: `<router-view/>`
})

This will call the routeLoaded method every time the route changes (which I'm deducing is what you need since you are using the <router-view> element), if you also want to call it initially, I would recommend the mounted hook (like in the example) or the immediate flag on the watcher

In my opinion on this situation, you should use component life cycle method of the loaded component, either use mounted method or created method.

or if your script doesn't depend on any vue component (store) you can use router.afterEach hook

router.afterEach((to, from) => { if (to.name !== 'ROUTENAME'){ // do something }});

The solution for me was to set up a custom event in every page's mounted() hook with a mixin and listen for that event on the body for example. If you wanted to strictly tie it with the router's afterEach or the route watcher to ensure the route has indeed changed before the event was fired, you could probably set up a Promise in the afterEach and resolve it in the page's mounted() by either the event or sharing the resolve function through the window.

An example:

// Component.vue

    watch: {
      '$route': function (from, to) {
        new Promise((resolve) => {
          window.resolveRouteChange = resolve;
        }).then(() => {
          // route changed and page DOM mounted!
        });
      }
    }

// PageComponent.vue

    mounted() {
      if(window.resolveRouteChange) {
        window.resolveRouteChange();
        window.resolveRouteChange = null;
      }
    }

In case of router-view, we can manually detect router-view.$el change after $route is changed

watch: {
        '$route'(to, from) {
            // Get $el that is our starting point
            let start_el = this.$refs.routerview.$el
            this.$nextTick(async function() { await this.wait_component_change(start_el)})
        }
    },
    methods: {
        on_router_view_component_changed: function() { }
        wait_component_change: async function(start_el) {
            // Just need to wait when $el is changed in async manner
            for (let i = 0; i < 9; i++) {
                console.log('calc_has_dragscroll ' + i)
                if(start_el) {
                    if (!start_el.isSameNode(this.$refs.routerview.$el)) {
                        // $el changed - out goal completed
                        this.on_router_view_component_changed()
                        return
                    }
                }
                else {
                    // No start_el, just wait any other
                    if(this.$refs.routerview.$el) {
                        // $el changed - out goal completed too
                        this.on_router_view_component_changed()
                        return
                    }
                }
                await this.$nextTick()
            }
        },
    }

You can accomplish this by hooking into VueJS lifecycle hooks:

  1. Use VueJS Lifecycle Hooks: Here is a summary of the major VueJS lifecycle hooks. Please consult the documentation for the full description.

i. beforeCreate: This function will be called before the component is created

ii. created: This function will be called after the component is created, but note although the component is created, it hasn't been mounted yet. So you won't be able to access the this of the component. However, this is a good place to make Network Requests that will update the data properties.

iii. mounted: This function is called once the component has been rendered and the elements can be accessed here. This is what you're looking for.

iv. beforeDestroy: This function is called before the component is destroyed. This can be useful to stop any listeners (setTimeout, setInterval..), that you created.

See the diagram below for the details.

const app = new Vue({
    el: '#app',
    router,
    mounted(){
      this.someFunction()
    },
    data: {
        some data
    },
    template: `
      <router-view/>
  `
})
  1. Use Vue Router Navigation Guards: Vue Router also expose some lifecycle hooks that can you hook into. However, as you will see below they do not fit your requirements:

i. beforeRouteEnter: called before the route that renders this component is confirmed. oes NOT have access to this component instance, because it has not been created yet when this guard is called!

ii. beforeRouteUpdate: called when the route that renders this component has changed, but this component is reused in the new route.

iii. beforeRouteLeave: called when the route that renders this component is about to be navigated away from.

References:

VueJS Documentation (LifeCycle): VueJS Instance

Vue Router Documentation (Navigation Guards): Navigation Guards

发布评论

评论列表(0)

  1. 暂无评论