Angular 2 Router

Gravity
7 min readAug 26, 2017

--

Intro

Angular 2 RouterModule is Angular’s built in module for in app navigation. Unlike AngularJS which shipped with ngRoute module which was most of the time insufficient for more complex apps, Angular 2 RouterModule adapts best practices from ui-router making it a great choice for routing. It offers nested views, lazy loading, navigation guards, and many more.

Getting started

Router is independent module separated from core. It’s located in package @angular\router. To get us started we need to import RouterModule from package:

import { RouterModule } from ‘@angular/router’;

HTML 5 navigation mode is router’s default mode. It uses browser’s history.pushState to update the browsers history and making the server URLs same as the in-app URLs. Prerequisite for HTML 5 navigation is setting up the <base href> in apps root index.html file which marks the root folder and tells the router where to find the app files and content. <base href> is usually first child node inside <head>.

Setting up the router

Because RouterModule is quite large module with many features best way to start learning is to create simple app from scratch. Our app is virtual library with private and public books. Public books can be accessed by anyone, changed and saved. Accessing private books will require authorization. We’ll start with simple stuff and later introduce more complex features.

Defining routes

All routes belonging to feature module should be placed into separate routing module. Projects do have tendency to grow and with routes declared inside separate module it is easier to extend it, maintain and run tests.

Initial version of library app has 2 defined routes inside routing module. Route to public and private components. Routes are defined as array of Route objects:

Route object minimally should consist of path property and component which will be shown on that exact path. Besides public and private routes we can notice route with the empty path and route with 2 asterisk(**) symbols.

path : ‘ ’ - Empty path route is a nice way to set default route inside module. Empty path route requires property redirectTo to redirect us to an existing route. pathMatch property is also required and it can have values full(unmatched segment is equal to ‘ ’) or prefix(unmatched segment begins with route prefix ).

path : ‘**’ - Matches every URL, even invalid ones. It will be triggered if user navigates to non defined, non existing route. It can be used to redirect user to custom 404 error component.

Router uses first match wins strategy, so it is important to place wildcard and empty path at the end of the configuration. In case that wildcard is placed first, it would match all the routes, and every URL would redirect us to the PageNotFoundComponent.

Configuring router

Now when routes are created, Router must be configured using forRoot static method which defines routes.

RouterModule.forRoot(appRoutes)

forRoot method should be called only once inside root app routing module. It is important to remember that order of module importing matters. App routing module should be always last. Routes are defined in order in which the modules are injected. We can imagine it as if the feature routing modules are augmenting app routing module.

Now when our routes are registered and ready, in order to display them, router requires <router-outlet> directive. It marks the place where routes will be displayed. We’ll add the <router-outlet> directive to the root app component.

You notice the routerLink and routerLinkActive directives from example? Those 2 directives are ui-router’s equivalent with ui-sref and ui-sref-active directives. routerLink directive redirect us to the route defined in the binding. Directive accepts string for URL or array syntax if we are going to pass additional data like fragment or queryParams. routerLinkActive adds class to the element if the route is activate.

Our library is starting to form:

Check the demo on Plunker.

Async(lazy) loading

It is time to add some content to the our library, so we should move public and private features to it’s own modules. Our feature modules will display list of books which we can select and edit. Separating feature modules enables route grouping and thus every feature module can be lazily loaded. This feature enables to load part of app on demand and therefore it will decrease initial app load. We can add more modules without need to worry about if it will have impact on first load.
To enable module async load loadChildren property must be defined on route definition. loadChildren takes the relative path to the module, separated with # and then followed with Module Class. This is all what it takes! When the router hits this route first time it will add the lazy loaded module to the route configuration. Lazy loaded feature is loaded just once. App routing module will look next:

Now PublicModule is loaded by default, and PrivateModule will be loaded on demand.

Nested routes

By clicking on one of the books router will redirect us to the new route displaying the details about that book. This means that we need to introduce nested routes. Nested routes are defined inside route’s children property. children property is an array of standard routes .

In order to display the BookDetailComponent inside PublicComponent we need to add the router-outlet directive to the PublicComponent html. By adding the router-outlet directive router will display the PublicComponent inside BookDetailComponent outlet, not inside the main outlet.

Plunker example with lazily loaded feature modules.

Named outlets

Router supports only one unnamed outlet per component but it can have any number of named outlets in component. Each named outlet can have its own routes.

Routes displayed in named outlets are called secondary routes. Secondary routes do not depend on other routes and they can be displayed in combination with other routes. To define secondary routes route definition requires property outlet to be defined with the name of the outlet which will display the route. Named outlets are quite useful for displaying modal(popup) content. Navigation to secondary route requires the router outlet route name:

or using routerLink directive

Displaying secondary route will be displayed as …/(popup:compose). Secondary route is surrounded with parentheses with outlet name and route path.

Route params

In book-detail path definition we can notice the :id part. :id represents route param in route definition. It would be bad to have BookDetailComponent for each book in our library, instead we will pass the id of the book as the route parameter. Activated component can access the route params using the ActivatedRoute service.

ActivatedRoute is the snapshot of current route which enables us to go up and down the route tree to get information. Service contains info about: url, data, params, queryParams, fragment, outlet ,parent, routeConfig ,firstChild and children.

Params property returns an Observable. We can extract the route id inside BookDetailComponent’s constructor and use the id later to get the proper book detail.

Route params can be optional or required. Even if route definition doesn’t have any required params we can still pass the optional route params. Navigating like this from Component:

or using routerLink directive:

would generate url …/public;id=11 with optional route param separated with semicolon(;). Although this syntax isn’t standard it’s valid syntax. This is called matrix URL notation.

Route guards

Routes can be guarded to prevent unauthorized, unauthenticated user from access. Also guards can prevent user from leaving route if there are some pending changes before saving or declining them. Route can have multiple defined guards:

  • canActivate guard - handles navigation to route
  • canActivateChild - handles navigation to child router-outlet
  • canDeactivate - handles navigation away from current router-outlet
  • resolve - data resolve before route activation (usally http requests)
  • canLoad - guards lazy loaded module route

Currently everyone has access to the private section of our library app. Only authorized users should have option to access this route. We will implement canActivate guard which will allow access to private section only to logged users.

For the sake of simplicity of this tutorial user login will require just login button click without the server authorization.

In order to implement route guard we must implement guard interface and method called as the name of guard. i.e. to implement canActivate guard class must implement CanActivate interface and canActivate method. Guards can return boolean, Promise or Observable. Our guard which will protect private section from unauthorized access is fairly simple. It will just check if the user is logged in and return true/false to continue or to cancel the navigation:

Example of canActivate guard on Plunker.

canLoad guard

If async loaded route has canActivate guard the module will be loaded even if user isn’t authorized to access that route. Module will be loaded and the route will be properly guarded against unauthorized access. We can prevent this from happening using the canLoad guard which will prevent module from load if user isn’t authorized.

Updated plunk which uses canLoad for lazy loaded module.

Preloads

Router offers option to preload modules. Preload means that module will be loaded in the background. This feature is turned off by default. For example our private module can be loaded in background if we are assuming that it will be surely activated. Router offers two preloading strategies:

  • no preload (default setting)
  • preload all modules

We can also use custom preload strategy. It is important to know that canLoad on async loaded module blocks preloading, so we will must use canActivate guard if we want to preload it. Preload is defined in the forRoot method of Router by providing config { preloadingStrategy: LoadingStrategy }

--

--

Gravity

Dispatches from Seven Content Delivery, development team in NSoft. Orbiting in Mostar, Bosnia and Herzegovina.