繁体   English   中英

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

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

情况:

这个问题是关于我正在使用 Angular 12 和 Ionic 5 构建的 SPA。当我在主页上时,我可以单击侧菜单中的“订单历史”链接,这会将我引导到订单历史页面。 我正在使用解析器,以便在路由完成之前从数据库中获取订单历史记录,以便在路由完成时,用户可以看到数据,因为它可以通过解析器轻松获得。 在这个解析器中,执行了 2 个主要操作(严格按顺序)。 他们是:

  1. 从 Ionic Storage 接收当前登录的用户 ID。

  2. 使用从上述步骤接收到的当前登录用户 ID,并对后端进行 HTTP 调用以获取与用户相关的订单。 只有在 HTTP 调用成功完成后,导航到“订单历史”页面并将 HTTP 调用数据记录到控制台。

问题:

当我单击侧菜单中的“订单历史记录”链接时,解析器运行,从存储中获取当前登录的用户 ID,但它不会等待 HTTP 调用完成。 相反,它只是路由到 Order History 页面,然后执行 HTTP 请求,然后给我来自 HTTP 请求的结果。 但这违背了解析器的目的,解析器应该等待所有调用完成,然后导航到目标页面,而是相反。 它导航到目标页面,然后完成 API 调用并提供数据。 我正在尝试解决此问题,以便解析器在实际路由发生之前执行上述 2 个主要操作。

这是我的代码:

应用程序路由.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 { }

获取订单历史.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);
    });

  }
}

身份验证.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;
    }
  });
}

订单.service.ts

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

订单历史.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 在内部将处理程序添加到返回的 Promise/observables。 如果数据被获取,它将路由到给定的页面,否则它不会。

在您的实现中,您将返回 Promise(离子存储)和解析器在此 Promise 内部添加处理程序,而不是您的 HTTP Observable。

这就是添加 2 个处理程序的原因。 一个由您进行 HTTP 调用和由解析器内部添加的一个。 他们俩都被处决了。 但是解析器只是在寻找this.authenticationService.getUserId()的解析值,一旦获得用户ID,它就会路由到相应的页面。

解决方案:使用 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
        }
       
      } 

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

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

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

关键部分在这里:

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(以及其他高 obs 运算符)允许您将第一个 promise/observable output 转换为链中的一个新的。

您可以将 promise 转换为可观察的,并从rxjs defer ,然后将您的可观察链接到 pipe 中。

我不确定您是否可以使用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