vuejs / vue-router

🚦 The official router for Vue 2

Home Page:http://v3.router.vuejs.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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>
commented

@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

commented

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

commented

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'
       }

}
.....

@kartsims I don't consider the usage of the meta prop to be a workaround - it exists for exactly these types of situations.

@roninliu That's what I would suggest as well.

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.