You are currently viewing Angular Routing and Navigation Tutorial
Angular Routing and Navigation

Angular Routing and Navigation Tutorial

Implement routing in Angular to enable navigation from one view to another. Users click buttons, anchor links and select options in drop-downs to perform tasks in web applications. Routing plays a key role in any modern web application and Angular makes it very easy to implement routing with router outlet.

Angular can understand a URL and take the user to a view in an application. It supports programmatic navigation through component events such as a button click. Angular routing mechanism also logs an activity every-time the URL changes so the back and forward buttons are supported.

Let’s generate a new Angular app with routing and look at how we can create routes for navigation in Angular.

Project Setup

We will start by create a new app using the command-line interface (CLI).

ng new AngularRouting
cd AngularRouting

If you have never used Angular CLI or created a project, get started with Angular.

When you’re asked “Would you like to add Angular routing?”, type y and hit enter.

Also, pick CSS for stylesheet format.

Angular Routing Starter Project
Angular Routing Starter Project

You’ll notice the src folder has a file called app-routing.module.ts. This module is imported into AppModule so that Angular knows the routing configuration of the app.

This is where we import the Angular Router service and define global routes in the application. The routes array is empty because we have not configured it yet.

Angular Router is a singleton service that is responsible for finding a component for a given URL. Singleton means there will only be one router object in the application. When the URL changes, Angular checks the routes array and tells the router to display the correct view.

Tip! You can tell Angular to log all its internal events to the console by setting the enableTracing flag:

imports: [RouterModule.forRoot(routes, { enableTracing: true })]

You should only turn this on for debugging purposes.

<base href> Element

To avoid issues in the long term, please open index.html in the root and ensure it has the <base href> element in the head and set to "/".

<!doctype html>
<html lang="en">
<head>
  ...
  <base href="/">
  ...
</html>

What is the base-href element needed for?

The base href element tells Angular routing how to construct internal links. It is set to "/" because the app folder lives in the root folder.

router-outlet

Open up the app.component.html file, where you will find the router-outlet element.

<router-outlet></router-outlet>

You can think of router-outlet as a placeholder in your application. Angular will inspect the URL, match it to a route in app-routing.module.ts and load the component for that route. Let’s create routes and see navigation in action!

Eager-loading Components

Eager-loading means every component in your route setup will be automatically loaded when the application starts. When the route changes, Angular will instantly swap views because all the components are in memory.

Generate a few components (use ng g c home if you’re feeling like a ninja):

ng generate component home
ng generate component customers
ng generate component account

Populate the routes for these components in app-routing.module.ts:

const routes: Routes = [
  // Load home component by default
  { path: '', component: HomeComponent },
  // /home loads home component
  { path: 'home', component: HomeComponent },
  // /account loads account component
  { path: 'account', component: AccountComponent },
  // /customers loads customers component
  { path: 'customers', component: CustomersComponent },
  // Wildcard route handles all other given values
  { path: '**', component: HomeComponent }
];

Add links to app.component.ts for navigation:

<div>
	<ul>
		<li>
			<a routerLink="/home">Home</a>
		</li>
		<li>
			<a routerLink="/customers">Customers</a>
		</li>
		<li>
			<button (click)="goToAccount()">Account</button>
		</li>
	</ul>
</div>
// Components will be injected here
<router-outlet></router-outlet>

As you can see above, our navigation bar consists of HTML anchor tags with routerLink directive instead of traditional href attribute.

Router link directive tells Angular router to perform navigation for a given route. If we had used href, the browser would do a traditional navigation. Unlike the href attribute, you can use routerLink on buttons for navigation.

Account button has a click event handler, which uses the router to navigate to the account route.

export class AppComponent  {
  constructor(private router: Router){}
  goToAccount(){
    this.router.navigate(['/account']);
  }
}

Run your application using ng serve --open in a new window to test it out.

Alternatively, run the live demo below.

That was easy!

But here’s the catch: Eagerly loading components on startup is fine for demo projects but the initial startup time will increase as your app grows. How can you ensure the app loads faster? The answer is to load components only when you need them with lazy-loading.

Lazy-loading Feature Modules

The size of your app will increase as you add more functionality to your app because you will have many components. You’ll reach a point where the app takes a long time to load initially. With lazy-loading (also known as asynchronous routing), we can load components on-demand when the user navigates to that component.

In order to implement lazy-loading, we need to organise the application a little bit.

We first need to create a feature module for each area we’d like to lazy-load. So for customers, we’ll have a feature module called customers.

Creating a module for customers will also allow us to place other customer-related components. Read more about using feature modules in Angular.

ng new AngularLazyRouting
cd AngularLazyRouting
ng generate module home
ng generate component home/Home
ng generate module customers
ng generate component customers/CustomerList
ng generate component customers/CustomerDetail
ng generate module account
ng generate component account/AccountHome

Execute commands above one at a time to generate a new project with routing and three feature modules.

Each module has an initial component to display.

Customers feature module also has a detail component, which is for demonstrating how to setup routing in a feature module.

Run the demo below to see what it looks like in action.

You’ll notice that I haven’t changed the navigation links in app.component.ts. These links map to the same global routes so there is no need to change anything.

We enable lazy-loading of feature modules with loadChildren property, setting it to the path of the module and specifying the class name at the end.

Here is the new routes array:

const routes: Routes = [
  {
    path: '',
    loadChildren: './home/home.module#HomeModule'
  },
  {
    path: 'home',
    loadChildren: './home/home.module#HomeModule'
  },
  {
    path: 'account',
    loadChildren: './account/account.module#AccountModule'
  },
  {
    path: 'customers',
    loadChildren: './customers/customers.module#CustomersModule'
  }
];

With this configuration of global routes, each feature module is loaded into the application as chunks when the user navigates to that area. Thus, each module must define its own routes.

For example, customer path tells Angular to look at the CustomersModule routes for navigation. Upon inspecting the customers-routing.module.ts file, you can observe that it has an empty path for showing the list of customers, and customer-detail path for showing the details of a customer.

We’re effectively combining eager-loading and lazy-loading here.

Because the AppComponent loads on startup (eager-loading) and feature modules are loaded on-demand (lazy-loading).

// customers-routing.module.ts
const routes: Routes = [
  { path: '', component: CustomerListComponent },
  { path: 'customer-detail', component: CustomerDetailComponent },
];

In a real-world app, you’d want to pass some kind of a customer identifier to CustomerDetailComponent for retrieving the data. Look at how to pass data through routes with parameters, if you want to achieve this.

Pre-loading Modules

We’ve seen how to load all the components on startup with lazy-loading. and loading feature modules on-demand with lazy-loading.

Pre-loading is something in between eager-loading and lazy-loading.

If you envisage that a user is highly likely to visit the account area after loading the application, you can pre-load the account module in the background.

This means that the account module is not loaded on startup so the app loads fast initially.

Since the account module loads in the background, it will probably finish loading the module by the time user navigates to account area.

// app-routing.module.ts
const routes: Routes = [
  //...
  {
    path: 'account',
    loadChildren: './account/account.module#AccountModule',
    data: { preload: true }
  }
  //...
];

You can re-load all feature modules, however, this might not be a great idea for mobile users in the context of a large application.

RouterModule.forRoot(
  appRoutes,
  { preloadingStrategy: PreloadAllModules }
)

Highlighting Active Route Link

When a user navigates to customers area, you may want to highlight that customers link in the navigation bar.

RouterLinkActive directive allows you to add a css class when the route becomes active.

<a routerLink="/home" routerLinkActive="active">Home</a>

You can then style the active link using CSS.

.active{
  color: red;
}

Here is a demo for highlight current nav link.

Passing Data Through Routes with Parameters

In most web applications, it is a common requirement to pass some kind of an identifier or an object to a view. Angular router uses patterns to pass route parameters and extracts parameters with ActivatedRoute service.

For example, when the navigation occurs to customer detail view, you would need to know the id of the customer to retrieve that customer from a back-end API.

Angular router supports route parameters through token placeholders in the path property.

// customers-routing.module.ts
const routes: Routes = [
  { path: '', component: CustomerListComponent },
  { path: 'customer-detail/:id', component: CustomerDetailComponent },
];

Pay attention to the :id token the the route definition for CustomerDetailComponent.

The id token allows us to pass data into a component, which can be used to populate the view of that component.

For example, the following URL tells the router to show CustomerDetailComponent and supplies 29 as the id.

customers/customer-detail/29

or from typescript code using Router:

constructor(private router: Router) {}
this.router.navigate(['/customers', 29]);

This then allows the detail component to extract the id parameter with the ActivatedRoute service. Since we subscribe to the route, ensure you import Observable from rxjs.

// customer-detail.component.ts
constructor(private route: ActivatedRoute) {}
ngOnInit() {
  this.route.params.subscribe(params => {
    alert(params.id);
  });
}

Here is a live demo for passing customer id in the route:

Passing Multiple Parameters

In some cases, you may want to pass multiple parameters in Angular routes. This can be achieved with the following syntax in route parameters array:

// customers-routing.module.ts
const routes: Routes = [
  { path: '', component: CustomerListComponent },
  { path: 'customer-detail/:id/:name', component: CustomerDetailComponent },
];

The above configuration implies that a second parameter, name, is expected in the URL.

You can pass the second parameter as an additional value in the navigate() method like so:

constructor(private router: Router) {}
this.router.navigate(['/customers', 29, 'John Doe']);

Pass Complex Objects in the Route

Angular router makes it easy when it comes to passing complex objects as parameters. There is no need to use a different route configuration, just remove the name of the parameter. Angular will pass object values as a matrix in the URL.

// customers-routing.module.ts
const routes: Routes = [
  { path: '', component: CustomerListComponent },
  { path: 'customer-detail', component: CustomerDetailComponent },
];

We can pass the customer object into the component using the following routerLink syntax. Define a customer object in customer-list.component.ts and pass it into the routerLink value.

customer: any = { id: 29, name: "Mr John Doe" };
<a [routerLink]="['/customer-details', customer]">View customer detail</a>

You can also achieve this programmatically using typescript:

constructor(private router: Router) {}
const customer = {name: "Mr John Doe"};
this.router.navigate(['/customers/customer-detail', customer]);

Extracting the object from the route is same as previously; just subscribe to the paramaters as shown below.

// customer-detail.component.ts
constructor(private route: ActivatedRoute) {}
ngOnInit() {
  this.route.params.subscribe(params => {
    alert(params.id + " " + params.name);
  });
}

Summary

Well done for making it this far!

In this post, we setup a new Angular project with routing and implemented navigation with eager-loading, lazy-loading and pre-loading techniques. We also looked at how to highlight navigation links with CSS and passed data through route parameters.

Umut Esen

I am a software developer and blogger with a passion for the world wide web.

Leave a Reply