3

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.

xkeshav
  • 53,360
  • 44
  • 177
  • 245
  • 1
    https://twitter.com/yurzui/status/1057167356626132992 – yurzui Oct 30 '18 at 07:10
  • @yurzui this is something what I am looking for. Thank you. Interestingly it works on negative condition only – xkeshav Oct 30 '18 at 08:59
  • check this , it only works perfectly when condition hs negation .https://ng-run.com/edit/ddqs9cmO4Qq8E8xlg39r?layout=2&open=app – xkeshav Oct 30 '18 at 09:08
  • in HTML `` and in directive `@Input() set ngIf(condition: boolean) { if (condition) {}` does not work perfectly but adding negation (!) on both HTML and directive make it work perfectly. why so? – xkeshav Oct 30 '18 at 09:13
  • That's how ngIf directive works. Check the sources https://github.com/angular/angular/blob/ede65dbede75b89ef759df780fadb6c2769d1133/packages/common/src/directives/ng_if.ts#L106 – yurzui Oct 30 '18 at 09:16
  • @yurzui will you please post an answer so that I can accept it. – xkeshav Oct 31 '18 at 04:12

5 Answers5

5

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

yurzui
  • 205,937
  • 32
  • 433
  • 399
1

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.

ferhado
  • 2,363
  • 2
  • 12
  • 35
  • doing this approach we again writing the same template code into each HTML page which I do not want to – xkeshav Oct 30 '18 at 05:59
  • 1
    Are you using Routing? – ferhado Oct 30 '18 at 06:24
  • what is the role of routing here? – xkeshav Oct 30 '18 at 08:32
  • @diEcho if your are using routing there is an other solution. – ferhado Oct 30 '18 at 21:42
  • Thanks for the snippet but I already implement the same logic on page change. Here I need the API response waiting spinner on same page. – xkeshav Oct 31 '18 at 02:11
  • This will wait for the api response, It will be ended when the html completely loaded. I have this timeout, because I don't have any response, but you can remove it. Alternative you can use a service to connet to api, and each time you call the service make this variable false and make it true again in success. – ferhado Oct 31 '18 at 07:27
0

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]
Torab Shaikh
  • 456
  • 1
  • 5
  • 17
  • by doing this ; I need to restructure my all forms and wrap over ``. I am using ng-container and nee to use in `*ngif` – xkeshav Oct 30 '18 at 06:01
0
<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()

Abidh
  • 457
  • 2
  • 5
  • 13
-1

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.

Dipal
  • 184
  • 10