This question already has answers here:
How can I bind the html <title> content in vuejs?
(9 answers)
Closed 5 years ago.
I'm building a web application using vue.js v1.0, vue-router v0.7 and WebPack. I'm following Single File Component pattern and have different components for each page.
I don't know how I could change page title in different routings (or maybe different components) when I am navigating through the web app pages. I also want page titles to be available in browser history.
In addition to my earlier solution posted here, there is a second method that I found after a bit of research: use Navigation Guards
As detailed in my previous answer, here is the problem: vue-router may start reusing route components after getting created for the first time. There is really no need to destroy these components on route-exit, and re-create on subsequent route-entry. Therefore the created hook in my earlier solution may not fire on subsequent visits to the same route. Therefore our window title may not work as expected.
To overcome that problem, we can set the window title on a route-change event. The router instance has a afterEach hook that gets called after route change. This can be used to set window title as detailed below:
// Let's say this is your router instance, with all "Named Routes"
const ROUTER_INSTANCE = new VueRouter({
mode: "history",
routes: [
{ path: "/", name: "HomeComponentName", component: HomeComponent },
{ path: "/about", name: "AboutComponentName", component: AboutComponent },
{ path: "*", name: "RouteErrorName", component: RouteError }
]
})
// Assign page titles for each of the "named routes"
// Reason: This allows us to have short named routes, with descriptive title
const PAGE_TITLE = {
"HomeComponentName": "Your Dashboard",
"AboutComponentName": "About Us Page",
"RouteErrorName": "Error: Page not found"
}
ROUTER_INSTANCE.afterEach((toRoute, fromRoute) => {
window.document.title = PAGE_TITLE[toRoute.name]
console.log(toRoute) // this lets you check what else is available to you here
})
This may still not help you if you are navigating between similar routes, like "/user/foo" to "/user/bar". If you want user name in the titlebar or some dynamic page specific info, check out Reacting to Params Changes as detailed in http://router.vuejs.org/en/essentials/dynamic-matching.html. Based on docs, we should be able to use watch in component as follows:
watch: {
'$route' (toRoute, fromRoute) {
window.document.title = "some page title"
}
}
Hope it helps!
I also had the same problem few days ago, and I resolved as follows in my route component definition:
export default {
created: function() {
window.document.title = "Page Title for this route"
...
},
...
}
That's really not the correct way of doing it. Reason: I am making a big assumption that the route component gets created everytime on changing to a new route. It is true in vue-router for now, but may change in future.
I was using ui-router in Angular 1.4 earlier, which allows route components to live in memory (sticky states), so that the route change is instantaneous next time. If vue-router ever implements something similar to sticky states, my above method of setting title in created hook will fail.
But till that happens, you may use this solution.
I've got a solution and used it on one my projects.
First create a directive.
Vue.directive('title', {
inserted: (el, binding) => document.title = binding.value,
update: (el, binding) => document.title = binding.value
})
Suppose, we are working on 'MyComponent.vue' file.
Then use that directive on the router-view component.
<router-view v-title="title" ></router-view>
export default {
data(){
return {
title: 'This will be the title'
}
}
}
This works even if the component is updated or the page is reloaded.
Worked very well for me!!
Related
I am creating dynamic pages using Nuxt. In the pages folder I have one file _url.vue. It contains the following code:
<template lang="pug">
div
component(
v-for="component in components"
:key="`${component.type}-${component.id}`"
:is="`the-${component.type}`"
)
</template>
<script>
// import vuex
export default {
computed: {
...mapGetters('app', {
components: 'getComponents'
})
}
}
</script>
setComponents happens at the middleware level:
export default async function ({ store }) {
await store.dispatch('app/setPage')
}
In the first milliseconds of page load, the content "jumps" as the components are rendered on the fly. How can this situation be corrected?
I'd first try to import the components manually, to see where this all comes from: the components taking some time to get injected or the layout being displayed, just to be sure.
Then, I had a discussion about it here, you may give it a look: Vue: wait to render until all components are mounted
There are several ways of handling this kind of micro-jumping explained there. You can choose your own solution. Also depends if you're using your app as universal or SPA only.
Looks like require is a way to go but some alternative are also available.
I am creating a VueJS app in which the user fills out a 5-step form.
These steps are routed to /step-1 through /step-5 in the Vue Router. However, I would like the site to return to the index page (/) when refreshing the page.
I could use abstract mode for this – but the result page is generated from the following url: /result/:userid in which I need the state to be history in order to be able to get the userid from the URL (and then do a post request to the server).
I also want this URL to be accessible even after finishing the form, so abstract here is not an option unfortunately.
So – is it possible to use both modes? Refresh the page to index.html when refreshing the form-pages, but then use history mode to render the result?
You cannot do this. It is either history or abstract but not both. Having said this, there are a couple of things you can do.
Approach 1: Use history mode with steps as query params
So instead of having routes like /step-1 or /step-2, use then as part of query params. So you will have routes like:
Index route: example.com/?step=1, example.com/?step=2
Result route: example.com/result/:userId
Approach 2: Use abstract mode with higher order component
Here, you will have a router with abstract but it will only serve as a state router and won't help with any browser URL manipulation.
Build a higher order component like AppComponent where you will have your own regular expressions to determine the route. It would look like:
// Should match route /result/:userId
const IS_RESULT = new RegExp('/result/(\\d+)$');
// User RegExp groups
const IS_STEP = new RegExp('/step-(\\d+)$');
export default class AppComponent extends Vue {
// Use Vue.js created hook
created() {
const path = window.location.pathname;
// Top level routing of the application
if (IS_STEP.test(path)) {
// Do something. You are in step-1/step-2/etc.
} if (IS_RESULT.test(path)) {
// Do something. You are in /result/:userId
// Get userId
const groups = IS_RESULT.exec(path);
const userId = groups[1];
} else {
// Goto Error page
throw new Error('Unknown route');
}
}
}
Approach 3: Use Multi-page SPA
Here, you will create two single page application. The first app will have routes /step-1, /step-2, etc. You will use abstract mode for this. The second application will have /result/:userId route with history mode.
In this architecture, when a user is on step-5, instead of pushing a new state to the router, you will use HTML5 history API to change the router and then cause a forced page refresh. Also, there are other ways you achieve this.
You can simply have a redirect in native javascript where you call it window.location.href('yourHomePage.com') and it will do a full refresh.
I am using the Creative-Tim Dashboard to develop a small application and I realize the components loaded on each page are destroyed and re-created each time I switch pages from the sidebar.
I use a global Vue Mixin to my application to send and receive MQTT messages. The methods created and beforeDestroy are called each time I switch panels.
Is there a way to:
Keep my Mixin alive
Keep my components data alive
As an example one of my component is a MQTT Widget:
<template>
<Widget :title="title" :subtitle="subtitle" :footer="topic">
<h1>{{value}}</h1>
</Widget>
</template>
<script>
import Widget from './Widget.vue'
export default {
name: 'numeric-widget',
components: {
Widget
},
props: {
title: String,
subtitle: String,
topic: String,
unit: String
},
data () {
return {
value: '--'
}
},
mounted () {
// Subscribe method is exposed by a global Vue Mixin
this.subscribe(this.topic, (topic, payload) => {
this.value = payload
})
}
}
</script>
Here what happens:
Load the page (seeing --)
Receive a MQTT value (seeing `80 bpm')
Switching to another page
Method beforeDestroy of my Mixin is called
Switching back to the dashboard
Method created of my Mixin is called
I see -- as if I never received any message.
I have seen on many question that using <keep-alive> may help. Unfortunately it does not seem to work in my case.
I think you can use tag if you don't want your components to be destroyed and recreated again. Following links might help.
vue js docs keep alive
vue js keep alive
I've been struggling with this for a few days and was hoping for a graceful way of handling dynamic URLs with no data.
I have the following routes:
const router = new VueRouter({
routes: [
{path: '/product/:slug', component: Product},
{path: '/404', component: PageNotFound, alias: '*'}
]
});
In the Product component, I have an object of products and, depending on the slug variable, load the product to show.
The issue i'm having is when the URL is a slug that does not exist in the products dataset. I would like to load the PageNotFound component, without updating the URL.
Is this possible? It would be nice to have a consistent 404 page throughout the app and would also be good for me not to have to repeat myself with a v-if in the product table.
The closest I've got to it is this:
if(!product) {
this.$router.replace({path: '/404', query: {product: this.$route.params.slug}});
}
However, this updates the actual URL which is not very good UX.
Any clues?
You could conditionally render your PageNotFound component in Product.vue if the query returns no results, and then not have to fiddle with your router at all.
Thanks to Kyle pointing me in the right direction, this is what I came up with.
Becuase I am being slightly unorthodox and using server-side components and JavaScript, I already had my page not found component loaded - which looks like this:
const PageNotFound = {
name: 'PageNotFound',
template: `<div>
<h1>404 Page Not Found</h1>
<p>Head back to the <router-link to="/">home page</router-link> and start again.</p>
</div>`
};
I made sure the PageNotFound.js file was loaded in the HTML before my product component, so I was able to do the following:
const ProductPage = {
name: 'ProductPage',
template: `<div>
<div v-if="product"><h1>{{ product.title }}</h1></div>
<page-not-found v-if="notFound"></page-not-found>
</div>`,
components: {
PageNotFound
},
data() {
return {
notFound: false
}
},
computed: {
product() {
let product;
if(Object.keys(this.$store.state.products).length) {
product = this.$store.state.products[this.$route.params.slug];
if(!product) {
this.notFound = true;
}
}
return product;
}
}
};
Things to note in the above:
Data is being loaded asynchronously, hence the check to see if products exist
The PageNotFound component is loaded in - this is ES6 for PageNotFound: PageNotFound - Vue then automatically makes a <page-not-found></page-not-found> element
That element then has a v-if which gets triggered. As the first container would not be in existence if there is no product, only the 404 component is displayed
I don't do it based on product, as you would get a flash of the 404 if the product data was still loading via an API.
It's better practice to have the URL params as props (see docs), which I will be doing at some point!
To conclude, this allows you to show a consistent 404 page throughout your SPA (single page application) while maintaining URLs with dynamic routes. It allows you to load another component or show another component without updating the URL and also lets you have a wildcard 404 for dynamic routes.
Hope that all makes sense and helps someone in the future and saves them from wasting ~4 hours of trial, error and googling. (and yes I have "keyword" and phrase stuffed this answer to help someone find it...)
Let's asume that I have three components:
<ArticleFinder />
<ArticleViewer />
<RecentArticleList />
ArticleFinder is a component that fetch article list from server, and shows it. If user click a list item, will show <ArticleViewer />.
ArticleViewer is a component that fetch single article from server and display it.
RecentArticleList is a component that fetch recent 10 articles from server, and shows it. Like ArticleFinder, on user click a list item will show <ArticleViewer />, and if <ArticleViewer /> already mounted, <ArticleViewer /> just reload the article.
ArticleFinder and RecentArticleList components are receiving brief article data like this:
[{ _id: 1, title: 'Helloworld', date: '...' }, { _id: 2, title: 'Farewell!', date: '...' }]
And ArticleViewer will receive more specific data like this:
{ _id: 1, title: 'Helloworld', date: '...', content: '<p>Helloworld!</p>', files: [ ... ], tags: [ ... ] }
This is just for reduce the size of transmission, server will response as minimum data as possible. So for display content and files and others from ArticleViewer, this component must have called somekind of getData method.
Note that those 3 components can exists same time, they have their own range on screen. They can be overwrapped but still they are in the same time, same screen.
So when ArticleViewer mounted, it calls own getData method and display the results, but the problem is that if ArticleViewer is already mounted, click a list from RecentArticleList or ArticleFinder will not trigger getData of ArticleViewer because component didn't unmounted and mounted.
I'm using React Router 4.x with this, so I can redirect the url, but still there is no way to forcely invoke ArticleViewer.getData in ArticleFinder and RecentArticleList.
Someway, I did it with some kind of trick. I just checked the props that receives from React Router(this.props.params) has changed, and then invoke the getData method like this:
componentWillReceiveProps(nextProps, nextState) {
if(nextProps.location.pathname !== prevPath) {
prevPath = nextProps.location.pathname;
this.getArticles(category, search);
}
}
It's working actually, even if refresh the page, but I don't like it, it's not intuitive, not elegant, at least to me.
Is there a way to solve this problem more elegantly? Especially, somekind of React like(if it exists)?
Code is quite a mess and adding more functionalities makes more ugly and I want to fix this. I know there are no answers in programming but there can be exists readable solution, as I think.
Any advice will very appreciate it. Thanks!
These components seem to share state and functionality between them. You could move this state to the component containing these three components and just pass the article to show into your <ArticleViewer>.
You can use key prop to create a completely new instance of component and will be exactly like you are using the component for the first time.
<ArticleViewer {...yuorProps} key={id_of_article}/>
make sure the id is unique for each article.
Remember that you won't be using the existing component, you end up creating new component.
The React Router v4 way would be to use a custom render function for the Article View route -
<Match pattern="/:articleid"
render={({params}) => <ArticleViewer article={params.articleid} />}/>
This will render a new ArticleViewer component on route change.