简体   繁体   中英

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);
      });
    }
  }

}

Resolve internally add handlers to returned promise/observables. If the data is fetched, it will route to the given page else it will not.

In your implementation, you are returning the Promise (Ionic Storage) & the resolver added handlers internally to this Promise instead of your HTTP Observable.

That's why 2 handlers were added. One by you that makes the HTTP call & the one added internally by the resolver. They both got executed. But resolver was only looking for the resolved value of this.authenticationService.getUserId() , it routed to the corresponding page once it got user id.

Solution: Use async/ await to get your user ID & then return the HTTP observable from the resolver.

async resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

        const currentUserId=await this.authenticationService.getUserId();
        if(currentUserId){
            return this.ordersService.getOrdersByCustomer(currentUserId);
        }
        else{
            //Handle the scenario when you don't have user ID in storage
            // You can throw an error & add global error handler 
            // Or route to login / any other page according to your business needs
        }
       
      } 

Now, the resolver will add handlers to the HTTP observable returned & wait until it fetches data from BE before routing.

I have prepared a demo for you to understand how to use the first promise response into a second one without using await, rather in the same chain of RxJS, which guarantees you that once the resolver resolve the observable, both have been evaluated:

https://stackblitz.com/edit/switchmap-2-promises?file=index.ts

Key part is here:

from(promise1())
  .pipe(
    tap((v) => console.log('Logging the 1st promise result', v)),
    // use above the first promise response for second promise call
    switchMap((v) => promise2(v)),
    tap((v) => console.log('Logging the 2st promise result', v))
  )
  .subscribe();

SwitchMap (and others high obs operators too) allows you to transform the first promise/observable output into a new one within the chain.

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)));
  }

    

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM