Angular Routing Guide: How to optimize app navigation

Feb 09, 2021 - 12 min read
Ryan Thelin
editor-page-cover

Angular is one of the most popular front-end frameworks, with 30% of developers preferring it for their projects. Angular is especially useful for large scale projects with many different views and components.

The key to making these large scale projects engaging is a logical navigation structure that allows users to easily explore and revisit pages. Thankfully, Angular’s routing functionality makes optimizing navigation simple.

Today, we’ll learn more about Angular Router’s capabilities and help you make a fully navigable app.

Here’s what we’ll cover today:


Design complex Angular apps with ease

Learn to use all the best Angular tools to create stunning and efficient web apps.

Angular: Designing and Architecting Web Applications


What is Angular Router?

The Angular Router is an importable package built-in to Angular 2+ by default. It’s used to build Single Page Applications with multiple views that can be navigated by URL, known as “routing”.

URLs consist of a domain name and a route definition, known as a path. A path is a JavaScript object that the server uses to access a specific resource in the database. When the server serves our application, Angular will grab the path from the URL and match it against any valid paths we’ve set up. Essentially, we set a key/value relationship with a path like /blog as the key and the desired page as the value.

This allows users to easily navigate your app and visit the page they want without having to start at the home component. Routing enables support for common browser behaviors like forward/back arrows and page bookmarking.

Router also contains tools for advanced behaviors like multiple router outlets, different path matching strategies, easy access to route parameters, and route guards to protect components from unauthorized access.


Routing Module and RouterOutlet

Routing Modules are special Angular modules that define new routes and help configure the router. All routing modules have the suffix -routing after their name, which is automatically added by Angular CLI.

Every routing module sets the routing behavior for a paired module with the same base name. For example, the routing behavior for our home module would be in the routing module home-routing.

Here’s an example of a routing module for our home module, called home-routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeMainComponent } from './home-main/home-main.component';
 
const routes: Routes = [
  { path: '', component: HomeMainComponent }
];
 
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class HomeRoutingModule { }

You can find our routes in the routes array variable. Each element of the routes array represents the route to a single component view.

The elements consist of two parts, the path property that provides a URL path and the component property that defines which component will be loaded at the provided path.

In this case, we enter an empty string (interpreted as a forward slash) to indicate that this component is our homepage and should load if someone just enters the domain name. We then enter the name of the component Angular should fetch as our homepage, HomeMainComponent.

We can also use redirectto to direct empty path URLs to another page if we’d prefer users land elsewhere.

Next, we’ll need to remove HomeMainComponent from the exports of HomeModule. This use of routing means we’re no longer exporting the component and instead let Router take care of loading the component if a user visits the route.

Finally, we’ll replace the contents of the app.component.html file with the line:

<router-outlet></router-outlet>

Here, <router-outlet> acts as a placeholder for the component. Instead of defining a component, our template will simply pull whatever component is rendered with the passed URL path. By using this placeholder, we don’t have to export the component. Instead, we can export the module.

You can now view this app by entering http://localhost:4200 in your browser address bar.

Export relationships among modules

To review, the HomeRoutingModule is a routing module where we define routes. We have one route that consists of a blank path. We’ll check if the client’s URL entry matches that path. If they are, we’ll load the homepage via HomeMainComponent.

The homepage component is available due to a series of imports. First, we import the home-routing module into its paired standard module home. Then, we import the home module into the app module. Finally, we use the <router-outlet> directive in the app.component.html file to load the HomeMainComponent registered in the original routes array.


What are Wildcard Routes?

What happens when a user enters an invalid path? We can avoid an error by including a Wildcard Route, which catches all unregistered paths and directs them to a certain page. You can think of Wildcards as an “other” category that reads as a match to any unregistered paths.

Most sites have a Wildcard that directs to a “404 Page Not Found” page. To create an error component for our app, enter the following into your command prompt:

ng generate component PageNotFound

We do not need a module because this error screen will just be simple text.

You can set a Wildcard by entering ** in place of a standard path in the routes array.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
 
const routes: Routes = [
  { path: '**', component: PageNotFoundComponent }
];
 
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Now, any invalid URL will redirect to our 404 error page.

We need to make sure this component is imported last in the app.module.ts file for our other components to load properly. This is because Angular loads the component from the first matching path. If AppRoutingModule is imported first, Angular would always load PageNotFoundComponent because the Wildcard would always read as a match and therefore Angular would return that component.

  imports: [
    BrowserModule,
    HomeModule,
    AboutModule,
    ContactModule,
    AppRoutingModule,
  ],
 

The Wildcard at the bottom of the imports array ensures that any valid matches are returned and the 404 is only returned if there are no other matches.


Child Routes

Sometimes it makes sense to have routes categorized as a subgroup within a route. For example, our “About Us” page could feature separate subpages for info on the employees, /about/team and info on past clients, /about/clients. Child components are only rendered if the user is on the parent /about path.

First, we’ll generate the components by entering the following into our command prompt:

ng generate component about/team
ng generate component about/clients:

We then set these as children of the “About Us” page by adding a children array property to the about route in about-routing.module.ts.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AboutMainComponent } from './about-main/about-main.component'
import { BioComponent } from './bio/bio.component';
import { TeamComponent } from './team/team.component';
import { ClientsComponent } from './clients/clients.component';
 
const routes: Routes = [
  {
    path: '',
    component: AboutMainComponent,
    children: [
      { path: '', component: BioComponent },
      { path: 'team', component: TeamComponent },
      { path: 'clients', component: ClientsComponent },
    ]
  }
];
 
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class AboutRoutingModule { }
 

The children array acts like a smaller version of the routes array, with similarly-formatted path and component properties. The difference is that child routes’ path properties are appended onto their parent path, meaning you do not need to write the full path.

For example, the full path to reach TeamComponent would be /about/team rather than just '/team'.

Finally, we’ll update the about-main.component.html template file with <router-outlet> to let it show any of the child components of about.

<h1>About Page</h1>
 
<router-outlet></router-outlet>

Continue learning Angular.

Pick up Angular in half the time. Educative’s hands-on courses let you learn top industry skills with real-world practice, not lengthy video lectures. By the end, you’ll know how to create fully-fledged Angular apps.

Angular: Designing and Architecting Web Applications



The RouterLink Directive

Most apps will allow users to navigate with a specific URL and user-clicks on link elements. We’ll need Bootstrap to add links. You can make links using standard href attributes. However, that requires the site refresh and assets reload whenever the page changes.

We can speed up loading using Angular Router’s routerLink directive, which leverages history API to let Angular access your immediate browser history. This means browsers only need to load each page once, as any later visits can display the previously loaded elements.

To implement routerLink, replace the contents of app.component.html with:

<nav class="navbar navbar-expand-md navbar-light bg-light mb-4">
  <a class="navbar-brand" routerLink="/">Website</a>
 
  <div class="collapse navbar-collapse">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item">
        <a class="nav-link" routerLink="/about">About</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" routerLink="/contact">Contact</a>
      </li>
    </ul>
  </div>
</nav>
 
<main class="container">
  <div class="card">
    <div class="card-body">
      <router-outlet></router-outlet>
    </div>
  </div>
</main>

The URL in the address bar will still change when navigating through these links, but the browser will not refresh the page or reload assets on return visits.


Adding ActiveLink Styles

Building on our new navigation links, we also need a way to tell the user which page they’re currently on. The best way to do this in Angular is to use the active class, which will change the style of a link if they’re currently on to indicate it is active.

In Bootstrap, the active class can be applied to the <li> element wrapped around the <a> element. We’ll use Angular’s routerLinkActive directive to achieve this.

<nav class="navbar navbar-expand-md navbar-light bg-light mb-4">
  <a class="navbar-brand" routerLink="/">Website</a>
 
  <div class="collapse navbar-collapse">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item" routerLinkActive="active">
        <a class="nav-link" routerLink="/about">About</a>
      </li>
      <li class="nav-item" routerLinkActive="active">
        <a class="nav-link" routerLink="/contact">Contact</a>
      </li>
    </ul>
  </div>
</nav>
 
<main class="container">
  <div class="card">
    <div class="card-body">
      <router-outlet></router-outlet>
    </div>
  </div>
</main>

We’re applying the directive on the <li> elements with the nav-item class. This directive will check if the URL in the address bar matches the path in the routerLink directive.

If the path matches, we’ll add it to the active class to change the link text to show that it’s active with the darker text color.



Lazy loading modules

We can improve the performance of our module by transitioning from eager loading to lazy loading.

Eager loading is when the browser is directed to load all components within the app module, regardless of which it will use.

Eager Loading at startup

Lazy loading instead splits the module into separate files so the app only loads the components it needs for the current page render. Lazy loading is often preferred as it allows the page to load the minimum amount of data for each render and therefore speeds up loading.

Lazy Loading at startup

To implement lazy loading, we first remove all module imports from app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
 
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
 
@NgModule({
  declarations: [
    AppComponent,
    PageNotFoundComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We still eager load PageNotFoundComponent, as it is low weight, and we could need it at any point.

We also need to update this information in the routes array found in app-routing.module.ts. This is the only routing module that will be sent at the user’s initial request. Angular can then use this file to load any future modules as needed.

const routes: Routes = [
  { path: '', loadChildren: () => import('./home/home.module').then(m => m.HomeModule) },
  { path: 'about', loadChildren: () => import('./about/about.module').then(m => m.AboutModule) },
  { path: 'contact', loadChildren: () => import('./contact/contact.module').then(m => m.ContactModule) },
  { path: '**', component: PageNotFoundComponent },
];

Notice that we’re not using the component property to tell Angular what component to load when the route is visited. Instead, we’re using the loadChildren property. This will tell Angular to lazy load a module. We’re setting it to an arrow function, which will request the module through the import() function. The import() function returns a promise. We chain the then() function to handle the response.

These loadChildren paths will pass along any previous path elements as a prefix for later paths. We must therefore update each of our routing modules’ Routes array to empty paths to ensure we do not repeat path names like /about/about.

{ path: '', component: AboutMainComponent }

What to learn next

Congratulations on making a fully navigable Angular application! Routing is the key to keep users engaging with your app, especially for large applications. However, it’s just one part of making an excellent Angular app.

Here are some more advanced concepts you’re ready to tackle along your Angular journey:

  • Advanced Routing (private routes, pairing CSS stylesheets)
  • Lifecycle Hooks
  • Modal Components
  • Authentication
  • Dependencies

To help you study these topics, Educative has created Angular: Designing and Architecting Web Applications. This course teaches you how to create large scale Angular applications in a logical and efficient way using advanced Angular techniques. You’ll even build a full-fledged application alongside the course.

By the end, you’ll have hands-on experience, plus a project for your professional portfolio.

Happy learning!


Continue Reading about Angular 2+ and front-end development


WRITTEN BYRyan Thelin

A free, bi-monthly email with a roundup of Educative's top articles and coding tips.