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 />
//app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [];
Our current view at http://localhost:4200 shows an empty router outlet.
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}
];
At this point it may not be a bad idea to introduce some navigation in our application. There are different ways to navigate.
- Use of
routerLink
. We are using this here. - Navigate programmatically using
navigate
(ornavigateByUrl
)
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 {
}
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 demo page: The URL 'http://localhost:4200/demo' points to DemoComponent
which can be seen in the router outlet.
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 },
];
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>
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>
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.
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 }
];
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];
}
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.
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}
];
Our current view in the not found page: The URL 'http://localhost:4200/non-matching-url' points to PageNotFoundComponent
displayed in the router outlet.
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)