我有一个组件需要多个 get 请求才能返回我需要的所有数据。这些是彼此独立的,但我想将所有调用放在一起,并在成功完成后加载 UI。
为了实现这一点,我正在使用 forkJoin,但我是 RxJS 的新手,而且我并不完全肯定我做得正确。请看我下面的例子。任何帮助将不胜感激。
import { Component, Input, OnInit, ElementRef, ViewChild } from '@angular/core';
import { Observable, forkJoin, of } from 'rxjs';
import { flatMap } from 'rxjs/operators';
import { DataService, ItemGroupDto} from 'src/app/shared/services/data-service.service';
import { UserService} from 'src/app/shared/services/user-service.service';
@Component({
selector: 'app-sample',
templateUrl: './sample.component.html',
styleUrls: ['./sample.component.less']
})
export class SampleComponent implements OnInit {
@Input() itemGroupId: number;
public datasetOne: Observable<any[]>;
public datasetTwo: Observable<any[]>;
public currentApprover: string;
constructor(
private _dateService: DateService,
private _userService: UserService,
) {}
ngOnInit() {
this.getData();
}
private _getApprover(itemGroupId: number) : Observable<ItemGroupDto> {
const approver = this._userService.getApprover(itemGroupId);
approver.subscribe(results => {
const approver = results.approvers.find(approver => (
approver.Id === 1
));
this.currentApprover = approver.UserFullName;
})
return approver;
}
private _sampleDatasetOne() : Observable<any>{
const sampleDatasetOne = this._dataService.sampleDatasetOne();
sampleDatasetOne.subscribe(results => {
this.datasetOne = results;
})
return sampleDatasetOne;
}
private _sampleDatasetTwo() : Observable<any>{
const sampleDatasetTwo = this._dataService.sampleDatasetTwo();
sampleDatasetTwo.subscribe(results => {
this.datasetTwo = results;
})
return sampleDatasetTwo;
}
getData() {
let sampleDatasetOne = this._sampleDatasetTwo();
let sampleDatasetTwo = this._sampleDatasetTwo();
let approver = this._getApprover(this.itemGroupId);
forkJoin(sampleDatasetOne, sampleDatasetTwo, approver).subscribe(_ => {
// all observables have been completed
this.loaded = true;
});
}
}
上面的代码基于我在研究使用 forkJoin 来实现此目的时发现的示例。这非常适合我的要求。它允许我在各自的订阅中操作每个结果,然后当所有可观察值完成时,我可以通过 *ngIf 或等效方法在视图中显示数据。
但是,我也看到过类似下面的例子:
let sampleDatasetOne = this._dataService.sampleDatasetOne();
let sampleDatasetTwo = this._dataService.sampleDatasetTwo();
let approver = this._userService.getApprover(this.itemGroupId);
forkJoin([sampleDatasetOne, sampleDatasetTwo, approver]).subscribe(results => {
this.datasetOne = results[0];
this.datasetTwo = results[1];
const approver = results[2].approvers.find(approver => (
approver.Id === 1
));
this.currentApprover = approver.UserFullName;
});
实际上,我在解释 forkJoin 这种特殊用法的在线示例或教程中看到了很多这种情况。这看起来有点疯狂,难以阅读、维护等。但也许我只是错过了一些关键信息或概念?
本质上,我只是在寻找正确的 Angular/rxJS 方式来等待多个调用完成并处理/操作每个调用的结果。
如果我走在正确的轨道上,有什么我遗漏的、做错的或可以改进的地方吗?
感谢任何人花时间回答并帮助我解决这个问题。
我想说这取决于你在做什么。
如果你只是想让所有 Observables 完成,然后用它们发出的值做一些事情,一些语法糖会有所帮助:
forkJoin([observable1, observable2, observable3])
// Array destructuring, a cool EcmaScript 6 feature.
.subscribe(([value1, value2, value3]) => {
this.value1 = value1;
this.value2 = value2;
this.value3 = value3
}
);
这仍然保留了在将可观察数据传递给 forkJoin 之前对其进行处理的可能性,例如:
const mappedObservable1 = observable1
.pipe(
map(value1 => mapToSomething(value1))
tap(mappedValue1 => this.value1 = mappedValue1)
);
forkJoin([mappedObservable1, observable2, observable3]).subscribe(() =>
this.loadingDone = true;
)
只要有一些指示,一切似乎都很好:
您最好创建一个服务来抽象出发出 http 请求并返回响应的步骤。本质上是一项充满以下内容的服务:
getMyEndpointData(): Observable<any> {
return this.http.get<any>('foo/bar/etc');
}
getMyEndpointDataById(id): Observable<any> {
return this.http.get<any>(`foo/bar/etc/${id}`);
}
// this is a good way to organize api interactions.
那么对于你的 forkJoin,我更喜欢将可观察的响应分隔成它们自己的变量的语法:
forkJoin([this.myService.getMyEndpointData(),
this.myService.getMyEndpointDataById(1)
]).subscribe(([data, idData]: any) => {
// do stuff with data recieved
}
如果您想接受失败并仍然完成其他任务,请务必捕获 forkjoin 的内部可观察量中的错误:
forkJoin([this.myService.getMyEndpointData().pipe(catchError(x => of(null))), // this deals with errors and allows other endpoints to succeed, you can do a null check in subscription code.
this.myService.getMyEndpointDataById(1)
]).subscribe(([data, idData]: any) => {
if(data) {
// do stuff with data recieved
}
}
如果你不这样做,并且 forkjoin 中的任何可观察到的内容都会失败,那么整个事情都会失败(如果我没记错的话,会默默地失败)。
您提供的代码看起来不错。为了可维护性和可读性,通常最好遵循尾随 $ 来命名可观察量。此外,您无需订阅和设置值,只需维护一个可观察对象,该可观察对象具有以您想要绑定到模板的对象格式映射的值。这将使其代码更少、更干净。
在这种情况下,是这样的:
let sampleDatasetOne$ = this._dataService.sampleDatasetOne();
let sampleDatasetTwo$ = this._dataService.sampleDatasetTwo();
let approver$ = this._userService.getApprover(this.itemGroupId);
let data$ = forkJoin([sampleDatasetOne$, sampleDatasetTwo$, approver$]).pipe(map(results => ({
dataSetOne: results[0],
dataSetTwo: results[1],
approver: results[2].approvers.find(approver => (
approver.Id === 1
))})));
data$ 可以在模板中进行异步管道传输(将订阅和取消订阅)。此外,您的映射函数可以对返回值进行强类型化,以便可以在模板或组件中的其他位置将其作为 Observable 进行检查。希望这对您有帮助。
但是 forkJoin 似乎是在这种情况下使用的正确运算符:)