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>
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'
}
I have some text that i am trying to change the styling on based on route name.I have set up 2 route names, ads and webs and have set up a watcher to watch the routes. If the route name is either ads or webs i want the text to have color:white;background-color:orange. But i am not sure how to do it.
new Vue({
el: '#app',
data() {
return {
styleClass: null
}
},
watch: {
'$route' (to, from) {
if (to.name == 'ads' || to.name == 'webs') {
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<div id="app">
<span><h1>Hello!!!</h1></span>
</div>
I'd create a computed property to represent if the current route matches your criteria.
computed: {
highlight () {
return this.$route.matched.some(({ name }) => /^(ad|web)s$/.test(name))
}
}
Then you can use that in a class binding expression.
<span :class="{ highlight }">Text goes here</span>
and in your CSS
.highlight {
color: white;
background-color: orange;
}
See https://router.vuejs.org/api/#route-object-properties for info on the matched property.
I guess you are missing the route vuejs plugin and name your application into the route name too. Here the full example. Hope you get what you want
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const routes = [
{ path: '/foo', component: Foo, name: 'ads' },
{ path: '/bar', component: Bar, name: 'nowebs' }
]
const router = new VueRouter({
routes
})
const app = new Vue({
router,
data() {
return {
styleClass: null
}
},
watch: {
'$route' (to, from) {
if (to.name == 'ads' || to.name == 'webs') {
this.styleClass = "color:white;background-color:orange"
} else {
this.styleClass = ""
}
}
}
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<div id="app">
<span><h1 :style="styleClass">Hello!!!</h1></span>
<p>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
</div>
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 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.