The displaying of your car has two sources: selectedCar and viewConfig. We can design an observable that will emit only when both of these data are available AND whenever either of them change. The combineLatest operator is perfect for this:
So now, instead of having two separate subscriptions, we just have one!
ngOnInit() {
combineLatest([
this.selectedCarStore.state$,
this.configStore.state$]
).pipe(
takeUntil(this.ngUnsubscribe)
)
.subscribe(([carState, config]) => {
this.allCars = carState.allCars;
this.selectedCar = carState.selectedCar;
this.viewConfig = config.viewConfig;
this.showCar();
});
}
The above code in the subscribe block will only emit when both selectedCarStore.state$ and configStore.state$ have emitted at least once.
Check out this working StackBlitz; See how the view is updated when you select different View Config or select a different car.
This accomplishes your goal... but we can make the code even better (simpler)!
If we take a look at your comment for the showCar() method:
// update UI with selected car and view config
It sort of hints at the fact that we weren't previously structuring the code in a declarative / reactive way. Now that we have designed a stream that emits when we want, with all the necessary data, we don't need to "manually" check in a function to update the view. Our view will update automatically, because our stream emits at the appropriate time. Any time either of the sources emit a new value, the view will automatically be updated! :-)
Notice how we are copying state into our component:
this.allCars = carState.allCars;
this.selectedCar = carState.selectedCar;
this.viewConfig = config.viewConfig;
This actually isn't necessary. Instead of subscribing in our component, we can leave all the data the view needs as observables. So instead of:
public viewConfig: ViewConfig;
public selectedCar: Car;
public allCars: Car[];
We can do:
private viewConfig$ = this.configStore.state$.pipe(
map(state => state.viewConfig)
);
private selectedCar$ = this.selectedCarStore.state$.pipe(
map(state => state.selectedCar)
);
public selectedCarVm$ = combineLatest([this.selectedCar$, this.viewConfig$]).pipe(
map(([car, config]) => ({car, config}))
);
public allCars$ = this.selectedCarStore.state$.pipe(
map(state => state.allCars)
);
The public ones are what the view needs. I used the Vm suffix to distinguish between the data for selectedCar and the view model we designed to only emit when both the data and config are available.
Notice, we define these observable streams right on our class, we no longer need to do this in ngOnInit. We also no longer need to subscribe, as we can use the async pipe in our template.
<div *ngIf="selectedCarVm$ | async as selected"
[ngStyle]="{'background-color': selected.config.bgColor }"
[class.large]="selected.config.size === 'large'"
>
{{ selected.car.year }} {{ selected.car.make }} {{ selected.car.model }}
</div>
The means we no longer need to manage unsubscribing, so we can get rid of ngOnDestroy, the ngUnsubscribe Subject as well as all our takeUntils!
Please compare code between your Original Code > the First Solution > and this Reactive Solution.
Each step reduces the code and simplifies the logic.
I hope this helps to show you how you can design streams to emit at the desired time, and keep the data in observable form in order to reduce the amount of code (and complexity).
Cheers! :-)