Tuesday, September 8, 2020

Angular Route Resolver - Passing Data Dynamically

In the posts route parameters and Angular query parameters we have seen examples of passing data in the route and using it in the component as it is or to retrieve more data. This process happens once you have navigated to a route but you can also get data before you have navigated to a route. That is done using Angular Resolve interface that classes can implement to be a data provider. A data provider class can be used with the router to act as a resolver and it can resolve data during navigation. Using Resolve interface in Angular gives you one way to get data dynamically before navigating to a route.

Angular Resolve interface

Resolve interface has a single method resolve() that is invoked when the navigation starts. The router waits for the data to be resolved before the route is finally activated.

interface Resolve<T> {
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T
}

You can say that Resolve also works like a CanActivate guard or CanActivateChild guard, acting before the route navigation is completed but in case of Resolve it will always navigate to a route but before that it will get some data dynamically.

Angular resolver example

In the example we’ll have an accounts route that shows a list of account numbers and a nested route account that shows the details of the account selected in the parent route. Fetching of data based on the passed account number is done in AccountResolver class which implements Resolve interface.

account.model.ts

This is the model class with the fields.

export class Account {
  accountNumber: string; 
  type: string;
  balance: number;
  constructor(accountNumber: string, type: string, balance: number) {
    this.accountNumber = accountNumber;
    this.type = type;
    this.balance = balance;
  }
}

AccountService (account.service.ts)

This is a service class with methods to retrieve all accounts or account by account number. Also creates an array of Account objects that is displayed through AccountsComponent.

import { Injectable } from '@angular/core';
import { Account } from '../accounts/account.model';

@Injectable({
  providedIn: 'root',
})
export class AccountService {
  accountDetails = [
    new Account('A1001','Saving', 22000),
    new Account('A1002','Checking', 1000)
  ];
   // get the Accounts list
  getAllAccounts(){
    return this.accountDetails;
  }
  // Account by number
  getAccountById(acctNo: string){
    return this.accountDetails.find(account => account.accountNumber === acctNo)
  }
}

AccountResolver Class (account-resolver.service.ts)

This is the class that implements Resolve interface. In the implemented resolve() method it calls the Service’s method to get single account details. This account data is passed as part of route when the Component is rendered. In the component you can retrieve the data from the data property of the ActivatedRoute.

import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Account } from '../account.model';
import { Observable } from 'rxjs';
import { AccountService } from 'src/app/services/account.service';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class AccountResolver implements Resolve<Account>{
  constructor(private acctService: AccountService) {}
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Account> | Promise<Account> | Account  {
    return this.acctService.getAccountById(route.params['acctno']);
  }
}

AccountsComponent (accounts.component.ts)

This component fetches all the accounts by calling the AccountService method and renders it using template.

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { AccountService } from '../services/account.service';

@Component({
  selector: 'app-accounts',
  templateUrl: './accounts.component.html'
})
export class AccountsComponent implements OnInit{
  accounts = [];
  constructor(private router: Router, 
          private route: ActivatedRoute, 
          private acctService: AccountService) {}

  ngOnInit() {
    this.accounts = this.acctService.getAllAccounts();
  }
}

accounts.component.html

In the template, account numbers are displayed using the ngFor directive. For each account a router link is created that corresponds to child route.

<div class= "row">
  <div class="col-xs-4 col-md-4">
    <h2>Account Numbers</h2>
    <div class="list-group">
      <a [routerLink]="['/account', account.accountNumber]"    
       href="#"      
       class="list-group-item"   
       *ngFor="let account of accounts">
          {{ account.accountNumber }}
      </a>
    </div>
  </div>
  <div class="col-xs-4 col-md-4">
    <router-outlet></router-outlet>
  </div>
</div>

AccountComponent (account.component.ts)

This is the component that gets account details for the selected account number.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router, RouterEvent, Data } from '@angular/router';
import { AccountService } from 'src/app/services/account.service';
import { Account } from '../account.model';

@Component({
  selector: 'app-account',
  templateUrl: './account.component.html'
})
export class AccountComponent implements OnInit{
  account: Account;
  constructor(private route: ActivatedRoute, 
            private router: Router, 
            private acctService: AccountService){ }

  ngOnInit() {
    this.route.data.subscribe((data: Data) => {
      this.account = data['account'];
    });
  }
}

In the ngOnInit() method you can see that using the data property of the ActivatedRoute, account data is retrieved which was already fetched by the AccountReolver class.

account.component.html

Template for showing account details.

<h2>Account Details</h2>
<div class="row">
  <div class="col-xs-6">
    <label>Account Number: </label> {{ account.accountNumber }}
  </div>
</div>
<div class="row">
  <div class="col-xs-6">
    <label>Account Type: </label> {{ account.type }}
  </div>
</div>
<div class="row">
  <div class="col-xs-6">
    <label>Balance: </label> {{account.balance}}
  </div>
</div>

HomeComponent (home.component.ts)

This component doesn’t do much just shows a welcome message.

import { OnInit, Component } from '@angular/core';

@Component({
    selector: 'app-home',
    templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
  message: string;
  constructor() {
    this.message = 'Welcome to Resolver example for getting data dynamically';
  }
  ngOnInit() {
  }
}

home.component.html

<h4>{{ message }}</h4>

AppRoutingModule (app-routing.module.ts)

Now comes the configuration part where we’ll configure the routes and also the resolver with the child route.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
import { UsersComponent } from './users/users.component';
import { AddUserComponent } from './users/adduser/adduser.component';
import { EditUserComponent } from './users/edituser/edituser.component';
import { CanDeactivateGuard } from './services/can-deactivate.guard';
import { UserEditCanDeactivateGuard } from './services/useredit-can-deactivate.guard';
import { PageNotFoundComponent } from './pagenotfound.component';
import { AccountComponent } from './accounts/account/account.component';
import { AccountsComponent } from './accounts/accounts.component';
import { AccountResolver } from './accounts/account/account-resolver.service';

const routes: Routes = [
                  {path: 'home', component: HomeComponent},                       
                  {path: 'account', component: AccountsComponent, children: [
                    {path: ':acctno', component: AccountComponent, resolve: {account: AccountResolver}}
                  ]},                 
                  {path: '', redirectTo:'/home', pathMatch: 'full'}               
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

This is the route definition where a resolver is configured using the resolve property.

{path: ':acctno', component: AccountComponent, resolve: {account: AccountResolver}}

With resolve property you can pass an object having key, value pairs. Note that the key you use here will be used to get the data from the data property as done in AccountComponent for this example.

ngOnInit() {
  this.route.data.subscribe((data: Data) => {
    this.account = data['account'];
  });
}

app.component.html

Code for creating a menu and Route links is in this template.

<nav class="navbar navbar-expand-md bg-dark navbar-dark">
  <div class="container-fluid">
    <div class="collapse navbar-collapse" id="collapsibleNavbar">
      <ul class="nav navbar-nav">
        <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="/account">Accounts</a>
        </li>
      </ul>
    </div>
  </div>
</nav>
<div class="container">
  <div class="row"><p></p></div>
  <div class="row">
    <div class="col-sm-12, col-md-12">
      <router-outlet></router-outlet>
    </div>
  </div>
</div>

AppModule (app.module.ts)

In the AppModule you need to provide the components in the declarations array. In the providers array you need to add AccountService and AccountResolver service.

providers: [AccountService, AccountResolver]

Once the configuration is done and code is successfully compiled access localhost:4200 and click on Accounts. On selecting any of the account number data for that account is dynamically fetched using Resolver.

Angular route resolver

That's all for this topic Angular Route Resolver - Passing Data Dynamically. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. Angular Route - Passing Static Data
  2. CanDeactivate Guard in Angular With Example
  3. Setting Wild Card Route in Angular
  4. Nested Route (Child Route) in Angular
  5. Path Redirection in Angular Routing

You may also like-

  1. Angular ngStyle Directive With Examples
  2. Injector Hierarchy and Service Instances in Angular
  3. Angular Custom Event Binding Using @Output Decorator
  4. Angular Example to Render Multiple Rows
  5. Difference Between Comparable and Comparator in Java
  6. Read or List All Files in a Folder in Java
  7. Tuple in Python With Examples
  8. Compressing File in bzip2 Format in Hadoop - Java Program

No comments:

Post a Comment