I have created a vue-router with a page title (h1).
Every vue-router link has a meta-title. How can I dynamically set my h1 page title?
I have tried $emit, but this is not working in my router. How can I change my page title?
const dashboard = {
template: `<div>DASHBOARD</div>`
}
const about = {
template: `<div>ABOUT</div>`
}
const routes = [
{path: '/dashboard', component: dashboard, name:'dashboard', meta: { title: 'Dashboard' }},
{path: '/about', component: about, name:'about', meta: { title: 'About' }}
]
const router = new VueRouter({
routes // short for routes: routes
})
router.beforeEach((to, from, next) => {
this.$emit('pagetitle', to.meta.title); // DOESN'T WORK!!
next();
});
new Vue({
el: '#app',
router,
data() {
return {
pagetitle: 'not set'
}
},
watch: {
'$route': function(value) {
// I can use things like value.name === 'about', but "watch" doesn't set the page title at the first page load.
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/dashboard">Dashboard</router-link>
<router-link to="/about">About</router-link>
<router-view></router-view>
<h1 v-text="pagetitle"></h1>
How can I change the page title???
</div>
In your <h1></h1> tag, just use the following
{{ $route.meta.title }}
If you would like to have correct browser history use this one:
router.afterEach((to, from) => {
Vue.nextTick( () => {
document.title = to.meta.title ? to.meta.title : 'default title';
});
})
Also if you need to change the document's title-tag, in your router.beforeEach replace this line :
this.$emit('pagetitle', to.meta.title);
with
document.title = to.meta.title;
It will work.
Related
I wanted to add the dynamic routes and use the same component for all the dynamic routes. I have tried the following code to render the components, but I have got the error that says:
[vue-router] "path" is required in a route configuration.
What is the proper way of adding the dynamic routes and display the same components?
const Foo = {
template: '<div>Foo</div>'
}
const Home = {
template: '<div>Home</div>'
}
const router = new VueRouter({
mode: 'history',
routes: [{
path: '/',
component: Home
}]
})
const app = new Vue({
router,
el: "#vue-app",
methods: {
viewComponent: function(path, method) {
debugger;
let tf = `${path}/${method}`;
let newRoute = {
path: tf,
name: `${path}_${method}`,
components: {
Foo
},
}
this.$router.addRoute([newRoute])
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="vue-app">
<a v-on:click="viewComponent('api/contact','get')">ddd</a>
<router-view></router-view>
</div>
Main problem is you are passing array into addRoute
Second problem is missing / at the beginning of the path (without it, you will get a "Non-nested routes must include a leading slash character" error)
Finally use $router.push to go to the new route
const Foo = {
template: '<div>Foo</div>'
}
const Home = {
template: '<div>Home</div>'
}
const router = new VueRouter({
mode: 'history',
routes: [{
path: '/',
component: Home
}]
})
const app = new Vue({
router,
el: "#vue-app",
methods: {
viewComponent: function(path, method) {
let tf = `/${path}/${method}`;
let newRoute = {
path: tf,
name: `${path}_${method}`,
component: Foo,
}
this.$router.addRoute(newRoute)
this.$router.push({ name: newRoute.name })
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="vue-app">
<a v-on:click="viewComponent('api/contact','get')">ddd</a>
<router-view></router-view>
</div>
To simplify the demo I'm using fake/pointless API)
I have the routers
const routes = [
{ path: '/', component: Menu},
{path: '/books', component: Index,
props: route => ({ api: `https://jsonplaceholder.typicode.com/posts` })
},
{path: '/videos',component: Index,
props: route => ({ api: `https://jsonplaceholder.typicode.com/todos` })
}
]
const router = new VueRouter({ routes })
const app = new Vue({ router }).$mount('#app')
The Menu component
var Menu = Vue.component('Menu', {
template: `<ul>
<li><router-link to="/">home</router-link></li>
<li><router-link :to="{ name: 'videos'}">videos</router-link></li>
<li><router-link :to="{ name: 'books'}">books</router-link></li>
</ul>`})
This is the problem, it does not switch from videos to books.
Finally the Index component
var BookComponent = {
props: {
api: { type: String }
},
data: function () {
return {
items: null,
}
},
mounted: function(){
this.getItems()
},
methods: {
async getItems() {
fetch(this.api).then(res => res.json())
.then(res =>{
this.items = res;
this.loading = false
})
},
},
template: `
<div class="home">
<p v-for="post in items" :key="post.id">{{post.title}}</p>
</div>
`
}
var Home = Vue.component('Home', BookComponent)
var Index = {
props: {
api: {type: String,},
filterBy: {type: String},
},
template: `
<div>
<Menu />
<div class="mainApp" style="margin-top: 40px!important">
<Home :api=api />
</div>
</div>`,
component: { Menu, Home },
};
It doesn't work on jsfiddle but here's the code anyway jsfiddle
You're calling named routes but you haven't defined any name in your routes so either use the path
<li><router-link to="/videos">videos</router-link></li>
<li><router-link to="/books">books</router-link></li>
or name your routes
const routes = [
{ path: '/', component: Menu},
{path: '/books', component: Index, name: 'books',
props: route => ({ api: `https://jsonplaceholder.typicode.com/posts` })
},
{path: '/videos',component: Index, name: 'videos',
props: route => ({ api: `https://jsonplaceholder.typicode.com/todos` })
}
]
To fix navigation - replace objects in router-link attribute to with to='/videos' and to='/books'
To fix render error, add v-if to post items loop, like this:
<div class="home">
<p v-if="items" v-for="post in items" :key="post.id">{{post.title}}</p>
</div>
Here is official docs for vue-router
It seems that I just needed to watch for the route changes and trigger the fetch method again.
watch: {
// call again the method if the route changes
'$route': 'getItems'
}
When I set a component props in the route definition the watcher doesn't work and the view is never updated.
In this example the update works correctly :
const Home = { template: '<div>Home</div>' }
const Test = {
template: '<div>Test : {{ nb }}<br><button #click="addOne" type="button">+1</button></div>',
props: {nb: Number},
methods: {
addOne() {
this.nb++
}
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{ name: 'test', path: '/test/:nb', component: Test, props: true }
]
})
new Vue({
router,
el: '#app'
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/">/home</router-link>
<router-link :to="{ name: 'test', params: { nb: 42 }}">/test</router-link>
<router-view></router-view>
</div>
Note : This example show a "mutating a prop directly" warning, but it's just for the simplicity it's not related to the issue since I don't do that in my code.
In this example the update doesn't work :
const Home = { template: '<div>Home</div>' }
const Test = {
template: '<div>Test : {{ counter.value }}<br><button #click="addOne" type="button">+1</button></div>',
props: {counter: Object},
methods: {
addOne() {
this.counter.value++
console.log('new value : ' + this.counter.value)
}
}
}
class Counter {
constructor(value) {
this.value = value
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{
name: 'test',
path: '/test/:nb',
props: (route) => ({counter: new Counter(route.params.nb)}),
component: Test
}
]
})
new Vue({
router,
el: '#app'
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/">/home</router-link>
<router-link :to="{ name: 'test', params: { nb: 42 }}">/test</router-link>
<router-view></router-view>
</div>
The counter object won't be reactive, so property changes won't be detected. Passing an object as a prop does not add reactivity.
I've just changed one line in your example:
props: (route) => ({counter: Vue.observable(new Counter(route.params.nb))}),
Passing the object to Vue.observable will apply the reactivity.
Note that 'applying reactivity' means rewriting all of the properties to use getters and setters. In the simple example presented here that just means the value property, which should be fine. Vue can only rewrite properties it can see, so any data hidden within closures would not be accessible.
const Home = { template: '<div>Home</div>' }
const Test = {
template: '<div>Test : {{ counter.value }}<br><button #click="addOne" type="button">+1</button></div>',
props: {counter: Object},
methods: {
addOne() {
this.counter.value++
console.log('new value : ' + this.counter.value)
}
}
}
class Counter {
constructor(value) {
this.value = value
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{
name: 'test',
path: '/test/:nb',
props: (route) => ({counter: Vue.observable(new Counter(route.params.nb))}),
component: Test
}
]
})
new Vue({
router,
el: '#app'
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/">/home</router-link>
<router-link :to="{ name: 'test', params: { nb: 42 }}">/test</router-link>
<router-view></router-view>
</div>
Part of the reason why Vue does not automatically apply reactivity to objects passed as props is that the receiving component shouldn't be modifying the object anyway. It's a violation of one-way data flow. In this example you should probably be updating the route directly rather than modifying the counter object.
I'm creating a new project using VueJS as first time, and trying to use a toolbar on all of my pages. But I want to make the title present in the toolbar, dynamic.
I tried to use props, but I must be wrong the way I use it. Check the example below :
In my index.html :
<div id="app" v-cloak>
<v-app>
<spa-toolbar :title="title"></spa-toolbar>
<router-view></router-view>
</v-app>
</div>
<script>
Vue.use(VueRouter);
Vue.use(VeeValidate);
Vue.config.devtools = true;
const routes = [
{
path: '/',
component: spaLogin,
props: {title: 'Connexion'}
},
{
path: '/parametres',
name : 'parametres',
component: spaParametres,
props: {title: 'Paramètres'}
},
];
let router = new VueRouter({
hashbang: false,
routes
});
router.beforeEach((to, from, next) => {
next();
});
var app = new Vue({
el: '#app',
watch: {},
data: {
title: 'Connexion',
},
router
});
</script>
toolbar.vue.js (with the "props: ["title"]) :
<v-toolbar-title class="white--text">{{title}}</v-toolbar-title>
And in another page "parametres.vue.js" I'm using the props["title"], but the value of the title is always "Connexion", the value defined in my main App in index.html.
How can I make this value dynamic ? Thought passing it through the router would be great...
Thanks !
You are passing the property title from the v-app component to the spa-toolbar component. Then you are setting a property on the routes which is also called title.
These two are not related in any way. Also the title prop from the route is passed to the component in router-view, i.e. spaLogin and spaParametre.
So I think a better way would be to define a meta field on the route:
<div id="app" v-cloak>
<v-app>
<spa-toolbar :title="(($route.matched[0] || {}).meta || {}).title"></spa-toolbar>
<router-view></router-view>
</v-app>
</div>
<script>
Vue.use(VueRouter);
Vue.use(VeeValidate);
Vue.config.devtools = true;
const routes = [
{
path: '/',
component: spaLogin,
meta: {title: 'Connexion'}
},
{
path: '/parametres',
name : 'parametres',
component: spaParametres,
meta: {title: 'Paramètres'}
},
];
let router = new VueRouter({
hashbang: false,
routes
});
var app = new Vue({
el: '#app',
data: {},
router
});
</script>
I am playing with an example given from vue-router documentation
HTML:
<div id="app">
<h3>{{title}}</h3>
<router-link to="/">/home</router-link>
<router-link to="/foo">/foo</router-link>
<router-view></router-view>
</div>
JavaScript:
const Foo = { template: '<div>foo</div>', data: {title: "Foo" }
const Bar = { template: '<div>bar</div>', data: {title: "Bar" }
Is it possible to access selected component data outside anchor and update {{title}} header?
Full example is here
I've two possibilities. First one is to edit your parent data directly with this.$parent.title.
And the second way is to trigger an event this.$parent.$emit('changeTitle', 'foo');.
I think the last one is better, because the control of your state is always in your parent component.
const Home = {
template: '<div>Home</div>',
data: {
title: "Home"
},
mounted() {
this.$parent.title = 'home'; // directly
}
}
const Foo = {
template: '<div>Foo</div>',
data: {
title: "Foo"
},
mounted() {
this.$parent.$emit('changeTitle', 'foo'); // emit an event
}
}
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
component: Home
},
{
path: '/foo',
component: Foo
}
]
})
new Vue({
router,
el: '#app',
data: {
title: 'Initial'
},
destroy() {
this.$off('changeTitle');
},
created() {
const _self = this;
this.$on('changeTitle', function(newTitle) {
_self.title = newTitle;
});
}
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h3>{{title}}</h3>
<router-link to="/">/home</router-link>
<router-link to="/foo">/foo</router-link>
<router-view></router-view>
</div>