combineLatestEager

The RxJS operator combineLatest is frequently employed to merge multiple observables, triggering emissions whenever any of the source observables emits a value. However, a limitation arises as it mandates all sources to emit at least once. In UI scenarios, combineLatest is often employed to construct a view model observable (vm$). When one of the sources is a Subject, using vm$ with the async pipe becomes problematic because the Subject may not emit any values initially. This issue is elegantly addressed by the augmented functionality of the combineLatestEager function.

combineLatestEager seamlessly extends the capabilities of combineLatest by incorporating startWith(null) for each Subject present in the provided sources. This augmentation ensures that the combined observable initializes with a null value for each Subject, resolving the challenge posed by combineLatest in scenarios where initial emissions from certain sources are not guaranteed.

In most cases, the aforementioned approach should work seamlessly for you. However, when dealing with numerous observables, it might become challenging to distinguish which ones lack initial emissions. For such scenarios, you have the flexibility to set the second parameter to true in combineLatestEager, which appends startWith(null) for all source observables. This option becomes especially valuable when dealing with situations where ensuring initial emissions is crucial.

Example Code

@if (vm$ | async; as vm) {
  <p>
    Today is <time> {{ vm.today | date }} </time>. Who is our today's rock star?
    <button (click)="showRockStar()" class="btn btn-outline">Unveil</button>
  </p>

  @if (vm.rockStarState?.error; as error) {
    <cll-alert [error]="error" />
  }
  @if (vm.rockStarState?.loading) {
    <cll-spinner />
  }
  @if (vm.rockStarState?.data; as rockStar) {
    <p class="!text-xl">{{ rockStar.name }}</p>
  }
}
import {AlertComponent, SpinnerComponent} from 'clr-lift';
import {combineLatestEager, switchMapWithAsyncState} from 'ngx-lift';
import {of, Subject} from 'rxjs';

export class RockStarComponent {
  today$ = of(new Date());

  private showStarAction = new Subject<void>();
  private http = inject(HttpClient);

  rockStarState$ = this.showStarAction.pipe(
    switchMapWithAsyncState(() => this.http.get<{name: string}>('https://jsonplaceholder.typicode.com/users/1')),
  );

  // using RxJS combineLatest won't work because showStarAction won't emit initial value until button click
  vm$ = combineLatestEager({today: this.today$, rockStarState: this.rockStarState$});

  showRockStar() {
    this.showStarAction.next();
  }
}

Today is . Who is our today's rock star?