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:
Receive the Currently Logged In User ID from Ionic Storage.
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);
});
}
}
}
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)));
}