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