DarkIudeX
DarkIudeX

Reputation: 105

Loading spinner with *ngIf not showing during execution of function

If trying to show a loading spinner while a function is being executed. I already tried different things, but the spinner won't show.

This is the HTML:

<div class="row pt-3" id="firstRow">
 <div class="col">
  <button class="btn btn-dark back-button">
   <fa name="upload"></fa>
   <span>Load .csv file</span>
  </button>
  <input type="file" #fileImportInput name="File Upload" class="txtFileUpload p-3" (change)="fileChangeListener($event)" accept=".csv" />
    <img class="spinner" *ngIf="loading" src="../../../assets/img/gif-spinner/Spin-2s-200px.gif" />
 </div>
</div>

This is part of the TS-code

export class MotionAnalysisComponent implements OnInit {
loading = false;

 fileChangeListener($event: any): void {
   this.loading = true;

   let files = $event.srcElement.files;

   if (this.isCSVFile(files[0])) {
    let input = $event.target;
    let reader = new FileReader();
    reader.readAsText(input.files[0]);

    reader.onload = () => {
     let csvData = reader.result;
     let csvRecordsArray = (<string>csvData).split(/\r\n|\n/);
     this.csvRecords = this.getDataRecordsArrayFromCSVFile(csvRecordsArray, 4);
   };

   reader.onerror = function () {
    alert('Unable to read ' + input.files[0]);
   };
  } else {
    alert("Please import valid .csv file.");
    this.fileReset();
  }

   this.loading = false;

If I comment the "this.loading = false" out within the function, then the spinner will show AFTER the function is executed and will (obviously) not disappear. So how can I make the HTML understand, that the spinner should be shown during the execution of the function?

Upvotes: 1

Views: 5552

Answers (4)

StepUp
StepUp

Reputation: 38094

  1. It seems that it is necessary to see debug information. So show value of loading in your HTML:

    Load .csv file

     <p>loading {{ loading  }} </p>
    
     <img class="spinner" *ngIf="loading" 
        src="../../../assets/img/gif-spinner/Spin-2s-200px.gif" />
    </div>
    

If you see, that value of loading updates correctly, then try to set style:

img { 
     width: inherit; 
     height: inherit; 
     position: fixed; 
     top:0; 
     left:0; 
}
  1. Is it typo? Your div tag ends incorrectly: /div>

UPDATE:

Try to set src of image by template expression:

HTML:

<img class="spinner" 
     *ngIf="loading" 
     [src]="imagePath"/>

TypeScript:

imagePath = '../../../assets/img/gif-spinner/Spin-2s-200px.gif;'

Upvotes: 0

Martin Parenteau
Martin Parenteau

Reputation: 73731

To allow time for the spinner to initialize, you can run the code asynchronously after setting loading to true. This can be achieved by moving the code inside of a setTimeout callback. The loading property should be reset at the end of that callback method.

fileChangeListener($event) {
  this.loading = true;                    // First, set loading property
  setTimeout(() => {                      // Call setTimeout to run code asynchronously
    let files = $event.srcElement.files;
    if (this.isCSVFile(files[0])) {
      ...
    } else {
      ...
    }
    this.loading = false;                 // Reset loading before leaving callback method
  }, 0);
}

See this stackblitz for a demo. Please note that you could increase the setTimeout delay, if necessary (it is set to 0 ms in the example above).

Upvotes: 1

Billy Koswara
Billy Koswara

Reputation: 497

@Component({
  selector: 'm-selector',
  templateUrl: './selector.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush, <-- add this line
  styleUrls: ['./selector.component.scss']
})

add a change detection method

then in your constructor

constuctor(
...,
private ref: ChangeDetectorRef <-- add this line
){ }

this make you have an access to ask angular to check changes manually

then you can use it, for example:

this.loading = true
this.ref.markForCheck()

call the ref.markForCheck everytime you change a variable that affect your html with *ngIf

this way you will consume less memory as you do not keep listening to a certain variable

but if your variable often changes overtime then it is recommended to use listener

Upvotes: 0

Adrian Brand
Adrian Brand

Reputation: 21638

My guess is that

... other part of the function...

is a call to an observable or an async function so your

this.loading = false;

is called straight away.

Without knowing exactly what is in that block of code I can't tell but most likely you need a callback function the executes once the async call has finished to set loading to false.

Upvotes: 0

Related Questions