我在使用Angular和Observables时遇到问题,我在此Stackblizt中将其复制:https://stackblitz.com/edit/angular-ivy-dl1y3y
放置一些上下文:
好吧,让我逐步向您展示
第1步
第一步是调用第一个URL(assets/step-1-getAccountReference.json
)以检索帐户参考ID:
{
"accountIdRef": "/assets/step-2.getAccount.json"
}
第2步
有了这个accountIdRef
,我可以调用另一个URL("/assets/step-2.getAccount.json"
)来检索帐户信息:
{
"accountId": "123",
"details": [
{
"nameRef": "/assets/step-3-pet-1-name.json",
"genderRef": "/assets/step-3-pet-1-gender.json"
},
{
"nameRef": "/assets/step-3-pet-2-name.json",
"genderRef": "/assets/step-3-pet-2-gender.json"
}
]
}
第3步
最后一步是通过调用其他URL(for each
和nameRef
)来检索genderRef
只宠物的所有详细信息。
如果您打开控制台,则应该看到如果我直接订阅并登录该帐户,则会显示该窗口(步骤1 和步骤2 中的可观察内容已完成) :
{
"accountId": "123",
"details": [
{
"nameRef": "/assets/step-3-pet-1-name.json",
"genderRef": "/assets/step-3-pet-1-gender.json"
},
{
"nameRef": "/assets/step-3-pet-2-name.json",
"genderRef": "/assets/step-3-pet-2-gender.json"
}
]
}
如果我3秒钟后再次登录该帐户,则会显示该信息(第3步中的所有观察值均已完成):
{
"accountId": "123",
"details": [
{
"nameRef": "/assets/step-3-pet-1-name.json",
"genderRef": "/assets/step-3-pet-1-gender.json",
"name": "Santa's Little Helper",
"gender": "Male"
},
{
"nameRef": "/assets/step-3-pet-2-name.json",
"genderRef": "/assets/step-3-pet-2-gender.json",
"name": "Snowball II",
"gender": "Female"
}
]
}
我想等待所有可观察对象完成(包括第3步),但是当然是动态的,而不是使用固定的超时时间。
这是我现在拥有的:
export class HttpService {
constructor(private http: HttpClient) {}
getAccount(): Observable<Account> {
return this.http.get("assets/step-1-getAccountReference.json").pipe( // Step 1
mergeMap((accountReference: AccountReference) => {
return this.http.get("" + accountReference.accountIdRef); // Step 2
}),
delay(500),
map((account: Account) => {
account.details.forEach((details: AccountDetails) => { // Step 3
let name$ = this.http.get("" + details.nameRef);
let gender$ = this.http.get("" + details.genderRef);
forkJoin([name$, gender$]).subscribe(results => {
details.name = results[0]["name"];
details.gender = results[1]["gender"];
});
});
return account;
})
);
}
}
那么,我该如何修改此代码,以使步骤3保持同步?我应该使用哪个运算符替换此forEach
?
谢谢您的帮助!
答案 0 :(得分:2)
您可以做的是通过详细信息进行映射并构建可观察的数组,以填充缺少的属性。
然后,您将该数组传递给forkJoin,它将把丢失的数据拉入您的详细信息中。
最后,您更新帐户详细信息并返回帐户。
export class HttpService {
constructor(private http: HttpClient) {}
getAccount(): Observable<Account> {
return this.http.get("assets/step-1-getAccountReference.json").pipe( // Step 1
mergeMap((accountReference: AccountReference) => {
return this.http.get("" + accountReference.accountIdRef); // Step 2
}),
delay(500), // why this delay ?
mergeMap((account: Account) => {
const populatedDetailsObservableArray = account.details.map((details: AccountDetails) => {
return forkJoin([name$, gender$]).pipe(
map(results => {
details.name = results[0]["name"];
details.gender = results[1]["gender"];
return details;
})
);
});
return forkJoin(populatedDetailsObservableArray).pipe(
map((newDetails: AccountDetails[]) => {
account.details = newDetails;
return account;
})
);
})
);
}
}
答案 1 :(得分:1)
这似乎可行:
getAccount() {
return this.http.get("assets/step-1-getAccountReference.json").pipe(
mergeMap((accountReference: AccountReference) =>
this.http.get("" + accountReference.accountIdRef).pipe(
mergeMap((account: Account) =>
forkJoin(account.details.map(detail => this.getDetails(detail))).pipe(
map(_ => account)
)
)
)
)
);
}
private getDetails(detail: AccountDetails): Observable<AccountDetails> {
let name$ = this.http.get("" + detail.nameRef);
let gender$ = this.http.get("" + detail.genderRef);
return forkJoin([name$, gender$]).pipe(
map(([nameObj, genderObj]: [{name: string}, {gender: string}]) => {
detail.name = nameObj.name;
detail.gender = genderObj.gender;
return detail;
})
);
}
使用两种独立的方法似乎更容易。
getDetails
方法使用详细信息来设置两个get操作。然后,它使用forkForin来执行它们。注意,则无需在此处订阅!然后,forkJoin使用地图来映射姓名和性别,并以Observable形式返回结果详细信息。
getAccount
方法使用mergeMap获取第一组子数据(帐户参考),并使用另一个mergeMap处理帐户详细信息。 forkJoin使用映射(而不是foreach)来处理每组详细信息。对于每个细节,它都会调用getDetails方法将适当的值设置到details对象中。
然后将结果映射到帐户,以返回结果帐户信息。
生成的StackBlitz在这里:https://stackblitz.com/edit/angular-ivy-etwwas?file=src/app/http.service.ts
答案 2 :(得分:0)
您可以为此目的使用RxJs库。 Here是一个适用于您的情况的util函数的链接。它称为concatMap,其作用是合并请求并保存其顺序。
以下是官方文档中的示例:
// RxJS v6+
import { of } from 'rxjs';
import { concatMap, delay, mergeMap } from 'rxjs/operators';
//emit delay value
const source = of(2000, 1000);
// map value from source into inner observable, when complete emit result and move to next
const example = source.pipe(
concatMap(val => of(`Delayed by: ${val}ms`).pipe(delay(val)))
);
//output: With concatMap: Delayed by: 2000ms, With concatMap: Delayed by: 1000ms
const subscribe = example.subscribe(val =>
console.log(`With concatMap: ${val}`)
);
// showing the difference between concatMap and mergeMap
const mergeMapExample = source
.pipe(
// just so we can log this after the first example has run
delay(5000),
mergeMap(val => of(`Delayed by: ${val}ms`).pipe(delay(val)))
)
.subscribe(val => console.log(`With mergeMap: ${val}`));