Reputation:
I have got some get-Calls from an API in a service.ts file:
//code
getCars()
{
this.car = this.http.get(car_url)
return this.car;
}
getTires()
{
this.tires = this.http.get(tires_url)
return this.tires;
}
getSeats()
{
this.seats = this.http.get(seats_url)
return this.seats;
}
In the detail.compoenent.ts I filter this data to a selected car and then present it through detail.component.html - detail.compoenent.ts looks like this:
//code
ngOnInit()
{
this.Service.getCars()
.subscribe(cars => this.car = cars.find(/*code (selecting car with certain car_id*/)
this.Service.getTires()
.subscribe(tires => {this.tires = tires.filter(/*code (selecting all the tires with the selected car_id*/)}
this.Service.getSeats()
.subscribe(seats => {this.seats = seats.filter(/*code (selecting all the seats with the selected car_id*/)}
}
To filter for the tires and seats, getCar() has to be executed first because information of it is needed to filter for the tires and seats. So how do I have to put the code to ensure this.Service.getCars()subscribe(/code/) is executed before this.Service.getTires().subscribe(/code/) and this.Service.getSeats().subscribe(/code/)?
Upvotes: 4
Views: 4926
Reputation: 12960
You can use mergeMap()
and subsequently call other observables, you can use the data of the other previous API in another.
this.Service.getCars().pipe(mergeMap((cars) => {
// cars is available for the other two
return this.Service.getTires().pipe(mergeMap((tierData) => {
// tierData is avaibale for getting seats.
return this.Service.getSeats()
}))
})).subscribe((seats) => {
})
You can merge the other two in the first pipe itself.
Upvotes: 1
Reputation: 23793
Having nested subscriptions is a big code smell. If you feel the need to have a nested subscription, please search higher order observables like switchMap, concatMap, mergeMap, zip, etc.
I took the time to build a full demo of what you're trying to achieve.
First, let's start by defining our interfaces to have some type safety:
export interface Car {
id: string;
seatId: string;
tyreId: string;
}
export type CarResolved = Omit<Car, "seatId" | "tyreId"> & {
seat: Seat;
tyre: Tyre;
};
export interface Tyre {
id: string;
diameter: number;
}
export interface Seat {
id: string;
width: number;
}
Now that we know what kind of data structure we want, let's build a service and return mock data that you can later on replace with your backend:
@Injectable()
export class ResourcesService {
public getCars(): Observable<CarResolved[]> {
return this._getCars().pipe(
switchMap(cars =>
forkJoin(
...cars.map(car =>
forkJoin(
this._getTyreById(car.tyreId),
this._getSeatById(car.seatId)
).pipe(
map(([tyre, seat]) => ({
id: car.id,
seat,
tyre
}))
)
)
)
)
);
}
private _getCars(): Observable<Car[]> {
return of(mockCars);
}
private _getTyreById(id: string): Observable<Tyre> {
return of(mockTyres.find(tyre => tyre.id === id));
}
private _getSeatById(id: string): Observable<Seat> {
return of(mockSeats.find(seat => seat.id === id));
}
}
const mockCars: Car[] = [
{ id: "car-1", seatId: "seat-1", tyreId: "tyre-1" },
{ id: "car-2", seatId: "seat-2", tyreId: "tyre-2" },
{ id: "car-3", seatId: "seat-1", tyreId: "tyre-3" }
];
const mockTyres: Tyre[] = [
{ id: "tyre-1", diameter: 80 },
{ id: "tyre-2", diameter: 60 },
{ id: "tyre-3", diameter: 75 }
];
const mockSeats: Seat[] = [
{ id: "seat-1", width: 10 },
{ id: "seat-2", width: 20 },
{ id: "seat-3", width: 30 }
];
If you look at the getCars
method, it has absolutely no subscribe. It just returns an observable that someone can later on subscribe to. Everything is done through streams :)
Finally, the easiest part: The view.
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
public cars$: Observable<CarResolved[]> = this.resourcesService.getCars();
constructor(private resourcesService: ResourcesService) {}
}
Notice here too: No subscription at all! :)
And the HTML side:
<pre>{{ cars$ | async | json }}</pre>
Our view now shows our array of resolved cars:
[
{
"id": "car-1",
"seat": {
"id": "seat-1",
"width": 10
},
"tyre": {
"id": "tyre-1",
"diameter": 80
}
},
{
"id": "car-2",
"seat": {
"id": "seat-2",
"width": 20
},
"tyre": {
"id": "tyre-2",
"diameter": 60
}
},
{
"id": "car-3",
"seat": {
"id": "seat-1",
"width": 10
},
"tyre": {
"id": "tyre-3",
"diameter": 75
}
}
]
Here's a live demo on stackblitz: https://stackblitz.com/edit/angular-qypary
Upvotes: 0
Reputation: 1352
Simple solution could be make subsequent http requests in call back functions.
ngOnInit()
{
this.Service.getCars().subscribe(
cars => {
this.car = cars.find();
this.Service.getTires().subscribe(tires => {
this.tires = tires.filter();
this.Service.getSeats().subscribe(
seats => {this.seats =
seats.filter(/*code (selecting all
the seats with the selected car_id*/)}
}
}
});
}
until all data is getting loaded you can show spinner to end user.
Upvotes: -1