Generating a completely blank page from a Vue Router navbar - vue.js

I'm trying to generate a blank page after the user clicks on a navbar button.
I imported a Vue router to my project on codepen.
var demo = new Vue({
// A DOM element to mount our view model.
el: '#main',
// This is the model.
// Define properties and give them initial values.
data: {
active: 'what'
},
// Functions we will be using.
methods: {
makeActive: function(item){
// When a model is changed, the view will be automatically updated.
this.active = item;
}
}
});
When I click on How, and About in the navbar, the text successfully changes to
"You chose: How/about"
so I know the navbar code is working. But I want to generate a completely fresh page.
When I click on How or About, I want the front page text to completely disappear, and a new page to appear for the user. How do I make the title page text disappear, and generate a completely new, black page?

If you are using Vue Router you will need to import that into your application when you create your Vue instance with the new Vue({...}) command. In your example above you are not including Vue Router.
If you include VueRouter you will have to create a routes page that has routes for /how and /about. Then in your Nav you will need to surround the links for these two routes with:
<router-link to="/how">How</router-link>
<router-link to="/about">About</router-link>
These routes will then open the appropriate component you specify in the routes page.

Related

How can I prevent vue-router from replacing the content loaded into one <router-view> when I load content into another <router-view>?

My Vue app contains two main router-views: one inside of a modal, allowing content to be loaded inside said modal, and another inside the main page. I differentiate between them using the name attribute. Here is a piece of code in the routes.js file which would load content into the modal, for example.
export default ({
mode: 'history',
routes: [
{
path: '/classComp/create',
components: {
modalView: createClass
}
}
]
})
Essentially, my issue is that, when I load content into the router, the content in the router-view for the main page disappears. I understand that most people use the children attribute to address this, but that is not feasible in this case. For instance, the user can press a router-link button in the sidebar to load the form to create classes into the modal. The ability to press this button is thus unrelated to what is loaded or not loaded into the main page, so creating a children attribute is not feasible.
In short, how can I get vue-router to load content into the modal's router-view without wiping the content of the mainbox's router-view?
**EDIT: **Someone suggested to use keep-alive for this, but I could not get this to work. Is this a viable suggestion, and, if so, how would I go about it?
Thinking over your use-case again, I don't think nested routes it the way to go.
Unless you really want to have a route that points to your modal, you can use something like portal-vue to push the modal into another view.
If you are using vue 3, it comes with a component that accomplishes something similar: Teleport
EDIT - continuing our conversation from the comments
As I understand it, portal allows you to insert HTML into two separate locations at once from a single route; my issue is that I need to have 2 routes loaded simultaneously.
You're right, portal won't really allow you to change that. The problem is that vue-router does not have native support for simultaneously loading two routes (unless one is a child of the other). A couple things you could try:
Add a modal nested route (route.children). This would allow you to use <router-view name="modal"> and not navigate away from the parent view. I don't think this makes a lot of sense though. You could do it programmatically using router.addRoutes
Have two routers and two vue apps. I don't believe that vue-router has native support for loading two routes at the same time (unless one is a child of the other). You could however have a separate vue instance just for your modal code. This might introduce complexities based on the design of your app though. A quick example:
const appRouter = new VueRouter({ ... })
const modalRouter = new VueRouter({ mode: 'abstract', ... })
Vue.prototype.$modalRouter = modalRouter
const mainApp = new Vue({ router: appRouter })
mainApp.mount('#app')
....
App.vue
-------
<template>
<div id="app">
<router-view></router-view>
<div id="modal-app" v-pre>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
mounted() {
const modalApp = new Vue({ router: this.$modalRouter })
modalApp.mount('#modal-app')
}
}
</script>
You could call this.$modalRouter.push anywhere in your app to update the route for it.
Both of those solutions seem a bit hacky to me. The easiest way to do this is probably not to use vue-router at all. With portal-vue you could mount content into your modal from anywhere in the app. You can even do it programmatically with Wormhole.open().

Why does the browser display cached Vue.js view on route/url change?

I have a homepage with <router-link> tags to views. It is a simple master/detail relationship where the Homepage is a catalogue of products and the Product detail page/view shows information on each item.
When I first launch the website and click on an item on the Homepage view (e.g. URL: http://localhost:8080/100-sql-server-2019-licence), the Product view gets loaded and the product detail loads fine.
If I then press the back button in the browser to return to the Homepage and then click on a different Product (e.g. URL: http://localhost:8080/101-oracle-12c-licence), the URL in the browser address bar changes but I get the previous product's information. Its lightning quick and no new network calls are done which means its just showing a cached page of the previous product. If I then hit the refresh button while on that page, the network call is made and the correct product information is displayed.
I did a search online but couldn't find this problem described on the search results. Could anyone point me in the right direction of how to cause a refresh/re-render of a route when the route changes?
What is happening
vue-router will cache your components by default.
So when you navigate to the second product (that probably renders the same component as the first product), the component will not be instantiated again for performance reasons.
From the vue-router documentation:
For example, for a route with dynamic params /foo/:id, when we
navigate between /foo/1 and /foo/2, the same Foo component instance
will be reused.
The easy (but dirty) fix
The easy -but hacky and not recommended - way to solve this is to give your <router-view /> a key property, e.g.:
<router-view :key="$route.fullPath" />
This will force vue-router to re-instantiate the view component every time the url changes.
However you will loose all performance benefits you would normally get from the caching.
Clean fix: properly handling route changes
The clean way to solve this problem is to react to the route-change in your component (mostly this boils down to moving ajax calls from mounted into a $route watcher), e.g.:
<script>
export default {
data() {
return {
productDetails: null,
loading: false
};
},
watch: {
'$route': {
// with immediate handler gets called on first mount aswell
immediate: true,
// handler will be called every time the route changes.
// reset your local component state and fetch the new data you need here.
async handler(route) {
this.loading = true;
this.productDetails = null;
try {
// example for fetching your product data
const res = await fetch("http://give.me.product.data/" + encodeURIComponent(route.params.id));
this.productDetails = await res.json();
} finally {
this.loading = false;
}
}
}
}
};
</script>
Alternative: Navigation Guards
Alternatively you could also use vue-routers In-Component Navigation Guards to react to route changes:
<script>
export default {
async beforeRouteUpdate (to, from, next) {
// TODO: The route has changed.
// The old route is in `from`, the new route in `to`.
this.productData = await getProductDataFromSomewhere();
// route will not change before you haven't called `next()`
next();
}
};
</script>
The downside of the navigation guards is that you can only use them directly in the component that the route renders.
So you can't use navigation guards in components deeper within the hierarchy.
The upside is that the browser will not view your site before you call next(), which gives you time to load the data necessary before your route is displayed.
Some helpful ressources
Vue Router Navigation Guards Documentation
vue-router github issue
Similar Question about vue-router component reuse on stackoverflow

Vue router reloading the current route

Without reloading the whole page I need to reload the current route again (Only a component reload) in a vue app.
I am having a path in vue router like below,
{
path: "/dashboard",
name: "dashboard",
component: loadView("Dashboard"),
},
When user clicks on the Dashboard navigation item user will be redirected to the Dashboard page with vue router programmatic navigation
this.$router.push({ name: "dashboard" });
But when user already in the dashboard route and user clicks the Dashboard nav item again nothing happens. I think this is vue router's default behaviour. But I need to force reload the Dashboard component (Not to refresh the whole page).
I can't use beforeRouteUpdate since the router is not updated. Also I have tried the global before guards like beforeEach. But it is also not working.
How can I force reload the dashboard component without reloading the whole page?
It can be done in two ways.
1) Try doing vm.$forceUpdate(); as suggested here.
2) You can take the strategy of assigning keys to children, but whenever you want to re-render a component, you just update the key.
<template>
<component-to-re-render :key="componentKey" />
</template>
<script>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
</script>
Every time that forceRerender is called, the prop componentKey will change. When this happens, Vue will know that it has to destroy the component and create a new one.
What you get is a child component that will re-initialize itself and “reset” its state.
Not mentioned here, but as the offered solutions require a lot of additional work just to get the app to render correctly, which imo is a brittle solution.. we have just implemented another solution which works quite well..
Although it is a total hack.
if (this.$route.name === redirect.name) {
// this is a filthy hack - the vue router will not reload the current page and then have vue update the view.
// This hack routes to a generic page, then after this has happened the real redirect can happen
// It happens on most devices too fast to be noticed by the human eye, and in addition does not do a window
// redirect which breaks the mobile apps.
await this.$router.push({
name: RouteNames.ROUTE_REDIRECT_PLACEHOLDER
});
}
... now continue to do your normal redirect.
Essentially, redirect to a placeholder, await the response but then immediately continue to another page you actually wanted to move toward

Element UI NavMenu gets out of sync with current route

I'm using the Element UI NavMenu with :router="true". It is working fine when I click on menu links (route changes and active menu item changes). The issue I'm having is that when I click on the browser navigation buttons (back and forward), the route and component change, but the NavMenu active tab does not change.
Is there an easy way to make sure the NavMenu and current route stay in sync with each other when using the browser navigation buttons? I'm using vue-router with mode: 'history'. I would have thought that this would be handled automatically.
I originally tried to implement this answer with no luck. I now have a working solution for this issue. In my navigation component, I have an el-menu with :router="true" and:default-active="activeLink"`.
Since I have a fairly simple Vue application, I did not want to loop over my router paths and build the NavMenu dynamically. This is a good practice, but I wanted to understand how it works at a basic level first.
From the element-ui docs, default-active controls the index of currently active menu. I added activeLink as a data property:
data() {
return {
logo: logo,
activeLink: null,
}
},
and then added a watch property as described in the gist linked above:
watch: {
$route (to, from) {
this.activeLink = to.path;
}
},
The part I was missing was that the index and the route properties of the el-menu-item need to be the same. Also, we can add a mounted method to make sure that the correct nav link is made active no matter what path we load the app from:
mounted: function(){
this.activeLink = this.$route.path;
},
That fixed the issue of the NavMenu getting out of sync when I use browser navigation buttons.
This was a pain to get to work. I couldn't get beforeRouteUpdate() to work at all, and :default-active="$route.path" almost works, but not if you have parameters for your routes. My current solution is to name all of my routes, and add menu items where the index is the name. Then the default-active value can just be taken from $route.name.
<el-menu :default-active="$route.name" #select="menuSelect">
<el-menu-item index="summary">
<span slot="title">Summary</span>
</el-menu-item>
<el-menu-item index="memory">
<span slot="title">Memory Overview</span>
</el-menu-item>
...
</el-menu>
And in your component:
public menuSelect(index: string) {
this.$router.push({
name: index,
});
}
You can also avoid the annoying error Navigating to current location ("summary") is not allowed like this:
public menuSelect(index: string) {
if (this.$route.name !== index) {
this.$router.push({
name: index,
});
}
}

Vue Router: does this.$router.push navigate to a new URL?

I read the documentation of vue-router (https://router.vuejs.org/guide/essentials/navigation.html)
This is the method called internally when you click a ,
so clicking is the equivalent of calling
router.push(...)
As far as I know clicking router-link element navigates to the URL placed in "to" attribute. However, according to History API
(https://developer.mozilla.org/en-US/docs/Web/API/History_API#Examples), history.pushState(...) only changes the history and does not navigate to a new URL.
So... how can we explain this contradiction?
I think you need to define exactly what you mean by "navigate to a new URL"; to me it can mean either reloading the page at a new URL, or simply changing the URL in the address bar without reloading the page.
history.pushState() does change the URL, but it doesn't cause the browser to perform a full page reload as is typical when you click a link. This is how "single page apps" work – they intercept <a> clicks and use history.pushState() to prevent the page from reloading.
history.pushState(...) only changes the history and does not navigate to a new URL.
Here I think "and does not navigate to a new URL" is wrong – it does, except the page doesn't reload.
There is no contradiction here. There is no reason why the Vue Router could not do a change to the url with the history api and change the component as rendered in various router-view components.
When you include a router-link in your code, this is a component like any other. Vue will render this component. The interesting part is this:
const router = this.$router
// And later
const handler = e => {
if (guardEvent(e)) {
if (this.replace) {
router.replace(location)
} else {
router.push(location)
}
}
}
const on = { click: guardEvent }
if (Array.isArray(this.event)) {
this.event.forEach(e => { on[e] = handler })
} else {
on[this.event] = handler
}
For the history api, you can see in the source that for a this.$router.push(..) we transition, and we push the state with this pushState function. The transition itself can be found in history/base.js.