Search code examples
javascripthtmlangularrxjs

Angular: ng-container not displayed


I have an ng-container that must show in case some observable has a value, and if not, a ng-template must show:

  <ng-container *ngIf="obs$ | async; else template">
    <router-outlet></router-outlet>
  </ng-container>
  <ng-template #template>
    <div>
      No data
    </div>
  </ng-template>

However when I log the value of the observable after subscribing to it, I see that it actually has a value, so I don't understand what the issue is here.

Here is how the obs$ observable is set:

  obs$: Observable<Promise<SomeClass | null>> =
    this.obs2$.pipe(
      map(async (foo: SomeOtherClass | null) => {
        if (!foo) {
          return null;
        }
        const { id } = foo;
        const bar = await this.someService.getBarById(
          id
        );
        return bar;
      })
    );


Solution

  • You need to use switchMap instead of going for map with async and await, it basically does the same thing.

    We initially set the value of null which is made a observable by using of, then we use the switchMap operator, what it does is, it takes the outer observable and waits for the inner one to complete, before completing the sequence. Until then the condition at *ngIf will evaluate to false, since the inner value of the observable is checked by the *ngIf after getting resolved by the async pipe, since its falsy the template will be shown until the value becomes true, which happens after the switchMap completes!

      obs$ = of(null).pipe(
        switchMap(() => {
          // replace below line with: return this.someService.getBarById(id);
          return of(true).pipe(delay(5000)); // simulates an API call!
        })
      );
    

    FULL CODE:

    import { Component } from '@angular/core';
    import { provideRouter, RouterOutlet } from '@angular/router';
    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    import { CommonModule } from '@angular/common';
    import { ChildComponent } from './app/child/child.component';
    import { map, timeout, delay } from 'rxjs/operators';
    import { of, interval, switchMap } from 'rxjs';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [RouterOutlet, CommonModule],
      template: `
        <ng-container *ngIf="obs$ | async; else template">
          <router-outlet></router-outlet>
        </ng-container>
        <ng-template #template>
          <div>
            No data
          </div>
        </ng-template>
      `,
    })
    export class App {
      name = 'Angular';
      obs$ = of(null).pipe(
        switchMap(() => {
          return of(true).pipe(delay(5000));
        })
      );
    }
    
    bootstrapApplication(App, {
      providers: [
        provideRouter([
          {
            path: 'default',
            component: ChildComponent,
          },
          {
            path: '**',
            redirectTo: 'default',
            pathMatch: 'full',
          },
        ]),
      ],
    });
    

    Stackblitz Demo