Search code examples
angulartypescriptrxjs

Angular condition to show spinner before subscribe Observable in HTML


im working Angular 15 project, i have a table where I want to show a loading spinner in a column before getting data from observable.

<tr *ngFor="let course of courseItems$ | async">
  <td>{{course.id}}</td>
  <td>{{course.name}}</td>
  <td>
    <ng-container *ngIf="condition to show only before getting data from course.additionalInfo$">
      Loading ...
    </ng-container>
    <ng-container *ngIf="(course.additionalInfo$ | async) as additionalInfo">
      {{additionalInfo.description}}
    </ng-container>
  </td>
</tr>

do you have any idea how can catch the moment before data subscription ?


Solution

  • Using *ngIf conditional template rendering.

    Following code is not optimized. Use with CAUTION.

    <tr *ngFor="let course of courseItems$ | async">
      <td>{{course.id}}</td>
      <td>{{course.name}}</td>
      <td *ngIf="
           course.additionalInfo$ | async as additionalInfo;
           then courseInfoTemplate
           else loadingTemplate
      ">
    
        <ng-template #courseInfoTemplate>
          {{additionalInfo.description}}
        </ng-template>
    
        <ng-template #loadingTemplate>
          Loading ...
        </ng-template>
      </td>
    </tr>
    
    

    *ngIf directive can be used to switch between conditional templates.

    Step-by-step

    Syntax: ngIf="condition; then trueTemplate else falseTemplate" switching between trueTemplate and falseTemplate. | Detailed Docs

    1. Angular converts course.additionalInfo$ to Observable that will live with current Template lifecycle.
    2. *ngIf will load the corresponding template as content of the directive element.

    UPDATE 1 [Good practice] - Optimized *ngFor performance and readability / code maintainability

    <ng-template> Elements defined into *ngFor loops may provide memory leak or performance issues and isn't recommended! see note - |1|

    <tr *ngFor="let course of courseItems$ | async">
      <td *ngIf="
        course.additionalInfo$ | async as additionalInfo;
        then courseInfoTemplate
        else loadingTemplate
      ">
      </td>
    </tr>
    
    <ng-container>
        <ng-template #courseInfoTemplate let-additionalInfo>
          {{additionalInfo.description}}
        </ng-template>
    
        <ng-template #loadingTemplate>
          Loading ...
        </ng-template>
    </ng-container>
    
    

    In short: We have seperated the templates from the loop *ngFor. We added let-additionalInfo attribute to the <ng-template> element. *ngIf will inject the resolved data as template variable (visible in the template scope). We named that variable: additionalInfo.

    Explanation for geeks:

    Since we moved the <ng-template> elmeents out of the scope(context) we cannot access additionalInfo directly. So we need to decode the
    Structural directive syntax (angular.io) Structural directive syntax After 10 minutes of stairing we begin to wonder whether we should seek an example from another 3rd-party source. Syntax explanation

    NgIf may inject an context(data) to the if/else templateRefs.

    Rare Magic Artefacts Hidden behavior or magic effects
    as keyword Define new named local variable. Example: *ngIf="expressionToEvaluate as expResult" will create local variable expResult visible within directive element scope.
    as keyword with additional condition operators books$ | async as books && booksListEnabled then listTemplate else emptyTemplate will create local variable books, but then emptyTemplate will be rendered.
    then and else NgIf API docs ngIfThen / ngIfElse are @Input()s promerties of NgIf Directive. Regarding to the official API - They are TemplateRef and inject NgIfContext to the EmbeddedViews of the templates. NgIfContext = expResult (see row |1|) In most cases NgIf context is injected using $implicit - that means the data is will be injected queitly into the template. And ONLY if we announce we will use it with let- key definition. (see next row)
    <ng-template let-blah>... let- key define a local variable for the template. In this example let-blah will define blah local variable. NgIf will inject the books object into blah local variable. However Instead of using books[0].title we have to access it as blah[0].title. Note: Usually we want to stay sync and use let-books for the context.

    Notes:
    Note |1|: Vary on different ng versions, angular compilator/cli versions or project configurations. Try to avoid this bad practice!