Accessing route VM within router hooks for updating document.title
holic opened this issue · comments
I'd like to change the page title on route changes for better history management. I thought to do this in the afterEach
router hook, using a data property on the VM (computed or otherwise). However, I can't find a good way to access the <router-view>
VM from within this hook.
Any alternatives you would suggest?
I thought I'd try something like:
<router-view v-ref="view"></router-view>
And then watch for a property on the VM reference:
// root Vue app
new Vue({
watch: {
'$.view.title': function (title) {
document.title = title
}
}
})
But this doesn't work. I also tried a computed property, with no luck:
// root Vue app
new Vue({
computed: {
title: function () {
return this.$.view && this.$.view.title
}
},
watch: {
'title': function (title) {
document.title = title
}
}
})
Ultimately, I found that if I watch the $route
property from the root VM, then add a title
watcher to the router VM via this.$.view.$watch
, it works. But I have to manually manage watchers (removing the previous one before adding a new one) and I don't get the first title on page initialization ($route
seems to be set before this.$.view
is available, so immediate: true
fires with an empty this.$.view
).
I think document title (and history title) support should really be built in to the router.
Agreed @JosephSilber. Ideally it would allow for a computed property on the router view so that the router view's component can update the title dynamically (e.g. unread count on an inbox).
I think I found a better solution than the one I mentioned above, via a mixin for my route components:
module.exports = {
watch: {
title: {
immediate: true,
handler: function (title) {
if (title) {
document.title = title
}
}
}
}
}
But I do think that this should be built in to the router in some way.
What's wrong with setting document.title
in activate
or data
hook?
@yyx990803 I didn't want to have to include it with every single route component (mixins previously did not extend to the route object) and I want to be able to get the title value from the current <router-view>
VM.
Hi,
I got the same issue but after some research I found 2 way of doing it, you can see my full answer on the vue.js forum: http://forum.vuejs.org/topic/563/how-can-i-access-a-child-router-views-data-in-a-parent-component/6
Here a demo: https://output.jsbin.com/cihizi/5/#!/arron
It's updating the title and the description tag.
Code: https://jsbin.com/cihizi/5/edit?html,js
Hope it helped :)
close this issue via #279
<router-view @route-data-loaded="changeTitle"></router-view>
export default {
methods: {
changeTitle(vm) {
document.title = vm.docTitle
}
}
}
So I just tried @lepture's suggestion with vue 1.0.17 and it doesn't work. No messages are logged to the console either, indicating the event isn't emitted?
<template>
<div>
<header>
<div class="groupincome-logo">
<img class="black" src="/images/groupincome-logo-black.png">
</div>
<nav>
<ul>
<li><a v-link="'/new-user'">New User</a></li>
<li><a v-link="'/user'">User Profile</a></li>
<li><a v-link="'/ejs-page'">EJS page test</a></li>
</ul>
</nav>
</header>
<div class="container">
<!-- change page title via: https://github.com/vuejs/vue-router/issues/112#issuecomment-173184057 -->
<router-view @route-data-loaded="changeTitle"></router-view>
</div>
</div>
</template>
<script>
export default {
methods: {
changeTitle(vm) {
console.log('changeTitle called')
if (vm.pageTitle) {
console.log('changing title to:', vm.pageTitle)
document.title = vm.pageTitle
}
}
}
}
</script>
Help? Problem on my end or a new bug?
FWIW the suggestions at the bottom here (linked to by @Atinux) work, and this seems to be a perfectly elegant solution for changing the title.
It involves moving what would normally be in App.vue
's <template>
into the root index.html
, and using a blank App instance on the router:
var App = Vue.extend({})
router.start(App, 'html')
I think I prefer this method anyway as it saves having an extra (and unnecessary) file.
try this way
Vue.directive('title', {
inserted: function (el, binding) {
document.title = el.dataset.title
}
})
<div v-title data-title="title here">
……
the div can be any tag within your component
</div>
@route-data-loaded='changeTitle'
If the function does not exist, there's an error. But even when the function is there, it will never be called.
This event has been removed in 2.0
Oh
After looking into the same issue I found something that might be what you are looking for so I thought I would post it here :
There is a meta
option that can be provided to the router configuration. It can be anything, so a string containing your page's title is fine. It can also be an object if you prefer to keep it clean.
Router config
...
{
path: '/documents',
component: DocumentsComponent,
meta: 'My Documents'
},
...
Title component
This could be a TitleComponent
, used anywhere in your layout :
<template>
<h1>{{ pageTitle }}</h1>
</template>
<script>
export default {
created () {
this.setTitle()
},
watch: {
'$route' () {
this.setTitle()
}
},
methods: {
setTitle () {
if (typeof this.$route.meta === 'string') {
this.pageTitle = this.$route.meta
} else {
this.pageTitle = null
console.error('No title set for path [%s]', this.$route.path)
}
}
},
data () {
return {
pageTitle: null
}
}
}
</script>
This is just a suggestion and can be easily adapted to your needs.
For instance, you could append the following line to the setTitle
method to update the page's title in the browser bar :
document.title = this.pageTitle
@kartsims It's worked for me. =D
@luizpaulo165 great :)
This is a workaround though... this issue is labeled "feature request" but is it part of the roadmap ?
I would love to have it implemented in the core too and the routing feature of vue-router pairs well with a common label, passed to the Route
object...
Could we imagine a dedicated option in the Router's config ? And be able to access this option's value in the Route
object made available ?
Something like :
Route config
...
{
path: '/documents',
component: DocumentsComponent,
title: 'My Documents'
},
...
DocumentsComponent (or even the component that contains the tag)
...
created () {
console.log(this.$route.title) // "My Documents"
}
...
What do you think ? I could try to send a PR if project's contributors think this could be part of the "core" vue-router lib
I use routing to solve the problem, the code is as follows:
.....
//in init router file
router.afterEach(route => {
document.title = route.meta.title;
})
......
//in router config
.......
{
path: "/login",
name: "login",
component: LoginView,
meta: {
title: 'Login System'
}
}
.....
I think setting the document's title should be the component's responsibility, not the router. This gives components more control.
Router hooks aren't fired if the route is the same, e.g. if a blog post id changes. It's up to the component to watch this.$route
and fetch data for the new blog post. After getting the new blog post it would set the document's title.
Router hooks aren't fired if the route is the same, e.g. if a blog post id changes
Not true since vue-router 2.2. It has beforeRouteUpdate
now.
@LinusBorg Thanks for pointing that out. I guess no need to watch $route
anymore.
Either way, the route change is handled in the component with beforeRouteUpdate
or by watching $route
, so to me it makes sense for the component to set the document title, not in the global route config using afterEach
.
I also agree that the component should be the thing setting the title. This becomes a necessity if the title depends on content dynamically fetched by the component. For example, the title for the route /user/99
might be the name of user with id 99.
I've got a solution and used it on one my projects.
First create a directive.
Vue.directive('title', {
inserted: el => document.title = el.dataset.title,
update: el => document.title = el.dataset.title
})
Then use that directive on the router-view
component.
<router-view v-title data-title="My Title" ></router-view>
This works even if the component is updated or the page is reloaded.