![](/img/trans.png)
[英]Angular 12 HttpInterceptor Wait until a http client method finish
[英]Angular 12 + Ionic 5: Resolver does not wait for the Storage and HTTP calls to finish
情況:
這個問題是關於我正在使用 Angular 12 和 Ionic 5 構建的 SPA。當我在主頁上時,我可以單擊側菜單中的“訂單歷史”鏈接,這會將我引導到訂單歷史頁面。 我正在使用解析器,以便在路由完成之前從數據庫中獲取訂單歷史記錄,以便在路由完成時,用戶可以看到數據,因為它可以通過解析器輕松獲得。 在這個解析器中,執行了 2 個主要操作(嚴格按順序)。 他們是:
從 Ionic Storage 接收當前登錄的用戶 ID。
使用從上述步驟接收到的當前登錄用戶 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
而不是defer
但defer
應該可以肯定
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.