DEV Community

Chukwuma Anyadike
Chukwuma Anyadike

Posted on • Updated on

Angular Routing and Navigation Made So Easy A Child Could Do It

As a junior developer one of the things that made it easier for me to navigate the code of large scale applications was understanding the concept of routing and navigation. This post focuses on the fundamentals of routing in Angular. It does not include more advanced concepts like route guards. I feel that once you strip away some of the complexities of code and focus on the fundamentals then the code becomes easier to understand. After that, you fill in the details (i.e. add back the complexity). Here I am demonstrating the fundamental concepts of routing in a story book fashion with lots of pictures and relatively few words, although there will be some code snippets and those contain words.

It is important to know what a route is. A route is a URL which directs or routes to a particular component.

We are building an application called Router Demo. At this point there is an AppComponent and a HeaderComponent. The app.routes.ts file (our routing file) has no routes. Note the empty routes array. The app.component.html file contains our header component and router outlet. The router outlet is our 'view' or outlet for displayed components. It is where the component is loaded. Right now our router outlet is empty. Pay attention to the URLs throughout this post.

//app.component.html
<div>
  <app-header></app-header>
</div>
<router-outlet />
Enter fullscreen mode Exit fullscreen mode
//app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [];
Enter fullscreen mode Exit fullscreen mode

Our current view at http://localhost:4200 shows an empty router outlet.
Our current view

Now we add two components and some routes. I am creating a HomeComponent and DemoComponent. Path 'home' routes to HomeComponent while path 'demo' routes to demo components. Path '' redirects to '/home' with a pathMatch of 'full'. Each element of the routing array typically contains a path and a component.

The path-match of full is important, otherwise, all your routes below it will redirect to the home component. The router selects the route with a first match wins strategy. As a rule of thumb, the more specific routes should come before the less specific routes.

//app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { DemoComponent } from './demo/demo.component';

export const routes: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full' },
    { path: 'home', component: HomeComponent },
    { path: 'demo', component: DemoComponent}
];
Enter fullscreen mode Exit fullscreen mode

At this point it may not be a bad idea to introduce some navigation in our application. There are different ways to navigate.

  1. Use of routerLink. We are using this here.
  2. Navigate programmatically using navigate (or navigateByUrl)

Here I create some buttons in the HeaderComponent and use routerLink.
Note that RouterLink is imported and added to the imports array in the TypeScript file. In the HTML file routerLink is used without data binding syntax (no square brackets).

//header.component.html
<div class="header">
    <h1>Header located above router outlet</h1>
    <button mat-raised-button routerLink="/home">Home</button>
    <button mat-raised-button routerLink="/demo">Demo</button>
</div>

//header.component.ts
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-header',
  standalone: true,
  imports: [MatButtonModule, RouterLink],
  templateUrl: './header.component.html',
  styleUrl: './header.component.css'
})
export class HeaderComponent {

}
Enter fullscreen mode Exit fullscreen mode

Our current view in the home page: The URL 'http://localhost:4200/home' points to HomeComponent which is now seen in the router outlet.

Our current view in the home page

Our current view in the demo page: The URL 'http://localhost:4200/demo' points to DemoComponent which can be seen in the router outlet.

Our current view in the demo page

Now we know something about routing and navigation we should move on to dynamic routing. In dynamic routing you can add a parameter to the route which can hold different values. I am creating a dynamic route for the ResourceComponent that I generate.

Updated Routing File with Dynamic Route where id is the parameter (param).

//app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { DemoComponent } from './demo/demo.component';
import { ResourceComponent } from './resource/resource.component';

export const routes: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full' },
    { path: 'home', component: HomeComponent },
    { path: 'demo', component: DemoComponent},
    { path: 'resource/:id', component: ResourceComponent },
];
Enter fullscreen mode Exit fullscreen mode

The ResourceComponent TypeScript file obtains information from the parameter of the URL using this.route.snapshot.params['id'] and assigns it to id. Note that an instance of ActivateRoute is injected as a dependency through the constructor.

The corresponding HTML file can use the id variable through interpolation.

//resource.component.ts
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-resource',
  standalone: true,
  imports: [],
  templateUrl: './resource.component.html',
  styleUrl: './resource.component.css'
})
export class ResourceComponent {

  id: string | undefined;

  constructor(private route: ActivatedRoute){}

  ngOnInit(){
    this.id = this.route.snapshot.params['id'];
  }

}

//resource.component.html
<main>
    <h2>Welcome to Resource Component</h2>
    <p>Please note that I am located in the router outlet below the header</p>
    <h3>My id is {{id}} which I took from the params portion of the url which matches this component</h3>
    <h3>Please note that params is a string and not a number</h3>
</main>

Enter fullscreen mode Exit fullscreen mode

In the DemoComponent I create some buttons and use routerLink. This time I am using data binding syntax. Note the square brackets.

//demo.component.html
<main>
    <h2>Welcome to Demo Component</h2>
    <p>Please note that I am located in the router outlet below the header</p>
    <div class="button-group-dynamic">
        <button mat-raised-button *ngFor="let id of ids" [routerLink]="['/resource', id]">
            Resource {{id}}
        </button>
    </div>
</main>
Enter fullscreen mode Exit fullscreen mode

Our current view in the resource page: The URL 'http://localhost:4200/resource/2' points to ResourceComponent which can be seen in the router outlet displaying a value of 2.

Our current view in the resource page

We will move on to child (nested) routes. I create two child components (FirstChildComponent and SecondChildComponent) and two child routes of the demo route. Child routes are created the same way as parent routes except that they go into a children array belonging to the parent route.

//app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { DemoComponent } from './demo/demo.component';
import { ResourceComponent } from './resource/resource.component';
import { FirstChildComponent } from './first-child/first-child.component';
import { SecondChildComponent } from './second-child/second-child.component';

export const routes: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full' },
    { path: 'home', component: HomeComponent },
    {
        path: 'demo',
        component: DemoComponent,
        children: [
            { path: 'first-child', component: FirstChildComponent },
            { path: 'second-child', component: SecondChildComponent }
        ]
    },
    { path: 'resource/:id', component: ResourceComponent }
];
Enter fullscreen mode Exit fullscreen mode

In the demo component HTML I make navigation buttons to display each child component and add a router outlet for the child components. In the TypeScript file I import RouterOutlet and add it to the imports array.

//demo.component.html
<main>
    <h2>Welcome to Demo Component</h2>
    <p>Please note that I am located in the router outlet below the header</p>
    <div class="button-group-dynamic">
        <button mat-raised-button *ngFor="let id of ids" [routerLink]="['/resource', id]">
            Resource {{id}}
        </button>
    </div>
    <h3>Child route buttons are located above child router outlet</h3>
    <div>
        <button mat-raised-button routerLink="/demo/first-child">Child 1</button>
        <button mat-raised-button routerLink="/demo/second-child">Child 2</button>
    </div>
    <router-outlet />
</main>

//demo.component.ts
import { Component } from '@angular/core';
import { NgFor } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { RouterLink } from '@angular/router';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [NgFor, MatButtonModule, RouterLink, RouterOutlet],
  templateUrl: './demo.component.html',
  styleUrl: './demo.component.css'
})
export class DemoComponent {
  ids = [1, 2, 3];
}
Enter fullscreen mode Exit fullscreen mode

Our current view in the demo page with child component: The URL 'http://localhost:4200/demo/first-child' points to DemoComponent with FirstChildComponent being displayed in the child router outlet. Think of it as a box within a box.

Our current view in the demo page with child component

Last, I am adding a wild card route to display a not found page. I am creating a PageNotFoundComponent. A wild card route consists of two asterisks like this '**'. It allows you to handle undefined or unknown routes which makes it particularly useful to handle 404 errors. Order of placement is important. Because a wildcard route is the least specific route, place it last in the route configuration.

//app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { DemoComponent } from './demo/demo.component';
import { ResourceComponent } from './resource/resource.component';
import { FirstChildComponent } from './first-child/first-child.component';
import { SecondChildComponent } from './second-child/second-child.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

export const routes: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full' },
    { path: 'home', component: HomeComponent },
    {
        path: 'demo',
        component: DemoComponent,
        children: [
            { path: 'first-child', component: FirstChildComponent },
            { path: 'second-child', component: SecondChildComponent }
        ]
    },
    { path: 'resource/:id', component: ResourceComponent },
    { path: '**', component: PageNotFoundComponent}
];
Enter fullscreen mode Exit fullscreen mode

Our current view in the not found page: The URL 'http://localhost:4200/non-matching-url' points to PageNotFoundComponent displayed in the router outlet.

Image description

We have now created a robust single page application with basic routing and navigation principles. However, this is by no means contains everything you need to know about routing in Angular. For more information go to Angular Docs-Routing. The code for this tutorial is available at Routing Demo. This uses Angular 18. Is this so easy a child could do it? I'll leave that for you to judge. Thank you.

Top comments (0)