I currently have the following problem with nested <router-view>s in my app and I want to know if this is even the right way to do it.
I have a navigation.vue route component with child routes configured in the router.
In this component, I have multiple <router-view>s (in a v-for loop).
Every router-view has its own link and if you click on it, the clicked container which holds the router-view will start a transition and reveal the content (the page.vue component).
To fire the transition before confirming the navigation, I listen for the beforeRouteUpdate() hook.
However, I now want to add other navigation components inside this navigation, so that I have something like that:
<navigation>
<page/>
<page/>
<navigation>
<page/>
<page/>
</navigation>
<page/>
</navigation>
The hook to open the sub-navigation seems to work - but if I try to open a page on the second level, the navigation component can't get the $refs that belong to itself. I see the beforeRouteUpdate() hook of the first level navigation being called. I think that's to be expected because it's still in the background, holding the second level navigation and its pages.
What can I do to only use the functionality of the second level navigation when it's opened?
Should I make some checks in the beforeRouteUpdate() hook, and are they both fired?
I'm probably confused because I don't know if the component is being reused or something - in my understanding it should be a second instance of the component.
I'm also using <keep-alive> around the <router-view>s - so if that's a problem and things work differently with that, I'd also be glad to get a hint.
Thanks!
I’m not sure if this will fix your problem but in this vue school video they talk about the Vue Router not always picking up on changes if the same component is being used. You can handle it by adding a key to the router-view like <router-view :key=“$route.path” />. Then any change to the path will trigger a reload of the component. Maybe you can experiment with adding keys to your nested <router-view>s?
I solved it this way:
Both beforeRouteUpdate() hooks are called, so I had to make sure which of the existing navigations should do the work. The upper level navigations skip the hook.
I also needed some checks to only render the navigation in the <router-view> if it is in the $route.match array of the current route.
Related
I’m using Vue & Vuetify to create my app. With vuetify I’m using v-expansion-panels to create an accordion style display. Each v-expansion-panel itself contains a custom component.
I have noticed these components are not created until the expansion panel is clicked for the first time. After that, using keep-alive allows all reactive properties and methods of the child component to be active (this is my desired behavior).
How can I force the child components to be created when the parent is created? This, any method triggered in the created() lifecycle hook of a child component should fire when the parent is created.
This Codepen is an example of the current behavior. Note: be sure to look at the console when you click the panel.
If you think about it, it actually makes sense to lazy load content of expansion panels since it is useless work if the user never opens them anyway. So probably the thing you try to accomplish has some better approach, but if you still like it then my advice is to find a way of programatically opening / closing the panel (as seen here) and quickly open it and close it when rendering parent component. In this way, you will have your child component created and the UI will remain the same.
A Vuetify solution should be achievable by adding the eager prop to a v-expansion-panel-content element in the Expansion Panel. This should force any components or content contained within the v-expansion-panel-content element to render on mounted.
<v-expansion-panels v-model="panels">
<v-expansion-panel>
<v-expansion-panel-content eager>
<custom-component />
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
I am attempting to access a child component's $refs inside a b-modal.
On page load, I can see with vue dev tools that "agent-edit" has not been created. If I put the component outside of b-modal, it does show and I can access it -- however I need this to load inside a modal. How can I access $refs.editAgent? Can I force this child component to load with the page?
<b-modal id="editModal" ref="editModal" title="Edit Agent" size="lg">
<agent-edit ref="editAgent"></agent-edit>
<div slot="modal-footer" class="w-100"></div>
</b-modal>
Refs are relative to the component they are created in (not the child components)
// use this
this.$refs.editAgent
// Not this
this.$refs.editModal.$refs.editAgent
Note that b-modal is lazy by default, meaning the content is not rendered (instantiated) in the document until the modal is shown.
Once the modal is finished opening, you should have access to the refs (they don't exist until they are rendered into the DOM)
Listen for the modal's shown event, and then access the refs once that event is emitted.
I guess, that there is no <agent-edit> inside <b-modal>, when you try to call the method.
When the modal is hidden, there is no need to render the child components. Try to first show the modal and then access its children (maybe even with a Vue.$nextTick to make sure everything is finished).
In your case, this.$refs.editModal.$refs.editAgent should work.
But pay attemption to the use of $refs and think about emitting events.
I have issue with having keep-alive actually keeping components alive.
Component that is being rendered in router-view have async fetching after component is mounted. My issue is that after the first time component shows up, when I render other component in that very same router, and then go back, then first component rerender as normal instead of keeping fetched data as it was.
I checked hooks and besides activated and deactivated also created hook fires which I suppose shouldn't be the case beyond first render. Also when I switch components destroyed hook fires which also shouldn't happen.
.container-fluid
.row.wrapper
aside.col-12.col-sm-2.p-0
nav.navbar.navbar-light.navbar-expand-sm.align-items-start.flex-sm-column.flex-row.text-uppercase#navbar1
a.navbar-toggler(href='', data-toggle='collapse', data-target='.sidebar')
span.navbar-toggler-icon
.collapse.navbar-collapse.sidebar
ul.flex-column.navbar-nav.w-100.justify-content-between
li.nav-item
router-link.nav-link.pl-0(to='candidates' data-toggle="collapse" data-target=".navbar-collapse.show")
font-awesome-icon.fa-fw.mr-2(:icon="iconTachometer")
| Dashboard
main.col.bg-faded.py-3
.card
.card-body
keep-alive
router-view(:key="$route.fullPath")
Okay, I found the answer - and my apologies because turned out my question wasn't fully informed.
First thing - the component in question was already nested within another router-view so what I was actually doing was nesting one in another.
Therefore, to keep alive that nested/child router-view parent router-view also has to be wrapped with keep-alive.
Based on answer here: https://forum.vuejs.org/t/how-to-use-keep-alive-with-nested-router-component/46813/4
See Special Attributes - key:
It can also be used to force replacement of an element/component
instead of reusing it. This can be useful when you want to:
Properly trigger lifecycle hooks of a component;
Trigger transitions.
If you bind key to $route.fullPath, it will always force a replacement of the <router-view> element / component every time a navigation event occurs. So just remove :key.
Let's suppose I have a component called FirstPage, which is my default route, now FirstPage triggers an asynchronous call, with the help of an action of the vuex store, to be made each minute to a backend Api (it's triggered when the component is loaded as a route), now let's say I go to an about route that goes to an About component, is FirstPage still making the calls?
Edit:
I'm not developing an app with that yet, so I can't provide examples.
It's on my interest to know the behavior in these cases of the router, because whenever I change the route I would want to stop making the constant calls (as they won't be necessary).
The reason is that Depending on this I'd have to switch tooling for a project I have in mind.
In general, a component's instance will be destroyed when you navigate away from it. However, there are two exceptions to this ..
When you use routes with params. From the Vue Router docs
One thing to note when using routes with params is that when the user navigates from /user/foo to /user/bar, the same component instance will be reused. Since both routes render the same component, this is more efficient than destroying the old instance and then creating a new one. However, this also means that the lifecycle hooks of the component will not be called.
When you wrap your router-view component within a keep-alive element. Since the <router-view> is essentially a dynamic component.
Generally Vue does a very good job of housekeeping and cleaning up after a component's instance when it gets destroyed. But sometimes you'll have to do some manual cleanup, especially if you use some kind of external library. This is usually handled in the beforeDestroy hook of an instance's lifecycle.
In normal conditions, any logic/scripts/etc done at creation inside said component will be "purged" on the on destroy/close hooks (not only pertinent to vue but seen in lots of other tools), if there is a need to persist something then it should be in a higher scope (or other solution)
Any script written for the respective component only runs if the component is rendered in page. Once you go to about component replacing the previous component then previous script wont run.
you can make a parent component with a router-view and load in your page you always want to get loaded, so your FirstPage component, but this component should just have logic behind it, and no html because otherwise you will always see that rendered. Router-view the page you want to display the real html and stuff. I hope you get the idea, if not i could make an example for you. Goodluck.
I know in vue, I can use this.$parent to get the upper component in the vdom tree. But I'm expecting something different: to get the component that rendered the current component.
For instance, I have a component (named comp-container) with template:
<template>
<comp-a>
<comp-b></comp-b>
</comp-a>
</template>
And in comp-b the $parent would be an instance of comp-a not comp-container which I'm expecting.
My current aproach is traversing up with the $parent attribute until I find comp-b exists in $options.components. This method is working for now but seems quite ugly and breaks if comp-b is a globaly registered component. Is there an official way to do this?
Passing the parent template component via props as <comp-b :container="this"></comp-b> may do the job, but it's too verbose to be liked.
I'm not sure about the exact use case, but basically if there are slots involved (which I almost assume, because otherwise $parent will work fine), you can find the rendering component at:
this.$slots.default[0].context;
Basically, the context property of a slot is the rendering context (rendering component - i.e. the component who's template the component was rendered in).
Only tested with Vue 2