publication

Angular: Routing

Miquel Canal

Thursday 11, May 2017
  • Angular

Angular is based on the single page application (SPA) concept and the routing functionalities allow to simulate the typical html flow pages via URL parameters. The content of the site will automatically update without the need of a constant page reload typical from static websites.

This will cover the very basic snippets to set up a router and the internal navigation between Angular components and services. The official Angular documentation should be reviewed for a better understanding.

First step is to create a new module which will control the render of components according to the current route. Then, a few imports and declarations need to be included in order to define each link of an application.

import { Routes, RouterModule } from '@angular/router';

const appRouting: Routes = [
    {
        path: '',
        component: HomeComponent
    },
    {
        path: 'users',
        component: UsersComponent
    },
    {
        path: 'servers',
        component: ServersComponent
    }
];

@NgModule( {
    imports: [
        RouterModule.forRoot( appRoutes )
    ],
    exports: [
        RouterModule
    ]
} );

export class AppRoutingModule {
}

The previous snippet shows a basic module named AppRoutingModule which defines three router links and stablishes which component should be rendered according to a given /path.

The forRoot receives the array of available paths and generates a NgModule. To make use of the new AppRoutingModule it should be added into the imports array of the main AppModule

Router Outlet

The router-outlet directive indicates where angular will render each component defined on the router. If an application uses child routing or nested routes it will use multiple router-outlets, one per each level of navigation.

<router-outlet></router-outlet>

Internal Navigation

The internal navigation between components and pages of an application should not be using link elements < a href='#' > because that will reload the application thus losing the single page app (SPA) functionality. Instead, Angular ships with a native directive to navigate through an application, the routerLink. A basic implementation of use:

<ul>
    <li><a routerLink='/'>Home</li>
    <li><a routerLink='/users'>Users</li>
    <li><a routerLink='/servers'>Servers</li>
</ul>

To handle an active page routing, angular uses the routerLinkActive attribute to assign a class to the component. To ensure an exact match of the path, use the attribute [routerLinkActiveOptions]='{exact: true}'.

<li routerLinkActive='active' [routerLinkActiveOptions]= "{exact: true} ">
    <a routerLink='/'>Home</a>
</li>

On the previous example the routerLinkActive will set the class active to the < li > element when the application path matches '/'

Navigation can also be triggered from within a method. To do so, define a new property of Router type on the constructor and use the navigate() method passing in the destination path.

constructor( private router: Router) {
}

navigateToUsers(){
    this.router.navigate(['/users']);    
}

Dynamic URL parameters

There are cases where URLs need to be set dynamically and the component must receive certain parameters passed on the URL path. The following snippet covers an example of a component which gets an ID from the URL parameters.

On the routing module, define the parameter name using the /:para_name convention, in this particular case will be /:id:

const appRouting: Routes = [
    {
        path: 'users/:id',
        component: UserComponent
    }
]

Then retrieve the parameter when the component initialises:

// Required import
import  { ActivatedRoute } from '@angular/router';

// Property declaration
routeSubscription: Subscription;
identifier: string;

constructor(private route: ActivatedRoute) {
}

// Get URL parameters upon component initializes
ngOnInit() {
    this.routeSubscription = this.route.params.subscribe(
        ( params: Params ) => {
            this.identifier = params['id'];
        }
    );
}
            

Any Angular subscription should always be stored into a class property so then can be cleared when the component is destroyed. Angular allows to access the lifecycle of a component using their “hooks” methods which trigger certain functionality when the status of a component changes.

Once Angular instantiates a new component it does not re-instantiate a new one each time a routing to that component is triggered. That would imply an unnecessary processing effort. However, this causes may cause problems if the component receives data from the URL parameters because the data is not reloaded. To avoid this issue, the component should always subscribe to the router.params as described above.

The internal navigation between components also supports passing GET parameters on the URL. This can be accomplished by binding the property queryParams into an element which uses the < li >routerLink< /li > as shown in the following example:

<!-- This will set a link to the path: /users/10?allowEdit=true -->
<a routerLink='/users/10' [queryParams]="{allowEdit: 1}">User</a>;

Then it is the component who access to the parameters using a similar approach than the one described early on the post. The main difference to be noticed here is that to the GET parameters are accessed using the route’s queryParams subscription. An example to access query parameters and router parameters when the component initialises:

ngOnInit() {
    // Accessing the URL params ( id = 10 on this example )
    this.route.params.subscribe(
        ( params: Params ) => {
            this.identifier = params['id'];
        }
    );

    // Accessing the URL GET parameters ( allowEdit = true on this example )
    this.route.queryParams.subscribe(
        ( params: Params ) => {
            this.allowEdit = params['allowEdit'] === '1';
        }
    );
}

Child routing and nested router

A router can define multiple child paths for a specific route. This creates a node-tree structure which gives a relation between multiple paths and sections of an application. There can be as many levels and the application requires, making it easy to visualise each parent-child relation.

const appRouting: Routes = [
    {
        path: '',
        component: HomeComponent
    },
    {
        path: 'users',
        component: UsersComponent,
        children: [
            {
                path: 'users/:id',
                component: UserComponent
            }
        ]
    },
    {
        path: 'servers',
        component: ServersComponent,
        children: [
            {
                path: 'server/:id',
                component: ServerComponent
            },
            {
                path: 'server/:id/edit',
                component: EditServerComponent
            }
        ]
    }
]

The previous snippet exposes a nested router for the paths: /users and /server. In this case the parent components need to insert the directive for angular to place its children but additionally, the nested components also need to use the a to implement their child components.

In those cases where nested routes needs to cover static and dynamic routes, the static one should always be placed first:

{
    path: 'new',
    component: NewComponent
},
{
    path: ':id',
    component: SpecificComponent
}

Redirects and 404 statement

Any path defined inside the Routes array can accept the redirectTo parameter which can be used to redirect from one route to another without the need of defining a new component for it. This is recommended for handling 404 errors and can be combined with a definition that will match all paths. That item should always be placed the last on the array to ensure is the last matching option.

{
    path: 'not-found',
    component: NotFoundComponent
},
{
    path: '**',
    redirectTo: 'not-found'
}

Guards and resolvers

When a certain route is matched in the router, Angular is able to run some methods before loading a component associated to it. The services that expose those methods are known as Guards and there are several types of interfaces that routers support:

// To mediate navigation to a route.
CanActivate

// To mediate navigation to a child route.
CanActivateChild

// To mediate navigation away from the current route.
CanDeactivate

// To perform route data retrieval before route activation.
Resolve 

// To mediate navigation to a feature module loaded asynchronously.
CanLoad

To cover the very basics, I will be focused on the CanActivate interface which allows to determinate weather a certain router path can be accessed or not. To do so a new service should be created and it need to implement the CanActivate method. That method must return a boolean value which will set the resolution of the navigation.

The following example uses the CanActivate interface to allow a user to access the /edit path only if it has the rights under the userConfigurationService service:

// The router defines a path which implements the canActivate interface
const appRouting: Routes = [
    {
        path: 'edit',
        component: EditComponent,
        canActivate: [ AuthorizationGuard ]
    }
]

// AuthorizationGuard service controls the access to the resource
export class AuthorizationGuard implements CanActivate {
    constructor( private userConfig: userConfigurationService) {

    }

    canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): boolean {
        return this.userConfig.canEdit();
    }
}
Create Angular Component after Application Initializes

Create Angular Component after Application Initializes

Basic learning of how to use build-in tools provided by Angular in order to create components after an application has been loaded.

Angular: Data Binding

Angular: Data Binding

Data communication between the logic component and DOM elements of an Angular application.

TypeScript Models

TypeScript Models

Basic overview of the TypeScript and ECMAScript 6 models. An example of use case to exemplify the use of models in external classes.

This site uses cookies to ensure a great experience. By continue navigating through the site you accept the storage of these cookies.