2

This seems like it would be simple but I cannot find anything to address it. What I'm trying to do is to have a CSS spinner/loader while an asynchronous list loads. I've tried all kinds of things including trying to use ngSwitch, but so far nothing works and the spinner just spins indefinitely. Here is the base code:

<div class="col-md-2 mediaContainer">
  Media:
  <ul class="media">
    <li *ngFor="let medium of media | async"
        [class.selected] = "medium === selectedMedium"
        (click)="onSelect(medium)">
      {{medium.name}}
    </li>
  </ul>
</div>

While I've tried lots of things the following is an example that will not work:

<div class="col-md-2 mediaContainer">
  Media:
  <ul class="media" [ngSwitch]="status">
    <li *ngSwitchCase="loading"><loaders-css [loader]="'square-spin'" [loaderClass]="'my-loader'"></loaders-css></li>
    <li *ngFor="let medium of media | async"
        [class.selected] = "medium === selectedMedium"
        (click)="onSelect(medium)">
      {{medium.name}}
    </li>
  </ul>
</div>

I've also put the ngSwitch and/or the switch cases inside the list but that doesn't work either. I have a feeling that 'status' only works with the elements themselves not their content, but I'm not sure how to set a switch value either (ie https://angular.io/docs/ts/latest/api/common/index/NgSwitch-directive.html) and this seems like something to do with a promise possibly. Any ideas? Given the nature of what I'm doing it seems like it would be pretty common...

2
  • See: stackoverflow.com/questions/38144655/… Commented Dec 2, 2016 at 4:41
  • <li *ngSwitchCase="loading"> replace it with <li *ngIf="loading"> and keep loading as boolean.. if its value become true it will run and if its value is false it will not excute Commented Dec 2, 2016 at 5:18

2 Answers 2

4

You can use the *ngIf directive to control the UI rendering logic , and use state variables to control the completion of request. Here is a sample.

on your component

        private loadingComplete = false;
        private isLoading = true ;
        private error;
        private media[];
        constructor(private mediaService:MediaService) {
            this.mediaService.getMedia()
                             .subscribe(
                                res =>{
                                    this.media = res;
                                    this.isLoading = false;
                                    this.loadingComplete = true;
                                },
                                err => {
                                    this.loadingComplete = true;
                                    this.error = err;
                                });
                        }

                isEmptyResult(){
                    //N.B assuming ther server returns empty array if no media found.
                    return (!this.isLoading && !this.error && this.loadingComplete && this.media && this.media.length===0);
                }
                mediaAvailable(){
                    return (!this.isLoading && !this.error && this.loadingComplete && this.media && this.media.length>0);
                }

on your template

 <div class="col-md-2 mediaContainer">
  Media:

    <li *ngIf="isLoading">
        <loaders-css [loader]="'square-spin'" [loaderClass]="'my-loader'">
        </loaders-css>
    </li>

    <p *ngIf='error'>
        Network Error.
    </p>   

    <p *ngIf='isEmptyResult()>
        No media found.
    </p>

    <ul *ngIf="mediaAvailable()" class="media" >
        <li *ngFor="let medium of media"
            [class.selected] = "medium === selectedMedium"
            (click)="onSelect(medium)">
        {{medium.name}}
        </li>
    </ul>
</div>
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks @Mehari! That worked perfect, saved me days probably, and I'm sure it will help many others. Also I think this reminds me that I should put as much logic in the components themselves as possible.
@jason glad I helped.
I have the same situation as above and i have implemented it and it works fine. If i have empty results what is the condition that i need to give?@Mehari
why use async in the template if you are subscribing in the controller?.. is that really needed?
@Mackelito you right, updated accordingly. The intention was to clarify things for the original question hence it uses async pipe.
|
4

I have tried showing spinner till data is available.

Please look into the following plunker.

https://plnkr.co/edit/4kSximI2a2l6SWVzqsyl?p=preview

Concerned code:-

HTML:

  <ul  >
       <div class="loader" [hidden]="myVar"></div>
        <li *ngFor="let medium of media "

        (click)="onSelect(medium)">
       {{medium.name}}
    </li>
 </ul>

CSS:-

/* Styles go here */

       .loader {
            border: 16px solid #f3f3f3; /* Light grey */
            border-top: 16px solid #3498db; /* Blue */
            border-radius: 50%;
            width: 120px;
            height: 120px;
            animation: spin 2s linear infinite;
        }

       @keyframes spin {
          0% { transform: rotate(0deg); }
         100% { transform: rotate(360deg); }
       }

Controller

          import {Component, NgModule} from '@angular/core'
          import {BrowserModule} from '@angular/platform-browser'

          export class Media {
            id: number;
           name: string;
          }

           @Component({
             selector: 'my-app',
            template: `
            <div>
             <h2>Hello {{name}}</h2>


           </div>

           <div class="col-md-2 mediaContainer">
              Media: 
           <ul  >
          <div class="loader" [hidden]="myVar"></div>
           <li *ngFor="let medium of media "

          (click)="onSelect(medium)">
           {{medium.name}}
         </li>
        </ul>
    </div>
    `,
   })
   export class App {
    name:string;
    myVar: boolean;
    media: Media[];
    constructor() {
    this.name = 'Angular2';


   setTimeout(() => {
      this.title = "Brave New World";
      this.myVar = true;

      this.media  =  [
      { id: 11, name: 'Mr. Nice' },
      { id: 12, name: 'Narco' },
      { id: 13, name: 'Bombasto' },
      { id: 14, name: 'Celeritas' },
      { id: 15, name: 'Magneta' },
      { id: 16, name: 'RubberMan' },
      { id: 17, name: 'Dynama' },
      { id: 18, name: 'Dr IQ' },
      { id: 19, name: 'Magma' },
      { id: 20, name: 'Tornado' }
      ];

       }, 2000);)
      }
    }

    @NgModule({
        imports: [ BrowserModule ],
        declarations: [ App ],
        bootstrap: [ App ]
    })
    export class AppModule {}

Please let me know in case of any query .

2 Comments

Please let me know in case of any query
Downvoted because: 1. The async pipe is for Observables. You haven't used one. 2. You haven't used the @Component decorator. 3. Your "external" CSS and HTML are useless. You don't declare the CSS in the component and the HTML is inlined. 4. You have a Media component but declared an App component. 5. myVar for a flag? 6. Indentation is important.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.