I have been using Angular/RxJS for a few weeks and have various models that are built from multiple REST requests which I have thus far achieved using switchMap(). Here is a simple contrived example (stackblitz: https://stackblitz.com/edit/angular-o1djbb):
import { Component, OnInit, } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay, switchMap } from 'rxjs/operators';
interface Order {
id: string;
itemName: string;
details?: string;
}
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
order: Order;
ngOnInit() {
this.getOrderFromApi(123)
.subscribe(item => this.order = item);
}
getOrderFromApi(id): Observable<Order> {
const item$ = this.getItemName(id);
const fullItem$ = item$.pipe(
switchMap(n => {
console.log(`Got name: '${n}''`);
return this.getDetails(n);
},
(nameReq, detailsReq) => ({ id: '123', itemName: nameReq, details: detailsReq })
));
return fullItem$;
}
getItemName(id): Observable<string> {
return this.fakeXhr('foo');
}
getDetails(itemName): Observable<string> {
console.log(`Got details '${itemName}''`)
return this.fakeXhr('Some details about foo');
}
fakeXhr(payload: any) {
return of(payload)
.pipe(delay(2000));
}
}
And a simple template:
<p>
item: {{order && order.itemName}}
</p>
<p>
details: {{order && order.details}}
</p>
This works but the order info is not rendered until both requests complete. What I would like to happen is for the itemName to render as soon as it's available and then details to render when they become available. Hence taking advantage of the multiple values that Observables can emit. Eg:
// first value emitted:
{ itemName: 'foo', details: null }
// second value emitted:
{ itemName: 'foo', details: 'Some details about foo' }
I realise I could probably achieve this with a BehaviourSubject or using Redux (as I have in the past with React), but feel there is a simple solution I can't quite grasp due to the newness of all this for me.
Use expand
to emit the data from the first request straight away and subsequently do the second request.
expand
will call the inner request recursively, getting the order
from the last request as input, so we only execute the second request when the order
has no details
and end the recursion otherwise with an EMPTY
Observable.
import { Observable, EMPTY } from 'rxjs';
import { map, expand } from 'rxjs/operators';
getOrderFromApi(id): Observable<Order> {
return this.getItemName(id).pipe(
map(itemName => ({ id, itemName, details: null } as Order)),
expand(order => order.details
? EMPTY
: this.getDetails(order.itemName).pipe(
map(details => ({ id, itemName: order.itemName, details } as Order))
)
)
);
}
https://stackblitz.com/edit/angular-umqyem
The returned Observable will emit:
{ "id": 123, "itemName": "foo", "details": null }
{ "id": 123, "itemName": "foo", "details": "Some details about foo" }
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments