Skip to content

New feature: Passing props to components from vue-router #973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/en/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Named Routes](essentials/named-routes.md)
- [Named Views](essentials/named-views.md)
- [Redirect and Alias](essentials/redirect-and-alias.md)
- [Passing props](essentials/passing-props.md)
- [HTML5 History Mode](essentials/history-mode.md)
- Advanced
- [Navigation Guards](advanced/navigation-guards.md)
Expand Down
1 change: 1 addition & 0 deletions docs/en/api/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
name?: string; // for named routes
components?: { [name: string]: Component }; // for named views
redirect?: string | Location | Function;
props?: boolean | string | Function;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is Boolean | Object | Function in passing-props.md

alias?: string | Array<string>;
children?: Array<RouteConfig>; // for nested routes
beforeEnter?: (to: Route, from: Route, next: Function) => void;
Expand Down
72 changes: 72 additions & 0 deletions docs/en/essentials/passing-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Passing props

Using `$route` in your component creates a tight coupling with the route which limits the flexibility of the component as it can only be used on certain urls.

To decouple this component from the router use props:

**❌ Coupled to $route**

``` js
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
```

**👍 Decoupled with props**

``` js
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true }
]
})
```

This allows you to use the component anywhere, which makes the component easier to reuse and test.

### Boolean mode

When props is set to true, the route.params will be set as the component props.

### Object mode

When props is an object, this will be set as the component props as-is.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be even better to say when it's useful to pass an object as the props

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've improved the description and code snippet.

Useful for when the props are static.

``` js
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
]
})
```

### Function mode

You can create a function that returns props.
This allows you to to cast the parameter to another type, combine static values with route-based values, etc.

``` js
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
})
```

The url: `/search?q=vue` would pass `{query: "vue"}` as props to the SearchUser component.

Try to keep the props function stateless, as it's only evaluated on route changes.
Use a wrapper component if you need state to define the props, that way vue can react to state changes.


For advanced usage, checkout the [example](https://github.com/vuejs/vue-router/blob/dev/examples/route-props/app.js).
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ <h1>Vue Router Examples</h1>
<li><a href="route-matching">Route Matching</a></li>
<li><a href="active-links">Active Links</a></li>
<li><a href="redirect">Redirect</a></li>
<li><a href="route-props">Route Props</a></li>
<li><a href="route-alias">Route Alias</a></li>
<li><a href="transitions">Transitions</a></li>
<li><a href="data-fetching">Data Fetching</a></li>
Expand Down
17 changes: 17 additions & 0 deletions examples/route-props/Hello.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<div>
<h2 class="hello">Hello {{name}}</h2>
</div>
</template>

<script>

export default {
props: {
name: {
type: String,
default: 'Vue!'
}
}
}
</script>
39 changes: 39 additions & 0 deletions examples/route-props/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Hello from './Hello.vue'

Vue.use(VueRouter)

function dynamicPropsFn (route) {
const now = new Date()
return {
name: (now.getFullYear() + parseInt(route.params.years)) + '!'
}
}

const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/', component: Hello }, // No props, no nothing
{ path: '/hello/:name', component: Hello, props: true }, // Pass route.params to props
{ path: '/static', component: Hello, props: { name: 'world' }}, // static values
{ path: '/dynamic/:years', component: Hello, props: dynamicPropsFn } // custom logic for mapping between route and props
]
})

new Vue({
router,
template: `
<div id="app">
<h1>Route props</h1>
<ul>
<li><router-link to="/">/</router-link></li>
<li><router-link to="/hello/you">/hello/you</router-link></li>
<li><router-link to="/static">/static</router-link></li>
<li><router-link to="/dynamic/1">/dynamic/1</router-link></li>
</ul>
<router-view class="view"></router-view>
</div>
`
}).$mount('#app')
6 changes: 6 additions & 0 deletions examples/route-props/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<link rel="stylesheet" href="/global.css">
<a href="/">&larr; Examples index</a>
<div id="app"></div>
<script src="/__build__/shared.js"></script>
<script src="/__build__/route-props.js"></script>
3 changes: 3 additions & 0 deletions src/components/view.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { resolveProps } from '../util/props'

export default {
name: 'router-view',
functional: true,
Expand Down Expand Up @@ -56,6 +58,7 @@ export default {
matched.instances[name] = undefined
}
}
data.props = resolveProps(route, component, matched.props && matched.props[name])

return h(component, data, children)
}
Expand Down
1 change: 1 addition & 0 deletions src/create-route-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function addRouteRecord (
name,
parent,
matchAs,
props: typeof route.props === 'undefined' ? {} : (route.components ? route.props : { default: route.props }),
redirect: route.redirect,
beforeEnter: route.beforeEnter,
meta: route.meta || {}
Expand Down
26 changes: 26 additions & 0 deletions src/util/props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

import { warn } from './warn'

export function resolveProps (route, component, config) {
switch (typeof config) {

case 'undefined':
return

case 'object':
return config

case 'function':
return config(route)

case 'boolean':
if (!config) {
return
}
return route.params

default:
warn(false, `props in "${route.path}" is a ${typeof config}, expecting an object, function or boolean.`)
}
}

3 changes: 2 additions & 1 deletion test/e2e/nightwatch.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// http://nightwatchjs.org/guide#settings-file

module.exports = {
'src_folders': ['test/e2e/specs'],
'output_folder': 'test/e2e/reports',
Expand All @@ -7,7 +8,7 @@ module.exports = {

'selenium': {
'start_process': true,
'server_path': 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.1.jar',
'server_path': require('selenium-server').path,
'host': '127.0.0.1',
'port': 4444,
'cli_args': {
Expand Down
25 changes: 25 additions & 0 deletions test/e2e/specs/route-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
'route-props': function (browser) {
browser
.url('http://localhost:8080/route-props/')
.waitForElementVisible('#app', 1000)
.assert.count('li a', 4)

.assert.urlEquals('http://localhost:8080/route-props/')
.assert.containsText('.hello', 'Hello Vue!')

.click('li:nth-child(2) a')
.assert.urlEquals('http://localhost:8080/route-props/hello/you')
.assert.containsText('.hello', 'Hello you')

.click('li:nth-child(3) a')
.assert.urlEquals('http://localhost:8080/route-props/static')
.assert.containsText('.hello', 'Hello world')

.click('li:nth-child(4) a')
.assert.urlEquals('http://localhost:8080/route-props/dynamic/1')
.assert.containsText('.hello', 'Hello ' + ((new Date()).getFullYear() + 1)+ '!')

.end()
}
}