0

I have the following template:

<ng-container *ngIf="{
    res1: obs1$ | async,
    res2: obs2$ | async
  } as observables; else loader">

the loader template will never be displayed since in the worst case I have an empty object which isn't falsy.

I could have 2 ng-container sequentially but that would mean that the async would be triggered sequentially. Is there any other way to solve this issue?

Scipion
  • 11,449
  • 19
  • 74
  • 139

3 Answers3

2

You could combineLatest to have both of them loaded at the same time.

import {combineLatest} from 'rxjs';

this.combinedObservable$ = combineLatest(
  obs1$,
  obs2$
)
<ng-container *ngIf="(combinedObservable$ | async) as observables; else loader">
   {{observables[0] | json}} // Result from obs1
   {{observables[1] | json}} // Result from obs2
</ng-container>
Guilhermevrs
  • 2,094
  • 15
  • 15
  • The answer needs to account for the fact that `combineLatest` wouldn't start emitting unless all the source observables have emitted atleast once using `startWith` operator. – ruth Sep 21 '20 at 12:07
  • Correct, `combineLatest` won't emit, but as far as I understood the question, OP doesn't want to display anything but the loader until both observables have returned. So I don't see the place for `startWith` here – Guilhermevrs Sep 21 '20 at 12:17
  • The issue is even if one of the source emits (eg. `obs1$`) a valid object the `#loader` block would still be shown since the `combinedObservable$` hasn't emitted yet. So one of the observable could potentially continue to emit valid notifications but it wouldn't be used unless the others emit as well. – ruth Sep 21 '20 at 12:19
  • Ok, I see your point and it seems to be a question of interpretation. I understood that OP wants to remove the loader once both observables have emitted (hence, `combineLatest`). It seems to be the same understanding from @MoxxiManagarm You understood that OP wants to remove once at least one of the observables have emitted. In this case, `combineLatest` is not, indeed, the way to go. – Guilhermevrs Sep 21 '20 at 12:23
2

Yes this is not possible, the object will never be falsy.

You have 2 options.

1st: Use nested ngIf

<ng-container *ngIf="{
    res1: obs1$ | async,
    res2: obs2$ | async
  } as observables">
  <ng-container *ngIf="observables.res1 && observables.res2; else loader">

2nd: combine the observables in component

observables$ = forkJoin([obs1$, obs2$]).pipe(
  map(([obs1, obs2]) => ({ res1: obs1, res2: obs2})),
);

<ng-container *ngIf="observables$ | async as observables; else loader">
MoxxiManagarm
  • 8,735
  • 3
  • 14
  • 43
0

You're question isn't clear at the moment, but you could try to introduce a common observable in the controller using RxJS merge function. You could also use RxJS map operator to return boolean false if the object is empty.

Try the following

import { merge, Observable } from 'rxjs';
import { startWith } from 'rxjs/operators';

export someComponent implements OnInit {
  combined$: Observable<any>;

  ngOnInit() {
    // Credit: https://stackoverflow.com/a/32108184/6513921
    emptyObj = (obj) => (Object.keys(obj).length === 0 && obj.constructor === Object);

    this.combined$ = merge(
      this.obs1$.pipe(map(obj => emptyObj(obj) ? false : obj)),
      this.obs2$.pipe(map(obj => emptyObj(obj) ? false : obj))
    );
  }
}

Template

<ng-container *ngIf="(combined$ | async) as observable; else loader">
  ...
</ng-container>
ruth
  • 29,535
  • 4
  • 30
  • 57