In this article about routing in Angular applications, I will cover routing from scratch to in-depth details. I will start from how routing works in Angular and then we will dive deeper with routing. Below are some use cases:
- How does routing work? (Creating routing from scratch)
- How to deal with wildcard characters? ( Not Found Routes)
- How to display the current page as active on the menu bar? (routerLinkActive)
- How to Guard Routes? (canActivate)
- How to store redirectURL and navigate to the same URL after a successful login?
- Child Routing?
- How to display login and logout button conditionally?
- How to display a spinner/progress bar using routing? (resolver)
- How to make routing, case insensitive?
- And many more…
At the end of the article, you will have in-depth knowledge of Routing in Angular applications.
So, as we will be focusing only on routing, we will not go in details with any other topics like how to fetch data through HTTP client and how to create services.
So, I had already created the project and directory structure which is shown below.
I already created product, home, user and header component as shown in the directory structure. Now to begin with, we will see how routing works.
I will divide the routing process into 4 steps.
Create the routes array
I will create a separate routing file and name it as app.routing.ts
- import { Routes, RouterModule } from '@angular/router';
- import { HomeComponent } from './home/home.component';
- import { LoginComponent } from './Users/login/login.component';
- import { ProductListComponent } from './Products/product-list/product-list.component';
- import { ProductAddComponent } from './Products/product-add/product-add.component';
- const arr: Routes = [
- {path: '', redirectTo: '/home', pathMatch: 'full'},
- {path: 'home', component: HomeComponent},
- {path: 'login', component: LoginComponent},
- {path:'products', component: ProductListComponent },
- {path:'addproduct/:id', component: ProductAddComponent }
- ];
-
- export const routing = RouterModule.forRoot(arr);
So when we request http://localhost:4200/products, it will check for the path of “ products” in our routing array, and if it finds that particular path it will display the corresponding component accordingly.
I had created an array of routes and I have declared that array as a root route or parent route. And I am exporting the root route so that we can import it on app.module.ts file
Import routes array in app.module.ts
- import { BrowserModule } from '@angular/platform-browser';
- import { NgModule } from '@angular/core';
- import { FormsModule } from '@angular/forms';
- import { AppComponent } from './app.component';
- import { routing } from './app.routing';
- import { HttpClientModule } from '@angular/common/http';
- import { ProductAddComponent } from './Products/product-add/product-add.component';
- import { LoginComponent } from './Users/login/login.component';
- import { HomeComponent } from './home/home.component';
- import { HeaderComponent } from './header.component';
- import { ProductListComponent } from './Products/product-list/product-list.component';
- @NgModule({
- declarations: [
- AppComponent,
- ProductAddComponent,
- LoginComponent,
- HomeComponent,
- HeaderComponent,
- ProductListComponent
- ],
- imports: [
- BrowserModule,
- FormsModule,
- routing,
- HttpClientModule
- ],
- providers: [],
- bootstrap: [AppComponent]
- })
- export class AppModule { }
Create the seperate header.component and replace href => routerLink,
- <nav class="navbar navbar-expand-lg navbar-light" style="background-color: #e3f2fd;" >
- <a class="navbar-brand" routerLink="">Routing Application</a>
- <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
- <span class="navbar-toggler-icon"></span>
- </button>
-
- <div class="collapse navbar-collapse" id="navbarSupportedContent">
- <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="/products">Products</a>
- </li>
- <li class="nav-item" >
- <a class="nav-link" [routerLink]="['/addproduct',0]">Add Product</a>
- </li>
- </ul>
- </div>
- </nav>
Here, I am using bootstrap 4 for styling purposes only. And to make routing work in an angular application we will be using routerLink directives instead of href.
So we can use it like,
- <a class="nav-link" routerLink="/home">Home </a>
“/home “ will be the same as we had declared in the routing file.
Use <router-outlet> on app.component.html
Now, replace the app.component.html as shown below,
- <app-header></app-header>
- <router-outlet></router-outlet>
First, it will display menu, i.e. header.component.html and then inside router-outlet it will display the component, which will match with the path, declared in routes array. Say for example if we request http://localhost:4200/ then as per routing array it will display the home.component,
How to deal with wildcard characters?
Say for example, a user requests a path/URL which doesn’t match with any path from routing array. We can handle this by simply updating our routes array as shown below,
- import { Routes, RouterModule } from '@angular/router';
- import { HomeComponent } from './home/home.component';
- import { LoginComponent } from './Users/login/login.component';
- import { ProductListComponent } from './Products/product-list/product-list.component';
- import { ProductAddComponent } from './Products/product-add/product-add.component';
- import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
-
- const arr: Routes = [
- {path: '', redirectTo: '/home', pathMatch: 'full'},
- {path: 'home', component: HomeComponent},
- {path: 'login', component: LoginComponent},
- {path:'products', component: ProductListComponent },
- {path:'addproduct/:id', component: ProductAddComponent },
- {path: '**', component:PageNotFoundComponent }
- ];
-
- export const routing = RouterModule.forRoot(arr);
Here, I have added only one path with “**” this stands for if your URL or path doesn’t match to any path defined in routing array then display page-not-found component. ( like error 404) but the most important thing, the order of path:’**’ must be last only, otherwise if you add ‘**’ path first in routing array then no other routes will work.
How to display current menu item selected on the menu bar?
Say, for example, if we are on the home page, then the currently selected menu item should be Home, and if we navigate to the Products page, then the currently selected menu item should be Products.
We can achieve it by simply writing CSS class for displaying the currently-selected menu item highlighted and then applying that CSS class to your header component, I am using Bootstrap 4, so it comes with pre-built CSS classes, in my case “Active” class is already created, so I am just applying it to my header component as shown below,
- <nav class="navbar navbar-expand-lg navbar-light" style="background-color: #e3f2fd;" >
- <a class="navbar-brand" routerLink="">Routing Application</a>
- <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
- <span class="navbar-toggler-icon"></span>
- </button>
-
- <div class="collapse navbar-collapse" id="navbarSupportedContent">
- <ul class="navbar-nav mr-auto">
- <!-- [routerLinkActiveOptions]="{ exact : true}" -->
- <li class="nav-item" routerLinkActive="active" >
- <a class="nav-link" routerLink="/home">Home </a>
- </li>
- <li class="nav-item" routerLinkActive="active" >
- <a class="nav-link" routerLink="/products">Products</a>
- </li>
- <li class="nav-item" routerLinkActive="active">
- <a class="nav-link" [routerLink]="['/addproduct',0]">Add Product</a>
- </li>
-
- </ul>
-
- </div>
- </nav>
We can also use [routerLinkActiveOptions]="{ exact : true}",
To match the exact path.
How to store redirectURL and navigate to the same URL after a successful login?
Now, to achive the above said use case, I already have created user-auth service and login component as shown below,
user-auth.services.ts
- import { User } from './user';
- import { Injectable } from '@angular/core';
- import { Router } from '@angular/router';
-
- @Injectable({
- providedIn: 'root'
- })
- export class UserAuthService {
- currentUser:User;
- redirectURL:string;
-
- constructor(private _route:Router) { }
- get isLoggedIn():boolean{
- return !!this.currentUser;
- }
- logIn(username:string,password:string):void {
- if(username==='admin'){
- this.currentUser={
- id:'[email protected]',
- uname:'admin',
- isAdmin:true
- }
- return;
- }
- this.currentUser={
- id:username+'@gmail.com',
- uname:username,
- isAdmin:false
- }
- }
- logout(){
- this.currentUser=null;
- this._route.navigate(['/home']);
- }
- }
To provide you with an overview of user-auth.service.ts, I had created a login and logout methods, which will validate the user, here I am using some static values, you can replace it with your actual values. I had created an isLoggedIn property to return a Boolean value, it will return true if the user is logged in and false if not logged in. Also, I have created two properties -- current user to store current user credentials and redirectURL to store the path from which user is redirected to the login page so that after being successfully logged in we can navigate user back to actual URL.
login.component.html
- <div class="card">
- <div class="card-header">
- {{pageTitle}}
- </div>
-
- <div class="card-body">
- <form novalidate
- (ngSubmit)="login(loginForm)"
- #loginForm="ngForm"
- autocomplete="off">
- <fieldset>
-
- <div class="form-group row">
- <label class="col-md-2 col-form-label"
- for="userNameId">User Name</label>
- <div class="col-md-8">
- <input class="form-control"
- id="userNameId"
- type="text"
- placeholder="User Name (required)"
- required
- ngModel
- name="userName"
- #userNameVar="ngModel"
- [ngClass]="{'is-invalid': (userNameVar.touched || userNameVar.dirty) && !userNameVar.valid }" />
- <span class="invalid-feedback">
- <span *ngIf="userNameVar.errors?.required">
- User name is required.
- </span>
- </span>
- </div>
- </div>
-
- <div class="form-group row">
- <label class="col-md-2 col-form--label"
- for="passwordId">Password</label>
-
- <div class="col-md-8">
- <input class="form-control"
- id="passwordId"
- type="password"
- placeholder="Password (required)"
- required
- ngModel
- name="password"
- #passwordVar="ngModel"
- [ngClass]="{'is-invalid': (passwordVar.touched || passwordVar.dirty) && !passwordVar.valid }" />
- <span class="invalid-feedback">
- <span *ngIf="passwordVar.errors?.required">
- Password is required.
- </span>
- </span>
- </div>
- </div>
-
- <div class="row">
- <div class="col-md-4 offset-md-2">
- <button class="btn btn-primary mr-3"
- type="submit"
- style="width:80px"
- [disabled]="!loginForm.valid">
- Log In
- </button>
- <button class="btn btn-outline-secondary"
- type="button"
- style="width:80px"
- [routerLink]="['/home']">
- Cancel
- </button>
- </div>
- </div>
- </fieldset>
- </form>
- </div>
-
- <div class="alert alert-danger"
- *ngIf="errorMessage">{{errorMessage}}
- </div>
- </div>
login.component.ts
- import { Component, OnInit } from '@angular/core';
- import { UserAuthService } from '../user-auth.service';
- import { Router } from '@angular/router';
- import { NgForm } from '@angular/forms';
-
- @Component({
- selector: 'app-login',
- templateUrl: './login.component.html',
- styleUrls: ['./login.component.css']
- })
- export class LoginComponent implements OnInit {
-
- errorMessage: string;
- pageTitle = 'Log In';
-
- constructor(private authService: UserAuthService,
- private router: Router) { }
-
- login(loginForm: NgForm) {
- if (loginForm && loginForm.valid) {
- const userName = loginForm.form.value.userName;
- const password = loginForm.form.value.password;
- if(!userName || !password){
- alert("Please enter Username and Password");
- }
- else{
- this.authService.logIn(userName, password);
- }
-
- if (this.authService.redirectURL) {
- this.router.navigateByUrl(this.authService.redirectURL);
- } else {
- this.router.navigate(['/products']);
- }
- } else {
- this.errorMessage = 'Please enter a user name and password.';
- }
- }
- ngOnInit(){}
-
- }
So, now here if the user is successfully validated, then we will also check for the redirectURL property of user-auth services, if it is empty then by default we will redirect the user to the product list page and if there is a value in the redirectURL property, we will redirect the user to the actual URL.
How to display login and logout button conditionally?
In order to display login and logout button conditionally; i.e. if the user is logged in then display Logout button and if the user is not logged in then display Login button. To do so we need to update the header component as shown below,
Header.component.html
- <nav class="navbar navbar-expand-lg navbar-light" style="background-color: #e3f2fd;" >
- <a class="navbar-brand" routerLink="">Routing Application</a>
- <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
- <span class="navbar-toggler-icon"></span>
- </button>
-
- <div class="collapse navbar-collapse" id="navbarSupportedContent">
- <ul class="navbar-nav mr-auto">
- <!-- [routerLinkActiveOptions]="{ exact : true}" -->
- <li class="nav-item" routerLinkActive="active" >
- <a class="nav-link" routerLink="/home">Home </a>
- </li>
- <li class="nav-item" routerLinkActive="active" >
- <a class="nav-link" routerLink="/products">Products</a>
- </li>
- <li class="nav-item" routerLinkActive="active">
- <a class="nav-link" [routerLink]="['/addproduct',0]">Add Product</a>
- </li>
-
- </ul>
-
- <button routerLink="/login" *ngIf="!isLoggedIn" class="btn btn-outline-success my-2 my-sm-0">Login</button>
- <button *ngIf="isLoggedIn" (click)="logOut()" class="btn btn-outline-success my-2 my-sm-0">Logout</button>
- </div>
- </nav>
Header.component.ts
- import { Component, OnInit } from '@angular/core';
- import { UserAuthService } from './Users/user-auth.service';
-
- @Component({
- selector: 'app-header',
- templateUrl: './header.component.html',
- styleUrls: ['./header.component.css']
- })
- export class HeaderComponent implements OnInit {
-
- constructor(private _userauth:UserAuthService) { }
- get isLoggedIn():boolean{
- return this._userauth.isLoggedIn;
- }
- logOut(){
- this._userauth.logout();
- }
- ngOnInit() {
- }
- }
Here, I have created isLoggedIn which will return true or false based on user’s logged in status, and I am using isLoggedIn property on my template to display either Login button or Logout button based on the value returned by isLoggedIn.
How to Guard Routes? (canActivate, canLoad)
It is always required that not every user has access to all the routes; say for example we don’t want to allow any user to have access to products pages like products list page and add new products page without logging In. We can do it simply by creating a guard for that.
Now to achieve the above-said use case, first I have created user-guard service as shown below,
- import { Injectable } from '@angular/core';
- import { CanActivate,CanLoad, Router,
- RouterStateSnapshot, ActivatedRouteSnapshot, Route } from '@angular/router';
- import { UserAuthService } from './user-auth.service';
- @Injectable({
- providedIn: 'root'
- })
- export class UserGuardService implements CanActivate,CanLoad {
-
- constructor(private _user_auth:UserAuthService,private _router:Router) { }
- canActivate(_next:ActivatedRouteSnapshot,_state:RouterStateSnapshot):boolean{
- return this.checkLoggedIn(_state.url);
- }
- canLoad(_route:Route):boolean{
- return this.checkLoggedIn(_route.path);
- }
- checkLoggedIn(url:string):boolean{
- if(this._user_auth.isLoggedIn){
- return true;
- }
- this._user_auth.redirectURL=url;
- this._router.navigate(['/login']);
- return false;
- }
- }
Here in the above service, I have implemented CanActivate, CanLoad interface from angular/router package which will be used to protect our routes. Here we are checking the user’s logged-in status and based on that we are allowing the user to navigate to a particular page.
Also, update app.routing.ts file as shown below,
- import { Routes, RouterModule } from '@angular/router';
- import { HomeComponent } from './home/home.component';
- import { LoginComponent } from './Users/login/login.component';
- import { ProductListComponent } from './Products/product-list/product-list.component';
- import { ProductAddComponent } from './Products/product-add/product-add.component';
- import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
- import { UserGuardService } from './Users/user-guard.service';
-
- const arr: Routes = [
- {path: '', redirectTo: '/home', pathMatch: 'full'},
- {path: 'home', component: HomeComponent},
- {path: 'login', component: LoginComponent},
- {path:'products', canActivate:[UserGuardService], component: ProductListComponent },
- {path:'addproduct/:id', canActivate:[UserGuardService],component: ProductAddComponent },
- {path: '**', component:PageNotFoundComponent }
- ];
-
- export const routing = RouterModule.forRoot(arr);
So as I said before the products list and add product pages should be displayed only after the user is loggedIn,that can be taken care of by canActivate directive, so now when user requests for a particular page it will first check the credentials in auth-guard services inside canActiviate interface and then user will be allowed to visit a particular page.
Child Routes?
We can also manage routes using children property on routing array, say for example if we want to organize all routes related to the products under one parent object, we can define children inside the parent object as shown below,
- import { Routes, RouterModule } from '@angular/router';
- import { HomeComponent } from './home/home.component';
- import { LoginComponent } from './Users/login/login.component';
- import { ProductListComponent } from './Products/product-list/product-list.component';
- import { ProductAddComponent } from './Products/product-add/product-add.component';
- import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
- import { UserGuardService } from './Users/user-guard.service';
-
- const arr: Routes = [
- {path: '', redirectTo: '/home', pathMatch: 'full'},
- {path: 'home', component: HomeComponent},
- {path: 'login', component: LoginComponent},
- {path:'products',canActivate:[UserGuardService],
- children:[
- {path:'',component:ProductListComponent},
- {path:':id',component:ProductAddComponent},
- {path:':id/edit',component:ProductAddComponent}
- ]
- },
- {path: '**', component:PageNotFoundComponent }
- ];
-
- export const routing = RouterModule.forRoot(arr);
So, now if we request for http://localhost:4200/products/, it will first go to products object and then inside products object, it will go to its children which is an array of child component and then check for matching the path to load the component. In our particular case, it will load the product list component.
Also, we need to update the header.component.html because we had changed routing array.
- <nav class="navbar navbar-expand-lg navbar-light" style="background-color: #e3f2fd;" >
- <a class="navbar-brand" routerLink="">Routing Application</a>
- <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
- <span class="navbar-toggler-icon"></span>
- </button>
-
- <div class="collapse navbar-collapse" id="navbarSupportedContent">
- <ul class="navbar-nav mr-auto">
- <!-- [routerLinkActiveOptions]="{ exact : true}" -->
- <li class="nav-item" [routerLinkActiveOptions]="{ exact : true}" routerLinkActive="active" >
- <a class="nav-link" routerLink="/home">Home </a>
- </li>
- <li class="nav-item" [routerLinkActiveOptions]="{ exact : true}" routerLinkActive="active" >
- <a class="nav-link" routerLink="/products">Products</a>
- </li>
- <li class="nav-item" [routerLinkActiveOptions]="{ exact : true}" routerLinkActive="active">
- <a class="nav-link" [routerLink]="['/products',0]">Add Product</a>
- </li>
-
- </ul>
-
- <button routerLink="/login" *ngIf="!isLoggedIn"
- class="btn btn-outline-success my-2 my-sm-0">Login</button>
- <button *ngIf="isLoggedIn" (click)="logOut()"
- class="btn btn-outline-success my-2 my-sm-0">Logout</button>
- </div>
- </nav>
Now although I had requested for add product page, it will first redirect me to log in because of the user-guard service.
Now after a successful login by default, it should redirect me to the product list page, but in this case, it will redirect me to add product page because of the redirecURL property of user-auth service.
How to display spinner using routing event? (Resolver)
Well, we can display spinner in the angular application in various ways, but I am talking about displaying spinner using routing event. It will solve your two problems -- one it will prevent loading the page partially and other it will also display a spinner.
How many time have you faced a problem, when you navigate to a page and you are making an HTTP call to fetch the data, and your template/ HTML is loaded and still you are waiting to load your HTTP data; i.e. because of partial page loading, Angular manages to route locally so your HTML/template will be loaded, but still, you are waiting for HTTP calls to complete to display the data. As a solution to this, we can use one of the routing events, which will only load the page/navigate to page after your HTTP call completes, this way you can prevent to partial page loading and also able to display spinner.
Okay so first create product-resolver.service.ts, as shown below,
- import { Injectable } from '@angular/core';
- import { Resolve,ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router';
- import { Product, productResolved } from './product';
- import { Observable, of } from 'rxjs';
- import { ProductService } from './product.service';
- import { map, catchError } from 'rxjs/operators';
- @Injectable({
- providedIn: 'root'
- })
- export class ProductResolverService implements Resolve<productResolved> {
- constructor(private productdata:ProductService) { } resolve(next:ActivatedRouteSnapshot,state:RouterStateSnapshot):Observable<productResolved>
- {
- return this.productdata.getAllProducts().pipe(
- map(product => ({ product: product,errorMessage:'' })),
- catchError(err=>{
- console.log(err);
- return of({ product: null,errorMessage:err.message });
- })
- );
- }
- }
Here, I had implemented the Resolve method of the interface, which will return the productResolved Observable before navigating or loading the page. Below are the product and product resolved classes,
- export class Product{
- constructor(public pimg:string,public pname:string,
- public pprice:number,public soh:number){}
- }
- export class productResolved{
- constructor(public product:any[],public errorMessage:string){}
- }
Here, Product class will describe what type of data we are receiving from HTTP call, and productResolved class is for holding the product data as well as errors if any.
Now in order to make Resolver work we need to do some adjustment in app.routing.ts as shown below,
- import { Routes, RouterModule } from '@angular/router';
- import { HomeComponent } from './home/home.component';
- import { LoginComponent } from './Users/login/login.component';
- import { ProductListComponent } from './Products/product-list/product-list.component';
- import { ProductAddComponent } from './Products/product-add/product-add.component';
- import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
- import { UserGuardService } from './Users/user-guard.service';
- import { ProductResolverService } from './Products/product-resolver.service';
-
- const arr: Routes = [
- {path: '', redirectTo: '/home', pathMatch: 'full'},
- {path: 'home', component: HomeComponent},
- {path: 'login', component: LoginComponent},
- {path:'products',resolve:{productData:ProductResolverService},
- canActivate:[UserGuardService],
- children:[
- {path:'',component:ProductListComponent},
- {path:':id',component:ProductAddComponent},
- {path:':id/edit',component:ProductAddComponent}
- ]
- },
- {path: '**', component:PageNotFoundComponent }
- ];
-
- export const routing = RouterModule.forRoot(arr);
Now to display the spinner, we need to subscribe to the routing events on root component; i.e. app.component.html, by subscribing to root component. We don’t need to put the spinner on each and every page, we just need to put the spinner on root component only, below is how app.component.ts will look:
- import { Component } from "@angular/core";
- import { Router,Event, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from "@angular/router";
-
- @Component({
- selector: "app-root",
- templateUrl: "./app.component.html",
- styleUrls: ["./app.component.css"]
- })
- export class AppComponent {
- title = "angularRoutingApplication";
- loading = true;
-
- constructor(private _router:Router){
- _router.events.subscribe((routerEvent: Event) => {
- this.checkRouterEvent(routerEvent);
- });
- }
-
- checkRouterEvent(routerEvent: Event): void {
- if (routerEvent instanceof NavigationStart) {
- this.loading = true;
- console.log('router event start');
- }
-
- if (routerEvent instanceof NavigationEnd ||
- routerEvent instanceof NavigationCancel ||
- routerEvent instanceof NavigationError) {
- console.log('router event finish');
- this.loading = false;
- }
- }
- }
Here I am subscribing to routing events first and then checking for navigationStart and NavigationEnd, based on that I am updating the loading property to true or false, which will be used on HTML/template to display the spinner. Below is a snippet of app.component.html,
- <div *ngIf="loading" class="spinner-border text-primary" role="status">
- <span class="sr-only">Loading...</span>
- </div>
- <div *ngIf="loading" class="spinner-border text-secondary" role="status">
- <span class="sr-only">Loading...</span>
- </div>
- <div *ngIf="loading" class="spinner-border text-success" role="status">
- <span class="sr-only">Loading...</span>
- </div>
- <div *ngIf="loading" class="spinner-border text-danger" role="status">
- <span class="sr-only">Loading...</span>
- </div>
- <div *ngIf="loading" class="spinner-border text-warning" role="status">
- <span class="sr-only">Loading...</span>
- </div>
- <div *ngIf="loading" class="spinner-border text-info" role="status">
- <span class="sr-only">Loading...</span>
- </div>
- <div *ngIf="loading" class="spinner-border text-light" role="status">
- <span class="sr-only">Loading...</span>
- </div>
- <div *ngIf="loading" class="spinner-border text-dark" role="status">
- <span class="sr-only">Loading...</span>
- </div>
- <app-header></app-header>
- <router-outlet></router-outlet>
So, now when I request, http://localhost:4200/products first it will navigate us to the login page. Remember we had used user-guard service, then after login, it will first call resolver service to fetch the data, and then only it will navigate to the product list page. Below is the code snippet of product-list.component.ts,
- import { Component, OnInit } from '@angular/core';
- import { Observable } from 'rxjs';
- import { Product, productResolved } from '../product';
- import { ActivatedRoute } from '@angular/router';
- @Component({
- selector: 'app-product-list',
- templateUrl: './product-list.component.html',
- styleUrls: ['./product-list.component.css']
- })
- export class ProductListComponent implements OnInit {
- items:Product[]=[];
- errorMessage:string='';
- data:productResolved;
- constructor(private _router:ActivatedRoute) {
- this.data=this._router.snapshot.data["productData"];
- }
- ngOnInit() {
- this.items=this.data.product;
- this.errorMessage=this.data.errorMessage;
- }
- }
So, now on product list.component.ts, instead of making an HTTP call, we will just use the data which has already been fetched by resolver services:
- this.data=this._router.snapshot.data["productData"];
Here “prodcutData” is the same name which we had used while declaring the app.routing.ts,
After routes gets resolved it will display the product list page.
How to make routing case insensitive?
The routes in the Angular application are case sensitive, i.e. if you have defined
{path: 'home', component: HomeComponent},
And if you request http://localhost:4200/home, it will work fine, but it will not work with following routes,
- http://localhost:4200/Home
- http://localhost:4200/HOME
- http://localhost:4200/hoME, etc
so to make all above URL work in Angular applications, I found a common workaround, UrlSerializer.
We can create lowecaseurlserializer which is derived from defaulturlserializer as shown below,
- import { DefaultUrlSerializer, UrlTree } from '@angular/router';
- export class LowerCaseUrlSerializer extends DefaultUrlSerializer {
- parse(url: string): UrlTree {
- return super.parse(url.toLowerCase());
- }
- }
Which will simply convert all URLs to lowercase so that it will match our routing array. Now in order to make this lowecaseurlserializer to work we need to make an adjustment to app.module.ts as shown below,
- import { BrowserModule } from '@angular/platform-browser';
- import { NgModule } from '@angular/core';
- import { FormsModule } from '@angular/forms';
- import { AppComponent } from './app.component';
- import { routing } from './app.routing';
- import { environment } from '../environments/environment';
- import { HttpClientModule } from '@angular/common/http';
- import { ProductAddComponent } from './Products/product-add/product-add.component';
- import { LoginComponent } from './Users/login/login.component';
- import { HomeComponent } from './home/home.component';
- import { HeaderComponent } from './header.component';
- import { ProductListComponent } from './Products/product-list/product-list.component';
- import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
- import { UrlSerializer } from '@angular/router';
- import { LowerCaseUrlSerializer } from './lowerCaseUrlSerializer';
- @NgModule({
- declarations: [
- AppComponent,
- ProductAddComponent,
- LoginComponent,
- HomeComponent,
- HeaderComponent,
- ProductListComponent,
- PageNotFoundComponent
- ],
- imports: [
- BrowserModule,
- FormsModule,
- routing,
- HttpClientModule
- ],
- providers: [
- {
- provide:UrlSerializer,
- useClass:LowerCaseUrlSerializer
- }
- ],
- bootstrap: [AppComponent]
- })
- export class AppModule { }
Download
Script for products table:
- CREATE TABLE IF NOT EXISTS `products` (
- `p_id` int(11) NOT NULL AUTO_INCREMENT,
- `pname` varchar(50) NOT NULL,
- `pprice` int(11) NOT NULL,
- `pimg` varchar(200) NOT NULL,
- `soh` int(11) NOT NULL,
- PRIMARY KEY (`p_id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;