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。当我在主页上时,我可以单击侧面的“订单历史记录”link菜单,这会将我带到“订单历史记录”页面。我正在使用解析器,以便在路由完成之前从数据库中获取订单历史记录,这样当路由完成时,用户可以看到数据,因为它可以通过解析器轻松获得。在此解析器中,执行了 2 个主要操作(严格按顺序)。他们是:
从 Ionic Storage 接收当前登录的用户 ID。
使用从上述步骤接收到的当前登录用户 ID,并向后端发出 HTTP 调用以获取与用户相关的订单。只有在 HTTP 调用成功完成后,导航到“订单历史记录”页面并将 HTTP 调用数据记录到控制台。
问题:
当我点击侧面菜单中的“订单历史记录”link 时,解析器运行,从存储中获取当前登录的用户 ID,但它没有 等待 HTTP 调用完成。相反,它只是路由到“订单历史记录”页面,然后执行 HTTP 请求,然后将 HTTP 请求的结果提供给我。但这违背了解析器的目的! Resolver 应该等待所有调用完成,然后导航到目标页面,但相反,它导航到目标页面,然后完成 API 调用并提供数据。 我正在尝试修复此问题,以便解析器在实际路由发生之前执行上面指示的 2 个主要操作。
这是我的代码:
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 )
}
顺序-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.
在您的实施中,您return使用 Promise(离子存储)并且解析器在内部向此 Promise 添加了处理程序,而不是您的 HTTP Observable。
这就是添加 2 个处理程序的原因。一个由您进行 HTTP 调用,另一个由解析器在内部添加。他们都被处决了。但是resolver只是在寻找this.authenticationService.getUserId()
的解析值,一旦获得用户id就路由到相应的页面。
解法:
使用 async/await 获取您的用户 ID,然后 return 从解析器可观察到的 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 可观察对象添加处理程序 returned 并等待它在路由之前从 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 输出转换为链中的新输出。
您可以使用 defer
从 rxjs
将您的承诺转换为可观察对象,然后将您的可观察对象链接到管道中。
我不确定您是否可以使用 from
而不是 defer
,但 defer
应该可以肯定
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return defer(() => this.authenticationService.getUserId())
.pipe(switchMap((currentUserId) =>
this.ordersService.getOrdersByCustomer(currentUserId)));
}
情况:
这个问题是关于我正在使用 Angular 12 和 Ionic 5 构建的 SPA。当我在主页上时,我可以单击侧面的“订单历史记录”link菜单,这会将我带到“订单历史记录”页面。我正在使用解析器,以便在路由完成之前从数据库中获取订单历史记录,这样当路由完成时,用户可以看到数据,因为它可以通过解析器轻松获得。在此解析器中,执行了 2 个主要操作(严格按顺序)。他们是:
从 Ionic Storage 接收当前登录的用户 ID。
使用从上述步骤接收到的当前登录用户 ID,并向后端发出 HTTP 调用以获取与用户相关的订单。只有在 HTTP 调用成功完成后,导航到“订单历史记录”页面并将 HTTP 调用数据记录到控制台。
问题:
当我点击侧面菜单中的“订单历史记录”link 时,解析器运行,从存储中获取当前登录的用户 ID,但它没有 等待 HTTP 调用完成。相反,它只是路由到“订单历史记录”页面,然后执行 HTTP 请求,然后将 HTTP 请求的结果提供给我。但这违背了解析器的目的! Resolver 应该等待所有调用完成,然后导航到目标页面,但相反,它导航到目标页面,然后完成 API 调用并提供数据。 我正在尝试修复此问题,以便解析器在实际路由发生之前执行上面指示的 2 个主要操作。
这是我的代码:
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 )
}
顺序-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.
在您的实施中,您return使用 Promise(离子存储)并且解析器在内部向此 Promise 添加了处理程序,而不是您的 HTTP Observable。
这就是添加 2 个处理程序的原因。一个由您进行 HTTP 调用,另一个由解析器在内部添加。他们都被处决了。但是resolver只是在寻找this.authenticationService.getUserId()
的解析值,一旦获得用户id就路由到相应的页面。
解法: 使用 async/await 获取您的用户 ID,然后 return 从解析器可观察到的 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 可观察对象添加处理程序 returned 并等待它在路由之前从 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 输出转换为链中的新输出。
您可以使用 defer
从 rxjs
将您的承诺转换为可观察对象,然后将您的可观察对象链接到管道中。
我不确定您是否可以使用 from
而不是 defer
,但 defer
应该可以肯定
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return defer(() => this.authenticationService.getUserId())
.pipe(switchMap((currentUserId) =>
this.ordersService.getOrdersByCustomer(currentUserId)));
}