angularangular-httpclientionic5angular-resolverionic-storage

Angular 12 + Ionic 5: Resolver does not wait for the Storage and HTTP calls to finish


SITUATION:

This question is regarding a SPA that I am building using Angular 12 and Ionic 5. When I am on the Home page, I can click the "Order History" link in the Side Menu and this routes me to the Order History page. I am using a Resolver in order to procure the Order History from the Database before the routing is complete, so that when the routing finishes, the User can see the data, as it is available readily via the Resolver. In this resolver, there are 2 main operations performed (strictly in order). They are:

  1. Receive the Currently Logged In User ID from Ionic Storage.

  2. Use the received Currently Logged In User ID from the above step and make a HTTP call to the backend to fetch the Orders related to the User. Only after the HTTP call finishes successfully, navigate to the "Order History" page and log the HTTP call data to console.

PROBLEM:

When I click on the "Order History" link in the Side Menu, the Resolver runs, fetches the Currently Logged in User ID from Storage, but it does not wait for the HTTP call to finish. Rather, it simply routes to the Order History page and then performs the HTTP request and then gives me the results from the HTTP request. But this beats the very purpose of the Resolver! The Resolver is supposed to wait for all the calls to finish and then navigate to the destination page, but instead, it navigates to the destination page and then finishes the API call and gives the data. I am trying to fix this so that the Resolver performs the 2 main operations as indicated above, before the actual routing occurs.

HERE IS MY CODE:

app-routing.module.ts:

import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
import { GetOrderHistoryResolver } from "@shared/resolvers/get-order-history/get-order-history.resolver";

const routes: Routes = [
  {
    path: 'order-history',
    resolve: {
      resolvedData: GetOrderHistoryResolver,
    },
    loadChildren: () => import('./order-history/order-history.module').then( m => m.OrderHistoryPageModule)
  },  
];

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

get-order-history.resolver.ts

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { OrdersService } from "../../services/orders/orders.service";
import { AuthenticationService } from "@core/authentication/authentication.service";
import { Storage } from '@ionic/storage';

@Injectable({
  providedIn: 'root'
})
export class GetOrderHistoryResolver implements Resolve<any> {

  constructor(private router: Router,
              private storage: Storage,
              private authenticationService: AuthenticationService,
              private ordersService: OrdersService) {
  }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

    return this.authenticationService.getUserId().then(currentUserId => {
      console.log(currentUserId); // This works correctly and logs the value as 5
      return this.ordersService.getOrdersByCustomer(currentUserId);
    });

  }
}

authentication.service.ts

getUserId() {
  return this.storage.get('user').then(user => {
    if (user) {
      // Make sure to parse the value from string to JSON object
      let userObj = JSON.parse(user);    
      return userObj.ID;
    }
  });
}

orders.service.ts

getOrdersByCustomer(userId): any {
  return this.http.get<any>(BASE_URL + '/orders?customer=' + userId )
}

order-history.page.ts

import { Component, OnInit } from '@angular/core';
import { OrdersService } from "@shared/services/orders/orders.service";
import { ActivatedRoute } from "@angular/router";
import { Storage } from '@ionic/storage';
import { AuthenticationService } from "@core/authentication/authentication.service";

@Component({
  selector: 'app-order-history',
  templateUrl: './order-history.page.html',
  styleUrls: ['./order-history.page.scss'],
})
export class OrderHistoryPage implements OnInit {

  constructor(private route: ActivatedRoute,
              private storage: Storage,
              private ordersService: OrdersService,
              private authenticationService: AuthenticationService) {
  }

  ngOnInit() {}

  ionViewWillEnter() {
    // If the Resolver is executed, then grab the data received from it
    if (this.route.snapshot.data.resolvedData) {
      this.route.snapshot.data.resolvedData.subscribe((response: any) => {
        console.log('PRODUCTS FETCHED FROM RESOLVE');
        console.log(response); // <-- Products are successfully logged here to console
      });
    } else {
      // Make a call to the API directly because the Resolve did not work
      this.getOrdersByCustomer();
    }
  }


  /**
   * Manual call to the API directly because the Resolve did not work
   * @returns {Promise<void>}
   */
  async getOrdersByCustomer() {
    // Wait to get the UserID from storage
    let currentCustomerId = await this.authenticationService.getUserId() ;

    // Once the UserID is retrieved from storage, get all the orders placed by this user
    if(currentCustomerId > 0) {
      this.ordersService.getOrdersByCustomer(currentCustomerId).subscribe((res: any) => {
        console.log(res);
      });
    }
  }

}

Solution

  • You can convert your promise to an observable with defer from rxjs and then chain your observables in a pipe.

    I am not sure if you can use from instead of defer but defer should work for sure

    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        return  defer(() => this.authenticationService.getUserId())
                                .pipe(switchMap((currentUserId) => 
                                         this.ordersService.getOrdersByCustomer(currentUserId)));
      }