I am trying to send httpclient in my Observable
function, it will run without the HttpClient Complete.
Here is a demo code which is used to reproduce
test() {
this.test2()
.pipe(
mergeMap((result) => {
console.log(result[0]);
return of([]);
})
)
.subscribe();
}
test1(): Observable<any> {
return of(this.getStudents());
}
test2(): Observable<any> {
return this.test1().pipe(
mergeMap((result) => {
if (result) {
result.map((rr) => {
if (rr.age == 1) {
this.client
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.subscribe((res) => {
console.log(res);
rr.age = rr.age * 10000;
});
} else {
rr.age = rr.age * 10;
}
return rr;
});
}
return of(result);
})
);
}
getStudents(): Student[] {
return [{ age: 1 }, { age: 2 }, { age: 3 }];
}
This is Student
export class Student {
age: number;
}
For expected result, the console.log(res);
should return before console.log(result[0]);
.
I have tried many ways like .toPromise
, and async await
, but fail to make it work.
You could fork a test version below:
https://stackblitz.com/edit/save-file-to-drive-7dawzd?file=src/app/app.component.ts
CodePudding user response:
Based on your expected result, you need the api call to finish first before printing the Student[0].
The issues on your code:
- You are subscribing to an api call which you are not waiting to
finish, hence
console.log(result[0])
prints first beforeconsole.log(res);
because the api call isn't done yet.
I used a couple of RXJS operators to get what you want.
mergeMap
to flatten the inner observableof
to convert the student array into an observablemap
to transform the current array into a a new array with their corresponding new ageforkJoin
- we wrapped multiple requests into one observable and will only return when a response has been received for all requests.
This is certainly only one way to do it and there might be other better ways.
test() {
this.test2()
.pipe(
mergeMap((result) => {
console.log(result);
if (result && result.length > 0) {
console.log(result[0]);
}
return of([]);
})
)
.subscribe();
}
test1(): Observable < Student[] > {
// return of(null);
return of(this.getStudents());
}
test2(): Observable < Student[] > {
return this.test1().pipe(
mergeMap((result) => {
if (!result) {
return of(result);
}
return forkJoin(
result.map((rr) =>
rr.age === 1 ?
this.client
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.pipe(
map((res) => {
console.log(res);
return (rr.age = rr.age * 10000);
})
) :
of ((rr.age = rr.age * 10))
)
).pipe(
map((paths) => {
return result.map((e, index) => ({
age: paths[index],
}));
})
);
})
);
}
getStudents(): Student[] {
return [{
age: 1
}, {
age: 2
}, {
age: 3
}];
}
I modified the stackblitz you created to mock the solution.
CodePudding user response:
The RxJS way:
Though it's strange to hit an api endpoint and ignore it's result, you can be sure to wait on that result using just about any of the RxJS higher-order operators (like mergeMap, switchMap, concatMap, etc).
Since you're doing this for an entire array, forkJoin is a natural choice. forkJoin
will subscribe to all your observables and emit an array of the last value for each once they complete.
test() {
// mergeMap here is silly, it doesn't merge or map anything in a
// meaningful way. I've removed it.
this.test2().subscribe(
students => console.log(students[0])
);
}
test1(): Observable<Student[]> {
return of(this.getStudents());
}
test2(): Observable<Student[]> {
return this.test1().pipe(
map(students => students?.map(student => {
if(student.age == 1){
return this.client.get(
'https://api.coindesk.com/v1/bpi/currentprice.json'
).pipe(
tap(currentPrice => console.log(currentPrice)),
mapTo({...student, age: student.age * 1000})
)
}else{
return of({...student, age: student.age * 10})
}
})),
mergeMap(studentCalls => forkJoin(studentCalls))
);
}
getStudents(): Student[] {
return [{ age: 1 }, { age: 2 }, { age: 3 }];
}