Angular Pages & Routing Tutorial

In this Angular tutorial we learn how to load various components with Routing to simulate different pages to a user without the drawbacks of additional HTTP requests.

We cover how to set up Routes and child (nested) Routes, navigate with links or programmatically and how to style active links.

Lastly we take a look at how to handle 404 errors with Route redirection and wildcards.

What is Routing

Routing is simply the way we navigate to different “pages” in our app.

Technically, we don’t have multiple pages in an Angular, all we’re doing is loading different components.

Loading the components we need, when we need them, makes it look and feel like separate pages to the user, without reloading the page or requesting a new one.

How to setup and load Routes

Before we learn to setup and load routes, let’s get our project ready for demonstration.

We are going to use 3 extra components called ‘home’, ‘about’ and ‘user’ so let’s generate them with the CLI.

Example: CLI Command
ng generate component home

ng generate component about

ng generate component user

Now let’s add them to the main component and set up some simple links that we will later use to navigate to each.

Example: app.component.html
<ul>
  <li><a href="#">Home</a></li>
  <li><a href="#">About</a></li>
  <li><a href="#">User</a></li>
</ul>
<hr>

<app-home></app-home>

<app-about></app-about>

<app-user></app-user>

If we take a look in the browser, we have all three components loaded and working below the menu.

What we want is to load only a specific component when we click on its link.

1. The first thing we need to do is to create an array of URL paths and associate them with their corresponding components.

In the app.module.ts file, create an array constant called ‘appRoutes’ of type Routes . We also need to import the class from ‘@angular/router’.

Example: app.module.ts
...
import { AppComponent } from './app.component';
import { AboutComponent } from './about/about.component';
import { UserComponent } from './user/user.component';
import { HomeComponent } from './home/home.component';

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

const appRoutes: Routes = [];

@NgModule({
  declarations: [
    AppComponent,
    AboutComponent,
    UserComponent,
    HomeComponent
  ],
...

The array expects a Javascript object with our URL paths and matching components. For that we use the path and component keys.

Example: app.module.ts
...
import { AppComponent } from './app.component';
import { AboutComponent } from './about/about.component';
import { UserComponent } from './user/user.component';
import { HomeComponent } from './home/home.component';

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

const appRoutes: Routes = [
  { path: '',      component: HomeComponent }, // www.example.com/
  { path: 'about', component: AboutComponent}, // www.example.com/about
  { path: 'user',  component: UserComponent }  // www.example.com/user
];;

@NgModule({
  declarations: [
    AppComponent,
    AboutComponent,
    UserComponent,
    HomeComponent
  ],
...

If we specify an empty path, it refers to the root of the domain. For example, www.example.com .

We don’t need to add a slash before the path, Angular will do it for us automatically.

2. Just having our routes in the array is not enough, we need to tell Angular that this is the array of routes it should use.

We do that with the RouterModule, so let’s add that to the import list from ‘@angular/router’.

Example: app.module.ts
...
import { Routes, RouterModule } from '@angular/router';

const appRoutes: Routes = [
  { path: '',      component: HomeComponent },
  { path: 'about', component: AboutComponent},
  { path: 'user',  component: UserComponent }
];
...

To register the routes, we use the RouterModule.forRoot() method in the imports array of the @NgModule decorator. This method takes a route array as an argument. In our case, the route array is appRoutes .

Example: app.module.ts
...
import { Routes, RouterModule } from '@angular/router';

const appRoutes: Routes = [
  { path: '',      component: HomeComponent },
  { path: 'about', component: AboutComponent},
  { path: 'user',  component: UserComponent }
];

@NgModule({
  declarations: [
    AppComponent,
    AboutComponent,
    UserComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(appRoutes)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
...

3. At the moment, we display all three components on every page. We want to display only the currently selected component. If we are on the ‘about’ page, we want to render only the ‘about’ component.

For this we don’t use the component’s selector. Instead we use the router-outlet special directive to tell Angular that we want to display the component that matches the URL.

Let’s replace the three component selectors with the new special directive.

Example: app.component.html
<ul>
  <li><a href="#">Home</a></li>
  <li><a href="#">About</a></li>
  <li><a href="#">User</a></li>
</ul>
<hr>

<router-outlet></router-outlet>

Keep in mind that directives may have any selector. So, even though it looks like a component, the router-outlet directive is still only a directive.

This now marks the place in our document where we want the Angular router to load the component of the currently selected route.

If we load the app in the browser, we only see the home component instead of all three. We haven’t set up navigation yet, but we can type in the URLS in the URL-bar to load their components.

How to navigate with Router links

To navigate with router links we need to bind the click event of the link, image or button to a route.

We do this by replacing the href attribute of a link with the routerLink directive.

Syntax:
<a routerLink="/path/to/component">Link</a>

Alternatively, we can give the directive a string array of route names with parameters, known as the Link Parameters array.

Syntax:
<a [routerLink]="['/path', '/to', '/component']">Link</a>

Each element in the array is a segment of the full path.

Note that when using the routerLink directive, we have to add the / at the beginning. Angular does not add it for us automatically.

Let’s add both of these routerLinks to our example for demonstration.

Example: app.component.html
<ul>
  <li><a routerLink="/">Home</a></li>
  <li><a routerLink="/about">About</a></li>
  <li><a [routerLink]="['/user']">User</a></li>
</ul>
<hr>

<router-outlet></router-outlet>

If we run the example in the browser, we can now navigate to each “page” of our app with the links.

How to style active Router links

Angular provides us with the routerLinkActive directive to style our active links based on the currently selected route.

The directive takes a CSS class and can be placed on either the link, image or button itself, or a wrapping element.

Syntax:
// on the link
<li><a routerLink="/path" routerLinkActive="active-class">Link</a></li>

// or on a wrapper
<li routerLinkActive="active-class"><a routerLink="/path">Link</a></li>

Let’s add a simple CSS class that changes the background of the list element if it’s active.

In this case we can add the style to our main app.component.css file, or we can add it to the global styles.css file in the src folder.

We will use the global src/styles.css file just for demonstration.

Example: styles.css
.link-active {
  background-color: gainsboro;
}

We will need to add the directive with this class to all the links.

Example: app.component.html
<ul>
  <li routerLinkActive="link-active"><a routerLink="/">Home</a></li>
  <li routerLinkActive="link-active"><a routerLink="/about">About</a></li>
  <li routerLinkActive="link-active"><a [routerLink]="['/user']">User</a></li>
</ul>
<hr>

<router-outlet></router-outlet>

If we run the example in the browser, we can see that the active link styling is applied as we navigate through the pages.

There is one problem however, the Home link always has the active style. That’s because the directive will, by default, mark the element as active if it contains the path we are on.

The / that’s in the Home link is part of all our other paths, so Angular considers Home as active. To fix this, we need to set a configuration option the directive.

We do this with the routerLinkActiveOptions directive on the element we want to configure. The directive takes a Javascript object for the options with a property called exact .

Example: app.component.html
<ul>
  <li
    routerLinkActive="link-active"
    [routerLinkActiveOptions]="{ exact: true }"
  ><a routerLink="/">Home</a></li>
  <li routerLinkActive="link-active"><a routerLink="/about">About</a></li>
  <li routerLinkActive="link-active"><a [routerLink]="['/user']">User</a></li>
</ul>
<hr>

<router-outlet></router-outlet>

If we set the property to true, we specify that we want Angular to consider this link active only if it matches the exact path. It will now only match / instead of /path .

If we run the example in the browser again, the Home link no longer shows as active when we’re on the other “pages”.

How to navigate Routes programmatically

Angular allows us to navigate throughout our app without links.

For example, the user clicks a login button, the app performs the necessary authorization actions and then we send them to the member area.

We don’t want a routerLink on the button, we only want to send the user to the member area if they login successfully.

1. Let’s start by adding a login button on our Home component.

We will add a click listener and execute the onLogin() method when the user clicks on the button.

Example: home.component.html
<p>home works!</p>
<button (click)="onLogin()">Login</button>

For the demonstration we can just imagine that the method performs the operation of authenticating the user.

2. Next, we want to direct the user to the User page once they click the button, from inside the onLogin() method.

We can inject the router into the component and use the navigate() method on its object.

Example: home.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  constructor(private r: Router) { }

  ngOnInit(): void {}

  onLogin() {
    // Auth user

    // Redirect to User component
    this.r.navigate(['/user']);
  }

}

We inject the Router by importing it from ‘@angular/router’, then creating a new private object, called ‘r’, of type Router.

In the onLogin() method that our button will execute, we use the navigate() method to specify where we want to redirect to.

The method takes a Link Parameters array as an argument where each segment of the path is a separate element.

Syntax:
this.object.navigate(['/path', '/to', 'component']);

If we navigate to Home in the browser and click the Login button, it will send us to User through that navigate() method.

How to setup a child Route

Sometimes we will need to load a component within another component. For this, Angular allows us to set up nested routes, also known as child routes.

For example, let’s say that we want our ‘about’ component to become the user’s “About Me” information and we want it to load only in the ‘user’ component.

We can simply nest the ‘about’ component inside of the ‘user’ component. So, instead of it being /about , it will now be /user/about .

1. First, we need to configure our routes so that ‘about’ will be a child.

To add a nested route, we use the children array and add the child route to it.

Syntax:
{ path: 'path/to/parent',
  component: component_name,
  children: [
    // www.example.com/parent/child
    { path: 'path/to/child', component: child_component_name}
  ]
}
Example: app.module.ts
...
const appRoutes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'user',
    component: UserComponent,
    children: [
      // www.example.com/user/about
      { path: 'about', component: AboutComponent}
    ]
  }
];
...

In the example above, the ‘user’ component is our parent and the ‘about’ component is our child.

2. Because the ‘about’ component will be loaded into the ‘user’ component, we can remove the link to it from the menu.

Example: app.component.html
<ul>
  <li
    routerLinkActive="link-active"
    [routerLinkActiveOptions]="{ exact: true }"
  ><a routerLink="/">Home</a></li>
  <li routerLinkActive="link-active"><a [routerLink]="['user']">User</a></li>
</ul>
<hr>

<router-outlet></router-outlet>

At this point, if we go to the ‘user’ page we won’t see the ‘about’ component being displayed.

That’s because there’s no place for it to display, there’s not outlet.

3. Let’s add a router outlet to the ‘user’ component where we want the ‘about’ component to be displayed.

Example: user.component.html
<p>user works!</p>
<hr>

<router-outlet></router-outlet>

Now if we go to /user/about it will show the contents of the ‘about’ component.

Even though it works at this point, there’s no way for the user to get to it without typing it into the URL bar.

We can navigate to it programmatically with the navigate() method or we can create a routerLink to it.

4. To keep the demonstration simple, we will simply add a routerLink and imagine it’s a sub-menu item that allows the user to access and edit their “About Me” information.

Example: user.component.html
<p>user works!</p>
<hr>
<a routerLink="/user/about">About</a>
<router-outlet></router-outlet>

How to handle 404 errors with Route redirection and wildcards

When a user visits a page that doesn’t exist, the browser will display a 404 Page not found error message. This is not very usefull to the user.

We should instead create a custom page that specifies the error but also allows the user to still navigate or search the app, and redirect them to it.

1. Let’s start by creating a 404 component called ‘page-not-found’.

Example: CLI Command
ng generate component page-not-found

In the View of this new component we can just have a standard message to keep the demonstration simple.

Example: page-not-found.component.html
<p>Sorry! The page you are looking for doesn't exist</p>

2. Next, let’s add it to our routes.

Example: app.module.ts
...
const appRoutes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'user',
    component: UserComponent,
    children: [
      { path: 'about', component: AboutComponent} // www.example.com/user/about
    ]
  },
  { path: 'page-not-found', component: PageNotFoundComponent }
];
...

If we navigate to /page-not-found we will see the error message, so everything works correctly.

3. Now let’s redirect any route that’s not in our appRoutes array to ‘/page-not-found’.

To redirect to a page we use the redirectTo property.

Syntax:
{ path: 'path-to', component: component_name }

{ path: 'path-from', redirectTo: 'path-to' }
Example: app.module.ts
const appRoutes: Routes = [
  { path: '',      component: HomeComponent },
  { path: 'user',
    component: UserComponent,
    children: [
      { path: 'about', component: AboutComponent} // www.example.com/user/about
    ]
  },
  { path: 'page-not-found', component: PageNotFoundComponent },
  { path: 'page-that-doesnt-exist', redirectTo: 'page-not-found' }
];

In the example above we redirect from /page-that-doesnt-exist to the ‘/page-not-found’ component.

Because it’s impractical to specify each and every single path that’s not in our app, we use what’s known as a wildcard for the path.

4. A wildcard will tell Angular that any path not in our route array must be redirected to whichever component we specify, in this case ‘page-not-found’.

A wildcard is simply two asterisk ** operators.

Syntax:
{ path: '**', redirectTo: 'path-to' }
Example: app.module.ts
const appRoutes: Routes = [
  { path: '',      component: HomeComponent },
  { path: 'user',
    component: UserComponent,
    children: [
      { path: 'about', component: AboutComponent} // www.example.com/user/about
    ]
  },
  { path: 'page-not-found', component: PageNotFoundComponent },
  { path: '**', redirectTo: 'page-not-found' }
];

Now we can visit any URL that’s not in our routes array and Angular will redirect it to the 404 component.

It’s important to note that Angular evaluates the route array in hierarchical fashion. The wildcard must be at the very end, or it will always redirect to the 404 component.