Skip to main content

Introduction

Routing is an essential part of every application that displays multiple pages. It allows to develop each part of an application separately and add new parts instantly. As it happens to be in MVC frameworks, each route targets specific controller which takes control over what happens next after a route is matched.

Setting up Router

All routes in IMA.js are registered inside the init function in app/config/routes.js. Same init function can be found in app/config/bind.js. See Object Container documentation for more information about the oc.get() function.

Usually you should be oke with simple string defined StaticRoutes (the ones defined below), but the router also has support for more advanced and powerful DynamicRoutes. For more information about these see the next section.

./app/config/routes.js
import { RouteNames } from '@ima/core';

import HomeController from 'app/page/home/HomeController';
import HomeView from 'app/page/home/HomeView';

export let init = (ns, oc, config) => {
const router = oc.get('$Router');

router
.add('home', '/', HomeController, HomeView)
.add(RouteNames.ERROR, '/error', ErrorController, ErrorView)
.add(RouteNames.NOT_FOUND, '/not-found', NotFoundController, NotFoundView);
}

The router add method has following signature:

add(name, pathExpression, controller, view, options = undefined);

name

 string

This argument represents unique route name. You can use this name when linking between routes or getting the route instance using getRouteHandler() method.

pathExpression

string | object

This can be either object for dynamic routes or string representing route path. The pathExpression supports **parameter substitutions

controller

string | function

Route assigned Controller class (can be a string alias, referring to the controller registered in the Object Container). It goes through its full lifecycle and renders the View.

view

string | function

Route assigned View class (also can be a string alias, referring to the view registered in the Object Container). Rendered by the route controller.

options

object = undefined

These are optional, however it accepts object with following properties and their respective defaults:

{
onlyUpdate: false,
autoScroll: true,
allowSPA: true,
documentView: null,
managedRootView: null,
viewAdapter: null,
middlewares: []
}

onlyUpdate

boolean | function = false

When only the parameters of the current route change an update method of the active controller will be invoked instead of re-instantiating the controller and view. The update method receives prevParams object containing - as the name suggests - previous route parameters.

If you provide function to the onlyUpdate option; it receives 2 arguments (instances of previous controller and view) and it should return boolean.

autoScroll

boolean = true

Determines whether the page should be scrolled to the top when the navigation occurs.

allowSPA

boolean = true

Can be used to make the route to be always served from the server and never using the SPA (when disabled) even if the server is overloaded.

This is useful for routes that use different document views (specified by the documentView option), for example for rendering the content of iframes.

documentView

AbstractDocumentView = null

Custom DocumentView, should extend the AbstractDocumentView from @ima/core.

managedRootView

function = null

Custom ManagedRootView component, for more information see rendering process.

viewAdapter

function = null

Custom ViewAdapter component, for more information see rendering process.

middlewares

function[] = []

Array of route-specific middlewares. See the middlewares section for more information.

Route params substitutions

The parameter name can contain only letters a-zA-Z, numbers 0-9, underscores _ and hyphens - and is preceded by colon :.

router.add(
'order-detail',
'/user/:userId/orders/:orderId',
OrderController,
OrderView
);

The userId and orderId parameters are then accessible in OrderController via this.params:

import { AbstractController } from '@ima/core';

class OrderController extends AbstractController {
load() {
const userPromise = this._userService.get(this.params.userId);
const orderPromise = this._orderService.get(this.params.orderId);

return {
user: userPromise,
order: orderPromise
}
}
}

Optional parameters

Parameters can also be marked as optional by placing question mark ? after the colon :.

router.add(
'user-detail',
'/profile/:?userId',
UserController,
UserView
);
caution

Optional parameters can be placed only after the last slash. Doing otherwise can cause unexpected behavior.

Linking between routes

URLs to routes can be generated via the Router.link() public method. These can be then used in ordinary anchor tags and IMA.js makes sure, to handle the site routing in SPA mode, rather than doing redirect/reload of the whole page.

import { AbstractComponent } from '@ima/react-page-renderer';

class OrderView extends AbstractComponent {
render() {
const { user, order } = this.props;

const orderLink = this.link('order-detail', {
userId: user.id,
orderId: order.id
});

return <a href={orderLink}>View order</a>
}
}

This is done by listening to window popstate and click events and reacting accordingly (in the listen method of ClientRouter, which is called by IMA.js on client during app init). If the handled URL is not valid registered app route, it is handled normally (e.g you are redirected to the target URL).

tip

You can use this.link helper method in IMA.js abstract component or the useLink hook from the @ima/react-hooks plugin in your components and views to generate router links.

note

Under the hood, this.link() is only alias for this.utils.$Router.link, where this.utils is taken from this.context.$Utils.

For more information about this.utils and $Utils objects, take a look at the React Context in the documentation.

Linking in Controllers, Extensions, Helpers and other Object Container classes requires you to import Router using dependency injection. To do that you can either use Router class in the dependency array, or $Router string alias:

import { AbstractController } from '@ima/core';

export default class DetailController extends AbstractController {
static get $dependencies() {
return ['$Router'];
}

constructor(router) {
this._router = router;
}

load() {
// ...
}
}

Then you get Router instance as the constructor's first argument, which gives you access to it's link public method (and many others), that you can use to generate your desired route URL:

load() {
const detailLink = this._router.link('order-detail', {
userId: user.id,
orderId: order.id
});

return { detailLink };
}

Error and NotFound route names

There are two special route names that @ima/core exports: RouteNames.ERROR, RouteNames.NOT_FOUND. You can use these constants to provide custom views and controllers for error handling pages.

./app/config/routes.js
import { RouteNames } from '@ima/core';

import { ErrorController, ErrorView } from 'app/page/error';
import { NotFoundController, NotFoundView } from 'app/page/not-found';

export let init = (ns, oc, config) => {
const router = oc.get('$Router');

router
.add('home', '/', HomeController, HomeView)
.add(RouteNames.ERROR, '/error', ErrorController, ErrorView)
.add(RouteNames.NOT_FOUND, '/not-found', NotFoundController, NotFoundView);
}

Redirects

In addition to the link method mentioned above (which handles URL generation for given routes), you can use Router.redirect() method to redirect directly to the targeted URL.

This URL can be either existing app route or external URL. As with links, in this case you also get SPA routing, in case of redirection to different IMA.js app route.

import { AbstractController, Router } from '@ima/core';

export default class DetailController extends AbstractController {
static get $dependencies() {
return [
Router // We're using class descriptor in this case for DI
];
}

constructor(router) {
this._router = router;
}

init() {
this._router.redirect(
this._router.link('order-detail', {
userId: user.id,
orderId: order.id
});
);
}
}
info

On client side, redirections are handled by simply changing the window.location.href, while on server you're using the express native res.redirect method.

Method signature

The redirect method has following signature, while the options object is available only on server side:

redirect(
url = '',
options = {} // Available only on server side
)

url

string

Target redirect URL.

options

object = {}

Additional options, used to customize redirect server response.

{
httpStatus: 302,
headers: undefined,
}

httpStatus

number = 302

Custom redirect http status code.

headers

object = undefined

Custom response headers.