How to set the page title in Vue 2, that will work on page load, from the component - vuejs2

I am trying to set the page title from the component as there are dynamic properties that will change the content of the title.
Using the examples I have found on here talk about setting the this.$route.meta.pageTitle. This only works when the page is navigated to via the application, not on a page refresh or load from bookmark.
I have tried to hook into all the lifecycle hooks, both in the router and the component, as well as adding a watcher in my root component.
How do I set the title on those load conditions?

In the component, you need to set the document.title rather than the route metadata.
created () {
this.$route.meta.title = `${myDynamicProp} - my site`
document.title = this.$route.meta.title;
}

Related

Nuxt.js footer component is not executing code to retrieve data

We have a nuxt.js application which retrieves data from strapi. this works for all the other pages that we've created, but when we try to retrieve data for the <Footer /> it appears that the code is not executing.
This is the code that we use for retrieving on the index page:
export default {
async asyncData({ $strapi }) {
return {
homepage: await $strapi.find("homepage"),
};
},
}
All we change from page to page is the variable name and the value its finding.
This works on 10 - 12 pages.
On main pages we are able to retrieve the data of the footer with this code:
export default {
async asyncData({ $strapi }) {
return {
footer: await $strapi.find("footer")
};
},
}
However when we put this code in our footer component it doesn't appear to execute, as no variable was shown in the view explorer, and if we try an render anything form the {{footer}} then we get an error saying we've referenced something that doesn't exist.
Is there any reason why this code isn't executing in the footer component?
The asyncData hook can only be used on page components. The official documentation explains how you can work around this issue:
asyncData is only available for pages and you don't have access to this inside the hook.
Use the new fetch hook that is available in Nuxt 2.12 and later versions.
Make the API call in the mounted hook and set data properties when loaded. Downside: Won't work for server side rendering.
Make the API call in the asyncData method of the page component and pass the data as props to the sub components. Server rendering will work fine. Downside: the asyncData of the page might be less readable because it's loading the data for other components.

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

how do I bring up "Are you sure you want to leave page?" popup in vue.js router?

I'm building a vue.js application. We'd like to have a popup come up when the user attempts to leave a specific page. The popup should say "Are you sure you want to leave the page?" I know I can implement something in the beforeRouteLeave hook of the component, but I'm wondering if there's a way to implement this in the beforeEach event of the router (i.e. not the component). The reason I'd like to use the router is because beforeEach in the router seems to respond to the user entering a different path in the browser url bar, whereas the beforeRouteLeave hook on the component does not. However, I don't have access to the popup in the router whereas I do in the component (the popup would just be part of the template).
So the question is: how can I bring up a popup in the router before the user actually leaves the page?
Thanks.
First you can assign a name for each of your routes objects in routes array inside your router or another field like requiredConfirmation or something like that, imagine that we have a routes like this :
routes : [
{
path : '/needconfirm',
component : NeedConfirmToLeaveCom,
name : 'needconfirm-route1'
},
{
path : '/neednotconfirm',
component : NeedNotConfirmToLeaveCom,
name : 'normal-route1'
},
]
then you can use router.beforeEach to set some conditions or some confirmations based on your Origin route and Destination route.
something like this :
router.beforeEach((to,from,next) => {
if(from.name.startsWith("needconfirm-")) {
if(window.confirm("Are you sure you want to leave the page?")) {
next();
}
}else next();
});
UPDATE * :
if you want to use some custom components for your popup, you can use vuex to store your component's logic and toggler and import that component in your App.Vue or other root/child components you wish. because you have access to your store management using $store right?
UPDATE ** :
and one other thing i want to mention, if you want to save some progress or state and because of that you want to get confirmation from user (progress will lost if they switch route), you should consider using Vuex to store your progress or state of your application and if you want more persisted solution you can use VuexPersisted store management which uses LocalStorage.
Vue router navigation guards document
Vuex Doc
You should use beforeunload event listener on the main component in that view.
MDN Reference
Depending on the browser, it will show the popup with default values populated.
This is how I use it in the created hook of the main component
window.addEventListener("beforeunload", (e) => {
e.preventDefault()
// chrome requires returnValue to be set
const message = "You have unsaved changes. Are you sure you wish to leave?"
e.returnValue = message
return message
})

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.

Vue.js - handling metadata with vue-simple-headful

I use vue-simple-headful plugin to work with metadata in vue.js app , page title, description, keywords, etc...
I have the following setup in my vue app:
main.js
import vueHeadful from 'vue-simple-headful';
Vue.use(vueHeadful, {component: true});
component's function:
headful(vm) {
return {
title: this.category.header,
description: 'yay, a static description'
}
The page title is updated from this.category.header, but only once, during the first call to the component. Following calls to the component with the headful function do not update the page title.
I would like it to be dynamic, that is whenever component's function is called page title and other metadata is updated accordingly.
I ended up using vue-headful instead of vue-simple-headful. Following the example from How to set the Document with Vue.js has done the trick.