简体   繁体   English

Angular 12 + Ionic 5:解析器不等待存储和 HTTP 调用完成

[英]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.这个问题是关于我正在使用 Angular 12 和 Ionic 5 构建的 SPA。当我在主页上时,我可以单击侧菜单中的“订单历史”链接,这会将我引导到订单历史页面。 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).在这个解析器中,执行了 2 个主要操作(严格按顺序)。 They are:他们是:

  1. Receive the Currently Logged In User ID from Ionic Storage.从 Ionic Storage 接收当前登录的用户 ID。

  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.使用从上述步骤接收到的当前登录用户 ID,并对后端进行 HTTP 调用以获取与用户相关的订单。 Only after the HTTP call finishes successfully, navigate to the "Order History" page and log the HTTP call data to console.只有在 HTTP 调用成功完成后,导航到“订单历史”页面并将 HTTP 调用数据记录到控制台。

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.当我单击侧菜单中的“订单历史记录”链接时,解析器运行,从存储中获取当前登录的用户 ID,但它不会等待 HTTP 调用完成。 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.相反,它只是路由到 Order History 页面,然后执行 HTTP 请求,然后给我来自 HTTP 请求的结果。 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.它导航到目标页面,然后完成 API 调用并提供数据。 I am trying to fix this so that the Resolver performs the 2 main operations as indicated above, before the actual routing occurs.我正在尝试解决此问题,以便解析器在实际路由发生之前执行上述 2 个主要操作。

HERE IS MY CODE:这是我的代码:

app-routing.module.ts:应用程序路由.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获取订单历史.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身份验证.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订单.service.ts

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

order-history.page.ts订单历史.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. Resolve 在内部将处理程序添加到返回的 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.在您的实现中,您将返回 Promise(离子存储)和解析器在此 Promise 内部添加处理程序,而不是您的 HTTP Observable。

That's why 2 handlers were added.这就是添加 2 个处理程序的原因。 One by you that makes the HTTP call & the one added internally by the resolver.一个由您进行 HTTP 调用和由解析器内部添加的一个。 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.但是解析器只是在寻找this.authenticationService.getUserId()的解析值,一旦获得用户ID,它就会路由到相应的页面。

Solution: Use async/ await to get your user ID & then return the HTTP observable from the resolver.解决方案:使用 async/await 获取您的用户 ID,然后返回解析器可观察到的 HTTP。

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.现在,解析器将向返回的 HTTP 可观察对象添加处理程序并等待它从 BE 获取数据,然后再进行路由。

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:我为您准备了一个演示,以了解如何在不使用 await 的情况下将第一个 promise 响应用于第二个响应,而不是在 RxJS 的同一链中,这可以保证一旦解析器解决了 observable,两者都已被评估:

https://stackblitz.com/edit/switchmap-2-promises?file=index.ts 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. SwitchMap(以及其他高 obs 运算符)允许您将第一个 promise/observable output 转换为链中的一个新的。

You can convert your promise to an observable with defer from rxjs and then chain your observables in a pipe.您可以将 promise 转换为可观察的,并从rxjs defer ,然后将您的可观察链接到 pipe 中。

I am not sure if you can use from instead of defer but defer should work for sure我不确定您是否可以使用from而不是deferdefer应该可以肯定

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

    

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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