Article summary
Here are a few lessons I learned about working with multiple observables in Angular’s ngOnInit. Recently, I was working on a software project that involved building an Angular component. That component needed to use the results of two subscriptions to determine whether a certain element could be shown. I ran into an asynchronous race condition issue, as my template was not reloading when the subscription changed.
The Problem
I wanted my HTML template to look as simple as this:
<div *ngIf="shouldShowSecretComponent">
<topSecret/>
</div>
However, top secret can only occur when two distinct conditions are met.
First Try
At first, I started by subscribing to both calls and then checking both in the template. I initially had the following logic to compute a UI state based on a feature flag and a user permission:
ngOnInit(): void {
this.featureFlagService.flagChange$.subscribe((flags): void => {
this.enableFeature = flags[FeatureFlag.flag1]
});
this.userPolicyService.getUserPolicy(PolicyNamesType.Policy1).subscribe((res): void => {
this.hasPermission = res.body.permissions.includes(this.permission);
});
}
Next, I also tried updating a value in both subscriptions to be used in the template in the if.
That meant adding to lines 5 and 9 above the following
this.shouldShowSecretComponent = this.hasPermission && this.enableFeature` to the end of both subscriptions
Second Try
While the previous method technically works, it has problems:
- The same computed value (shouldShowSecretComponent) is set in two places.
- There is no clear control over the order or timing. That’s not a problem in my case, but it’s not ideal.
- It’s easy to introduce bugs when one observable emits before the other.
- Testing what would happen if they emitted concurrently wasn’t easy.
Eventually, I came across `combineLatest` and was able to apply it in the following way:
this.shouldShowSecretComponent$ = combineLatest([
this.featureFlagService.flagChange$,
this.userPolicyService.getUserPolicy(PolicyNamesType.policy1)
]).pipe(
map(([flags, res]) => {
const enablefeature = flags[FeatureFlag.flag1];
const hasPermission = res.body.permissions.includes(permission1);
return enablefeature && hasPermission;
})
);
Result
This final tweak:
- Allowed me to avoid duplicate assignment.
- Made it easier to test and reason about what was happening to my component.