A router instance must be configured at start of application.
An application should not have more than one instance.
import { Router } from 'slick-router/core.js'
const router = new Router(options)
- options.routes - route tree definition (an array or a callback function).
- options.log - a function that is called with logging info, default is noop. Pass in
true
/false
or a custom logging function. - options.logError - default is true. A function that is called when transitions error (except for the special
TransitionRedirected
andTransitionCancelled
errors). Pass intrue
/false
or a custom error handling function. - options.pushState - default is false, which means using hashchange events. Set to
true
to use pushState. - options.root - default is
/
. Use in combination withpushState: true
if your application is not being served from the root url /. - options.qs - default is a simple built in query string parser. Pass in an object with
parse
andstringify
functions to customize how query strings get treated.
The route tree can be configured as a callback, that receives a route
function
route
first argument must be a unique name, the second (optional) argument is the route options and the third (also optional) is a callback to configure the children.
The route options can be path
, abstract
or arbitrary ones that can be used by the middlewares.
// route tree as callback
const routes = function (route) {
route('app', {path: '/'}, function () {
route('about')
route('post', {path: ':postId'}, function () {
route('show')
route('edit')
})
})
}
It can be defined also as plain array:
// route tree as array
const routes = [
{
name: 'app',
path: '/',
children: [
{
name: 'about'
},
{
name: 'post',
path: ':postId',
children: [
{
name: 'show'
},
{
name: 'edit'
}
]
}
]
}
]
The routes definition can be passed directly as Router constructor routes
option or using map
const router = new Router({routes})
// or
const router = new Router()
router.map(routes)
Nested routes are defined by passing a callback to the route
function or, when using the array notation, by defining a children
property.
Each route is associated to a path composed by its own path concatenated with the parent routes paths.
router.map(function (route) {
route('foo', {path: '/foo'}, function () {
route('bar', {path: 'bar'}, function () {
route('baz', {path: 'baz'})
});
})
})
The above map results in one path /foo/bar/baz
mapping to ['foo', 'bar', 'baz'] routes.
Nested paths are concatenated unless they start with a '/'. For example
router.map(function (route) {
route('foo', {path: '/foo'}, function () {
route('bar', {path: '/bar'}, function () {
route('baz', {path: '/baz'})
});
})
})
The above map results in one path /baz
mapping to ['foo', 'bar', 'baz'] routes.
When a navigation occurs, the route with the path which best matches the current URL is matched together with its parents, e.g., when 'bar' route is matched, 'foo' will also be matched even if 'foo' path is not related at all with current URL.
Paths can contain dynamic segments as described in the docs of path-to-regexp. For example:
route('foo', {path: '/hello/:myParam'}) // single named param, matches /hello/1
route('foo', {path: '/hello/:myParam/:myOtherParam'}) // two named params, matches /hello/1/2
route('foo', {path: '/hello/:myParam?'}) // single optional named param, matches /hello and /hello/1
route('foo', {path: '/hello/:splat*'}) // match 0 or more segments, matches /hello and /hello/1 and /hello/1/2/3
route('foo', {path: '/hello/:splat+'}) // match 1 or more segments, matches /hello/1 and /hello/1/2/3
By default, both leaf and non leaf routes can be navigated to. Sometimes you might not want it to be possible to navigate to certain routes at all, e.g. if the route is only used for data fetching and doesn't render anything by itself. In that case, you can set abstract: true
in the route options. Abstract routes can still form a part of the URL.
router.map(function (route) {
route('application', {path: '/'}, function () {
route('dashboard', {path: 'dashboard/:accountId', abstract: true}, function () {
route('defaultDashboard', {path: ''})
route('realtimeDashboard', {path: 'realtime'})
});
})
})
Abstract routes are especially useful when creating index
subroutes as demonstrated above. The above route map results in the following URLs:
/ - ['application']
/dashboard/:accountId - ['application', 'dashboard', 'defaultDashboard']
/dashboard/:accountId/realtime - ['application', 'dashboard', 'realtimeDashboard']
Navigating to an abstract route that has an index route is equivalent to navigating to the index route. E.g. these are equivalent:
router.transitionTo('dashboard')
router.transitionTo('defaultDashboard')
Generating links is also equivalent
router.generate('dashboard') === router.generate('defaultDashboard')
However, if the abstract route does not have an index route, then it's not routable and can't have URLs generated.
It's also common to redirect from non leaf routes. In this example we might want to redirect from application
to the defaultDashboard
route. If each of your routes are backed by some route handler object, you can achieve the redirect with the following middleware:
router.use(function redirect (transition) {
var lastRoute = transition.routes[transition.routes.length - 1]
if (lastRoute.handler.redirect) {
lastRoute.handler.redirect(transition.params, transition.query)
}
})
If a route path is not specified, it defaults to the name of the route, e.g.:
route('foo')
// equivalent to
route('foo', {path: 'foo'})
If a route has a name with dots and no path specified, the path defaults to the last segment of the path.
route('foo.bar')
// equivalent to
route('foo.bar', {path: 'bar'})
Middlewares (builtin or custom ones) are added through use
method
Add a transition middleware. Every time a transition takes place this middleware will be called with a transition as the argument. You can call use
multiple times to add more middlewares. The middleware function can return a promise and the next middleware will not be called until the promise of the previous middleware is resolved. The result of the promise is passed in as a second argument to the next middleware. E.g.
router.use(function (transition) {
return Promise.all(transition.routes.map(function (route) {
return route.options.handler.fetchData()
}))
})
router.use(function (transition, datas) {
transition.routes.forEach(function (route, i) {
route.options.handler.activate(datas[i])
})
})
Its possible to control the order which the middleware is inserted in internal queue by using options.at
:
// ensure middleware will be executed first
router.use(function () { console.log('my middleware') }, { at: 0 })
The middleware can be defined also as an object with one or more of the following methods:
resolve(transition, prevData)
: called once per transition after previous middleware, if any, is resolved. Main action should be done here.done(transition)
: called when transition succeeds and after all middlewares are resolvedcancel(transition, err)
: called if transition is cancelled / redirectederror(transition, err)
: called if an error occurs while executing the trasitioncreate(router)
: called once at middleware registration. Useful to configure the middlewaredestroy(router)
: called once when router destroyed
After the router has been configured with a route map and middleware is necessary to call listen
Start listening to URL changes and transition to the appropriate route based on the current URL.
When using location: 'memory'
, the current URL is not read from the browser's location bar and instead can be passed in via listen: listen(path)
.
router.listen()
The query params is extracted and parsed using a very simple query string parser that only supports key values. For example, ?a=1&b=2
will be parsed to {a: 1, b:2}
. If you want to use a more sophisticated query parser, pass in an object with parse
and stringify
functions - an interface compatible with the popular qs module e.g.:
const router = new Router({
qs: require('qs')
})
Is possible to configure how browser's URL/history is managed. By default, Slick Router will use a very versatile implementation - slick-router/lib/locations/browser
which supports pushState
and hashChange
based URL management with graceful fallback of pushState
-> hashChange
-> polling
depending on browser's capabilities.
Configure BrowserLocation by passing options directly to the router.
var router = new Router({
pushState: true
})
- options.pushState - default is false, which means using hashchange events. Set to true to use pushState.
- options.root - default is
/
. Use in combination withpushState: true
if your application is not being served from the root url /.
MemoryLocation can be used if you don't want router to touch the address bar at all. Navigating around the application will only be possible programatically by calling router.transitionTo
and similar methods.
e.g.
var router = new Router({
location: 'memory'
})
You can also pass a custom location in explicitly. This is an advanced use case, but might turn out to be useful in non browser environments. For this you'll need to investigate how BrowserLocation is implemented.
var router = new Router({
location: myCustomLocation()
})
When using pushState is necessary to intercept link clicks otherwise the browser would just do a full page refresh on every click of a link.
Slick Router automatically handles that when using routerLinks middleware or interceptLinks
function
The clicks are intercepted only if:
interceptLinks(router)
is called- the currently used location and browser supports pushState
- clicked with the left mouse button with no cmd or shift key
The clicks that are never intercepted:
- external links
javascript:
linksmailto:
links- links with a
data-bypass
attribute - links starting with
#
The default implementation of the intercept click handler is:
function defaultClickHandler (event, link, router) {
event.preventDefault()
router.transitionTo(router.location.removeRoot(link.getAttribute('href')))
}
You can pass the root element to look for links and a custom function as interceptLinks
params:
function customClickHandler (event, link, router) {
event.preventDefault()
router.replaceWith(router.location.removeRoot(link.getAttribute('href')))
}
interceptLinks(router, document, customClickHandler)