Angular routing is a mechanism that enables navigation between different views or pages of an Angular application. In this post, I will explain the basic concepts of angular routing.
Setup basic angular routing
- Define the app.routing.ts file
import { Routes } from "@angular/router";
import { AboutComponent } from "./about/about.component";
import { ContactComponent } from "./contact/contact.component";
import { HomeComponent } from "./home/home.component";
export const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'home', component: HomeComponent },
{ path: 'contact', component: ContactComponent },
{ path: 'about', component: AboutComponent },
];
Here are all your routes. When someone tries to pen www.your-application/contact– the ContactComponent
will load, and if the address is www.your-application/about – AboutComponent
. Home and empty route are reserved for the HomeComponent
.
The Routes
type represents an array of route configuration objects. Each object in the array defines a single route in your application and maps a URL path to a component.
The RouterModule
is a module that provides the necessary services and directives for routing in your application. It contains the Router
service, which is responsible for managing the navigation state of your application, as well as a set of directives such as router-outlet
and routerLink
that are used to bind the Router to your templates.
- Configure the
RouterModule
module:
Register the RouterModule
in the imports array of your module:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
RouterModule,
RouterModule.forRoot(appRoutes)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
The actual setup of your routes is done by calling RouterModule.forRoot(appRoutes)
. It sets up the Router to work and understand the routes you provided.
- Add the
router-outlet
directive to the template where you want to display the content of the currently selected route:
Add this in the app.component.html:
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="navbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" routerLink="/home">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/contact">Contact</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/about">About</a>
</li>
</ul>
</div>
</nav>
<router-outlet></router-outlet>
The <router-outlet>
directive is a placeholder used to display the content of the currently selected route. When a user navigates to a different route, the Router replaces the contents of the <router-outlet>
with the component that is associated with that route.
Use the routerLink
directive in your template to navigate to different routes. For instance – <a class="nav-link" routerLink="/home">Home
Setup dynamic angular routing
Sometimes your routes may accept parameters at runtime. These routes are called dynamic.
- Let’s define a product list component and a model Product:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html'
})
export class ProductListComponent implements OnInit {
public products: Product[] = [];
constructor() { }
ngOnInit() {
this.products = [
new Product("pizza", 3),
new Product("burger", 4),
new Product("sallad", 5)
]
}
}
export class Product {
name: string = '';
id: number = 0;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
}
- In the template generate dynamic routes, for each product:
<a *ngFor="let product of products" [routerLink]="['/products', product.id]">
{{ product.name }}
</a>
The routeLink
directive will build a route in the format product/{id} for each product.
- Define a product component:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product',
templateUrl: './product.component.html'
})
export class ProductComponent implements OnInit {
public productId: number = 0;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.params.subscribe(params => {
this.productId = params['id'];
// Get prouct from server, based on the ID
});
}
}
Use the ActivatedRoute
to parse the dynamic id parameter from the route. With this id, you can request a backend service to fetch the product. ActivatedRoute
can be used to access the current route’s parameters, query parameters, and data. It is typically used in components that need to display information based on the current route.
- Product component template:
<p>Fetching product with id {{productId}}</p>
- Register the product dynamic route, and the product list:
export const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'home', component: HomeComponent },
{ path: 'contact', component: ContactComponent },
{ path: 'about', component: AboutComponent },
{ path: 'products', component: ProductListComponent },
{ path: 'products/:id', component: ProductComponent },
];
Now in the browser you can see the 3 generated links:
And if you click on pizza for example, the id will be passed to the product component route, and parsed by the ActivatedRoute
:
Create child route
The route from the previous example product/:id
may be called child route of the /products
route. Child routes in Angular are routes that are nested within a parent route. In our example the dynamic :id route can be easily nested inside the products one. And this is the better approach instead of declaring them separately.
To define child routes, you need to include an additional array of route configurations within the parent route configuration object. Here’s an example:
export const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'home', component: HomeComponent },
{ path: 'contact', component: ContactComponent },
{ path: 'about', component: AboutComponent },
{
path: 'products',
children: [
{ path: '', component: ProductListComponent },
{ path: ':id', component: ProductComponent }
]
}
];
The products
route is the parent route, and if it is accessed without any parameters it will load the ProductListComponent
. If you add an id, the ProductComponent
will be loaded.
Working with the resolve property
The resolve
property in Angular is a way to pre-fetch data for a route before the component associated with that route is displayed. With the resolve
property, you can define a service or a function that returns data that is required for the component, and the Router will wait for the data to be loaded before navigating to the component.
Here is how to tell your component, it needs a resolver:
{
path: 'products',
children: [
{ path: '', component: ProductListComponent },
{
path: ':id',
component: ProductComponent,
resolve: {
user: UserResolver
}
}
]
}
The UseResolver
will be called just before the component loads. Here is its definition:
@Injectable()
export class UserResolver implements Resolve<User> {
constructor(private userService: UserService) { }
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<User> {
// Get the currentUserId on login and save it in a store
// Here it is hard coded for the simplicity of the example
const currentUserId: number = 5;
return this.userService.getUser(currentUserId);
}
}
Here is the UserService
with the hard coded result:
@Injectable()
export class UserService {
constructor() { }
getUser(userId: number): Observable<User> {
// Get the user from server, based on the ID
return of(new User("Alex", userId, true));
}
}
Also, don’t forget to register the UserResolver
and the UserService
in the providers array of the app.module.ts
, like this providers: [UserService, UserResolver]
.
And now, you can access the resolved data in your component using the ActivatedRoute
service:
ngOnInit() {
this.route.params.subscribe(params => {
this.productId = params['id'];
// Get prouct from server, based on the ID
});
this.user = this.route.snapshot.data['user'];
console.log(this.user);
}
And when you access some product you will get the user logged in the console:
Using canActivate
The canActivate
property in Angular is used to control access to a route. It allows you to specify a guard function that determines whether a user is allowed to activate a particular route, based on certain criteria. If the guard function returns true
, the user is allowed to navigate to the route, and if it returns false
, the navigation is blocked.
Here’s an example of how to use the canActivate
property in a route:
{
path: 'admin-panel',
component: AdminPanelComponent,
canActivate: [AuthGuard]
}
The AuthGuard
will be called just before loading the component. Here you an place the logic for component’s persmissions:
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.authService.isAdmin()) {
return true;
} else {
console.log('The current user doesn\'t have admin rights');
this.router.navigate(['']);
return false;
}
}
}
The AuthService
just returns false in this example:
@Injectable()
export class AuthService {
constructor(private userService: UserService) { }
isAdmin(): boolean {
// Check if the current user is admin and return true or false
return false;
}
}
Register the AuthGuard
and the AuthService
in the providers array of the app.module.ts
.
When you try to access the admin-panel
route, angular will redirect to the empty ''
route and will log the guard message in the console: