How to point other components in the else part of *ngIf

Viewed 4017

With extension of my previous question, here are another scenario where I need to use a template in else part of *ngIf

Here is my current code where I am using loading spinner on each page until API response returns

   <ng-container *ngIf="!isLoading; else spinner">
         <form [formGroup]="myForm" novalidate (ngSubmit)="submit()" >
            // form content 
         </form>
    </ng-container>
    <ng-template #spinner>
        <div class="fa-3x tc">
            <i class="fas fa-spinner fa-spin"></i>
        </div>
    </ng-template>

this way we have to write the same #spinner component on every page so I have created a component with the same content.

import { Component } from '@angular/core';

@Component({
    selector: 'app-display-loading',
    styles: [`.load__spinner { color: black; text-align: center; font-size: 30px;}`],
    template: `
        <ng-template>
            <div class="load__spinner"> <i class="fas fa-spinner fa-spin"></i></div>
        </ng-template>
    `,
    preserveWhitespaces: true
})

export class DisplayLoadingComponent {
    constructor() {
        console.log('display loading called');
    }
}

Now my question is how to use this <app-display-loading> component within *ngIf

or this may be not the right way. Kindly suggest how to do this.

5 Answers

Angular structural directive can help us to point other component in the else part of ngIf directive.

All you need to do is to just create directive that will supplement built-in ngIf directive.

The final syntax should look like:

<div *ngIf="model withLoading">
  Model
</div>

which is just a sugar for:

<ng-template [ngIf]="model" [ngIfWithLoading]="null">
  <div>
      Model
  </div>
</ng-template>

And here is directive itself:

@Directive({
  selector: '[ngIfWithLoading]',
})
export class LoadingDirective {
  @Input() set ngIf(val: any) {
    if (!val) {
      const factory = this.resolver.resolveComponentFactory(LoadingComponent);
      this.vcRef.createComponent(factory)
    }
  };

  constructor(private vcRef: ViewContainerRef, private resolver: ComponentFactoryResolver) { }
}

So as soon as model is false the directive above will place your LoadingComponent instead of model template.

Don't forget to include LoadingComponent into entryComponents array

Ng-run Example

See also

You can do that using ng-template like:

<div *ngIf="test; else otherTest"></div>
<ng-template #otherTest>
  <app-display-loading></app-display-loading>
</ng-template>

Update: If your are using Routing then this example is useful for you:

in your AppComponent you can subscribe navigation changes when NavigationStart show your spinner, when NavigationEnd hide the spinner.

AppModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { NavbarComponent } from './navigation/navbar/navbar';
import { HomeComponent } from './views/home/home';
import { CategoriesComponent } from './views/categories/categories';

const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'categories', component: CategoriesComponent }
];


@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    RouterModule.forRoot(routes)
  ],
  declarations: [
    AppComponent,
    NavbarComponent,
    HomeComponent,
    CategoriesComponent
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

AppComponent:

import { Component } from '@angular/core';
import { Router, NavigationStart, Event, NavigationEnd } from '@angular/router';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html'
})


export class AppComponent {
  timeout;
  routerChanged = true;
  constructor(private router: Router) {
    router.events.subscribe((event: Event) => {

      if (event instanceof NavigationStart) {
        // Show spinner
        this.routerChanged = true;
      }

      if (event instanceof NavigationEnd) {
        // Hide spinner
        this.timeout = setTimeout(() => {
          clearTimeout(this.timeout);
          this.routerChanged = false;
        }, 1000);
      }
    });
  }
}

AppComponent Html:

<app-navbar></app-navbar>
<router-outlet></router-outlet>
<div class="spinner" *ngIf="routerChanged"></div>

Here is a worked example:

And you don't need any more to repeat the same spinner html in all your pages.

I am using something similar to <app-display-loading> all over the project and all I need to do is

<app-display-loading *ngIf="isLoading"></app-display-loading>

but first you need to add DisplayLoadingComponent class in the module in which the component is located like this

exports = [DisplayLoadingComponent]
declarations = [DisplayLoadingComponent]
<ng-container *ngIf="!isLoading">
     <form [formGroup]="myForm" novalidate (ngSubmit)="submit()" >
        // form content 
     </form>
</ng-container>

<app-display-loading *ngIf="isLoading"></app-display-loading>

set isLoading accordingly.and, set prevent default inside submit()

Hi you can use HTTP interceptor for displaying spinner in every request.

You can check this. Might be helpful for you. and for conditioning the spinner you can use variable from common service.

Or you can use your loading component on higher level and by CSS you can show loading on whole page so you don't need else condition part.

Related