Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue-router reload components

I have a few routes that each load 3 components. Two of the components are the same on all routes. When I move between those routes I want to pass new data and on some init event of the component I want to fill the data of that component so it reflects on the UI. Also I want to retrigger bootstrap animations of components being loaded. How would I go about doing that. Because right now, I don't know where in the component lifecycle would I fetch the data and rerender the component with this new data. Concretly in myapps/1 and /newapp/ I have a main view component and a sidebar component. In the /newapp/ URL I want all icons on the sidebar to be red (nothing on the main view has been filled) and the main view should be empty. On the myapps/1 I want to load the data from the server into the main view and if it's all filled I want icons on the sidebar to be green. What now happens I load myapps/1, click on the second item in the sidebar and the main view changes to the second view. Then I router.push("/newapp/); and sidebar stays on the second item and second main view. So router.push("/myapps/"); doesn't reload my sidebar and my main view.

EDIT:

Here you see my routes, sidebars and default are the same.

const routes = [
  {
    path: "/myapps/newproject",
    components: {
      default: ProjectMain,
      "breadcrumbs": BreadcrumbsNewProject,
      "sidebar": SidebarProject,
     }
  },
  {
    path: "/myapps/:id",
    components: {
      default: ProjectMain,
      "breadcrumbs": BreadcrumbsCurrentProject,
      "sidebar": SidebarProject,
    }
  },
  {
    path: "/myapps/newversion/:id",
    components: {
      default: ProjectMain,
      "breadcrumbs": BreadcrumbsNewVersion,
      "sidebar": SidebarProject,
    }
  }
];

This is my ProjectMain.vue

<template>
  <div class="wrapper wrapper-content">
    <component :is="currentProjectMain"></component>
  </div>
</template>

This is my router-view in index.html:

<router-view name="sidebar"></router-view>

And I have another one in index.html, the default one:

<router-view></router-view>

When I click on some item on Sidebar i emit event to the ProjectMain to switch out the component. So like this: In Sidebar:

eventBus.$emit("currentProjectMainChanged", "appOverview");

or

eventBus.$emit("currentProjectMainChanged", "appSettings");

And than in ProjectMain:

eventBus.$on("currentProjectMainChanged", function(data) {
  if(data === "appOverview") {
    self.currentProjectMain = CurrentProjectAppOverview;
  }else if(data === "appSettings") {
    self.currentProjectMain = CurrentProjectSettings;
  }
});

If I got to "/myapps/:id". It loads the sidebar and ProjectMain and I get a little animation of the sidebar and the ProjectMain with this bootstrap classes: <div class="animated fadeInUp"> and both components got through the entire lifecycle.

By default appOverview is selected in sidebar and CurentProjectAppOverview.vue is loaded as a component in ProjectMain. Than I click on appSettings in the sidebar and class is added to that item in the sidebar to mark it as selected and in the ProjectMain CurrentProjectSettings.vue is loaded as a component.

But then in the breadcrumbs I have a button to go to "/myapps/newversion/:id" Here is the problem. When I click to go to "/myapps/newversion/:id" (router.push("/myapps/newversion/" + id);) the second item on the sidebar remains selected (appSettings) and in ProjectMain CurrentProjectSettings.vue remains loaded, and I don't get the bootstrap animation of the sidebar and ProjectMain, and the components don't go through their lifecycle.

What I want here when I click the button to go to "/myapps/newversion/:id" is my bootstrap animation (<div class="animated fadeInUp">), I want the Sidebar and ProjectMain to go through their entire lifecycle. And I want the default item to be selected on the sidebar (so appOverview) and default component to be loaded in ProjectMain (so CurentProjectAppOverview.vue).

There are colored buttons for the each item in the sidebar. If I go to "/myapps/newversion/:id" from "/myapps/:id", I want to load the data from the server into CurrentProjectAppOverview.vue and if all data is filled I want the button on the sidebar to be green and if not it should be red.

So In short when moving between this two routes I want to be able to load data and fill the fields I want, and I want bootstrap animation and default views to be selected and loaded and they should go through their entire lifecycle. Now router just reuses them as they are.

So something like "ReloadComponent: true", or destroy and recreate component.

I could duplicate SidebarProject.vue and ProjectMain.vue and rename them and in each route load basically a copy but that would mean I have to write the same code in different .vue files.

Before there was an option to set YouComponent.route.canReuse to false, which would do what I want, I think, but it is removed in the newest version.

like image 571
dari0h Avatar asked Dec 23 '16 17:12

dari0h


People also ask

How do I reload my Vue router?

You can force-reload components by adding :key="$route. fullPath". However, :key="$route. fullPath" only can force-reload the components of the different route but not the components of the same route.

How do you reload a component in Vue?

The best way to force Vue to re-render a component is to set a :key on the component. When you need the component to be re-rendered, you just change the value of the key and Vue will re-render the component.

How do you reset a Vue component?

There are three ways to reset component state: Define key attribute and change that. Define v-if attribute and switch it to false to unmount the component from DOM and then after nextTick switch it back to true. Reference internal method of component that will do the reset.

How do I update my Vue components?

Vue offers us a built-in method called forceUpdate() , by using this method inside vue instance we can force update the component. In the above example, we have attached an update method to the button element.


2 Answers

I was facing the same problem. I will quote a found answer, which is really simple and great. The simplest solution is to add a :key attribute to :

<router-view :key="$route.fullPath"></router-view>

This is because Vue Router does not notice any change if the same component is being addressed. With the key, any change to the path will trigger a reload of the component with the new data.

like image 52
Gkiokan Avatar answered Sep 18 '22 03:09

Gkiokan


Partially thanx to this: https://forum.vuejs.org/t/vue-router-reload-component/4429

I've come up with a solution that works for me:

First I added a new function to jQuery which is used to retrigger bootstrap animation when the component loads.

$.fn.redraw = function(){
    return $(this).each(function(){
        var redraw = this.offsetHeight;
    });
};

In component:

export default{
        created:function(){
            this.onCreated();
        },
        watch: {
            '$route' (to, from) {
                //on route change re run: onCreated
                this.onCreated();

                //and trigger animations again
                $("#breadcrumbsCurrentProject").find(".animated").removeClass("fadeIn").redraw().addClass("fadeIn");
            }
        }
}

I call the onCreated(); method when the component loads and when the route reloads or the ID of the same route changes but the component stays the same I just call the method again. This takes care of the new data.

As for re triggering bootstrap animations I use redraw(); function I add to jQuery right on the start of the app. and this re triggers the animations on URL change:

$("#breadcrumbsCurrentProject").find(".animated").removeClass("fadeIn").redraw().addClass("fadeIn");
like image 39
dari0h Avatar answered Sep 19 '22 03:09

dari0h