如何解决你能转换 switchMap observable 的结果来获取它的值而不是 observable 本身吗?
我正在尝试创建一个内存中的单例来保存用户正在浏览的当前供应商。
在所有特定路由上使用警卫来捕获参数:
canActivate(
route: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean | UrlTree> {
let currentUrl = this._router.url;
const param = route.params['vendname'];
return this._vendorService.getByName(param).pipe(map(a => {
if (a == null) {
this._snackBarService.open('vendor not found','x',{ duration: 5000 });
return this._router.parseUrl(currentUrl);
}
return true;
}));
}
服务用于按名称获取供应商。如果它存在于内存中,则返回它。如果没有,请先从服务器获取。
set vendor(value: IUser) {
this._vendor.next(value);
}
get vendor$(): Observable<IUser> {
return this._vendor.asObservable();
}
getByName(name: string): Observable<IUser> {
const result = this.vendor$.pipe(map(v => {
if (v != null && v.displayName == name) {
return v;
}
else {
return this.Get<IUser>(`api/vendor/${name}`).pipe(switchMap(v => {
this.vendor = v;
return of(v)
// ...
}));
}
}))
return result;
}
问题是我需要检查 vendor$
的值,该值返回 Obervable<IUser>
,但 switchMap 也返回 Obervable<IUser>
,导致结果为 Observable<Observable<IUser>>
。如何让 result
返回单个 User Observable?
解决方法
我相信您误解了 switchMap()
的用例,因为它不是您的 Observable<Observable<IUser>>
的原因。 map()
中的第一个 getByName()
运算符要么返回 IUser
类型的值(如果为真),要么返回 IUser
的可观察值(如果为假)。所以 getByName()
返回 Observable<IUser>
或 Observable<Observable<IUser>>
。
但是,如果您想尝试从可观察对象中重放内存中的值,那么我建议使用 shareReplay()
。此外,对于这种情况,这里有一个推荐的模式。
private vendorName = new Subject<string>();
public vendor$ = this.vendorName.pipe(
distinctUntilChanged(),switchMap(name=>this.Get<IUser>(`api/vendor/${name}`)),shareReplay(1)
)
public getByName(name:string){
this.vendorName.next(name);
}
然后在保护文件中。
canActivate(
// ...
){
let currentUrl = this._router.url;
const param = route.params['vendname'];
this._vendorService.getByName(param);
return this._vendorService.vendor$.pipe(
map(vendor=>{
if(!vendor){
this._snackBarService.open('Vendor not found','x',{ duration: 5000 });
return this._router.parseUrl(currentUrl);
}
return true;
})
);
通过此设置,您的组件和守卫可以订阅 vendor$
以获取所需的 IUser
数据。当您知道供应商的名称时,您可以调用 getByName()
。这将触发以下步骤:
-
userName
主题将发出新名称。 -
vendor$
observable(订阅userName
)将采用该名称,然后 switchMap 从内部 observable 获取值(Get
方法) -
shareReplay(1)
确保对vendor$
(当前或未来)的任何订阅都将从内存中获取最后 (1) 个发出的值。
如果您需要更新供应商名称,只需使用您的新名称调用 getByName()
,订阅 vendor$
的所有内容都会自动获得更新的值。
与我看到的 vendor$ 相关的是 IUser 流,这里是使用 iif rxjs 运算符重构您的代码以订阅第一个或第二个可观察对象根据您的情况
this.vendor$.pipe(
mergeMap(v => iif(
() => v && v.displayName == name,of(v),this.Get<IUser>(`url`).pipe(tap(v => this.vendor = v))
)
)
)
,
虽然我喜欢@digclo 的回答,但我正在添加另一个有效的解决方案。
好像 canActivate
也返回 Promise
,您也可以使用可靠的旧 async
await
方法。
注意:在 rxjs 7 中,toPromise
现在是 lastValueFrom
。
async getByVendorName(name: string): Promise<IUser> {
const v:IUser = await new Promise(resolve => {
this.vendor$.subscribe(a => {
if (a != null && a.displayName.toLocaleLowerCase() == name) {
resolve(a);
}
});
resolve(null);
});
if (v == null) {
return this.Get<IUser>(`api/vendor/${name}`).pipe(switchMap(v => {
this.vendor = v;
return of(v);
})).toPromise();
}
return Promise.resolve(v);
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。