0

I have a basic Angular component as follows. From this component, I generate an array of objects of type TestObj and use the same array to make synchronous post-call for all elements of under the condition that if kth (0 <= k <= x.length) element fails to process, the remainder of the array is not processed.

processing-component.component.ts

import { Component, OnInit } from "@angular/core";
import { FormBuilder, Form, FormGroup } from "@angular/forms";
import { Router } from "@angular/router";
import { processingService } from "./processing-component.service";
import { catchError, finalize,  map } from "rxjs/operators";
import { concat } from "rxjs/observable/concat";

import { Observable, BehaviorSubject } from "rxjs";
export class TestObj {
    processedFor:string;
    isProcessingHappened: boolean ;
    isProcessingSuccess: boolean;
    constructor(isProcessingHappened,isProcessingSuccess) {
        this.isProcessingHappened = isProcessingHappened;
        this.isProcessingSuccess = isProcessingSuccess;
    }
}

@Component({
    selector: 'processing-component',
    templateUrl: './processing-component.component.html',
    styleUrls:['./processing-component.component.scss'],
    providers: [ProcessingService]
})
export class ProcessingComponent {

    constructor(private processingService: ProcessingService) {    }    

//some array generation logic to fetch arrayToProcess

    public arrayToProcess: Array<TestObj>= [];
    public displayedColumns: string[] = ['processedFor',  'isProcessingHappened', 'isProcessingSuccess']

    public startBatchRun() {            
        const processes = this.arrayToProcess.map(details => this.processingService.runBatchExperiment(details).pipe(
          map(() => {
            this.isLoading.next(true);   
            details.isProcessingSuccess = true;
            return this.arrayToProcess;
          }),
          catchError(() => {
            this.isLoading.next(false);
            details.isProcessingSuccess = false;
            throw(false);
          }),
          finalize(() => {
            this.isLoading.next(false);
            details.isProcessingHappened = true;
          }),
        ));
        concat(...processes)
        .map(() => this.isLoading = true)
        .subscribe(res => {

        })
    }
}

My service looks as follows

processing-component.service.ts

import { Injectable, Inject } from "@angular/core";
import { Http } from "@angular/http";

@Injectable()
export class ProcessingService {

    constructor(@Inject('portfolioBaseUrl') private portfolioBaseUrl: string, 
    private http: Http) {}

    processingUrl = this.portfolioBaseUrl + '/process/'

    public runBatchExperiment(processingObj:TestObj ) {
        return this.http.post(`${this.processingUrl}`,processingObj);
    }

}

My template is as follows

processing-component.component.html

<button *ngIf = "arrayToProcess.length > 0" mat-raised-button  (click) = "startBatchRun()" >Start Processing</button>   

<div *ngIf = "arrayToProcess.length > 0" >
    <mat-table [dataSource] = "arrayToProcess" >

        <ng-container matColumnDef="processedFor">
            <mat-header-cell *matHeaderCellDef> ID</mat-header-cell>
            <mat-cell *matCellDef="let element"> {{element.processedFor }} </mat-cell> <!--add pipe here-->
        </ng-container>


        <ng-container matColumnDef="isProcessingHappened">
            <mat-header-cell *matHeaderCellDef> Proceesing happened </mat-header-cell>
            <mat-cell *matCellDef="let element">  
                <mat-icon *ngIf = "element.isProcessingHappened === true" >done</mat-icon>
                <mat-icon *ngIf = "element.isProcessingHappened === false" >error_outline</mat-icon>
            </mat-cell>
        </ng-container>


        <ng-container matColumnDef="isProcessingSuccess">
            <mat-header-cell *matHeaderCellDef> Batch success </mat-header-cell>
            <mat-cell *matCellDef="let element"> 
                <mat-icon *ngIf = "element.isProcessingSuccess === true" >done</mat-icon>
                <mat-icon *ngIf = "element.isProcessingSuccess === false" >error_outline</mat-icon>
            </mat-cell>
        </ng-container>

        <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
        <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
    </mat-table>
</div>

What additionally I want to do is display a progress spinner which gets displayed when the post-call goes for an object of type TestObj and gets hidden when we get a response from the back-end for the particular object. This progress spinner must disable the UI screen completely.

I tried using an interceptor service to achieve the same. The problem with that is the interceptor displays the progress spinner for all the calls made by the entire front end and what I am trying to achieve is to do the same, but only for the http calls made by ProcessingComponent alone. I tried to take a local variable isLoading and set it to false in the constructor of ProcessingComponent and on the execution of startBatchRun, I set isLoading to true and set the same to false on the exit of startBatchRun. Is there any way that I can achieve the functionality from the observable pipe?

  • Does this answer your question? [Angular 2 Material Progress Spinner: display as overlay](https://stackoverflow.com/questions/42963444/angular-2-material-progress-spinner-display-as-overlay) – Mustahsan Feb 28 '20 at 08:20
  • Which bit of this is the problem you want help with? 1. How to conditionally show loading spinner 2. How to style a loading spinner? – Kurt Hamilton Feb 28 '20 at 08:23
  • @KurtHamilton i need help to toggle a local variable ( isLoading in this case) and use this to display the progress spinner. I want to set isLoading to true when the post call goes and set it to false when i get a response from the back end. I'm trying to do this from inside the observable. In succint, I want to conditionally show the spinner – chaitanya guruprasad Feb 28 '20 at 08:26
  • @chaitanyaguruprasad So the process is something like: 1. Start. loading = true. 2. Call an observable for each item in the array. 3. When all observables have returned a result, loading = false? – Kurt Hamilton Feb 28 '20 at 08:30
  • @KurtHamilton yes. Your understanding is right. The catch here is that the http calls are going synchronously. Until the first observable returns, the second wont be pushed. When the first is pushed, loading = true and when the first returns, loading = false. Then again when second is pushed, loading = true and so on – chaitanya guruprasad Feb 28 '20 at 08:36
  • @chaitanyaguruprasad But how much time do you expect there will be between each call? Surely there will be no time at all - why set loading to false for milliseconds between each call? – Kurt Hamilton Feb 28 '20 at 08:41
  • @KurtHamilton I have observed that on an average there is a 3 seconds delay in the request and the response. Thats the primary reason why I am trying to use a progress spinner. – chaitanya guruprasad Feb 28 '20 at 08:43
  • @chaitanyaguruprasad 3 seconds for all requests, or each item in the array? Can you please provide a stackblitz to demonstrate your problem. I'm not understanding what you want to achieve. If you can't recreate it using RxJS, then use setTimeout or some other method to display the desired effect – Kurt Hamilton Feb 28 '20 at 09:01

1 Answers1

0

You should take a look at the implementation described in the Angular Material documentation, under the Data table examples, specifically, the "Table retrieving data through HTTP" example.

Relevant code snippet:

displayedColumns: string[] = ['created', 'state', 'number', 'title'];
data: GithubIssue[] = [];
isLoadingResults = true;

...

ngAfterViewInit() {

  ...

  merge(this.sort.sortChange, this.paginator.page)
    .pipe(
      startWith({}),
      switchMap(() => {
        this.isLoadingResults = true;
        return this.exampleDatabase!.getRepoIssues(this.sort.active, this.sort.direction, this.paginator.pageIndex);
      }),
      map(data => {

        ...

        this.isLoadingResults = false;

        ...

        return data.items;
      }),
      catchError(() => {

        ...

        return observableOf([]);
      })
    ).subscribe(data => this.data = data);
}

This way, you only need a local boolean variable, and hide/show the spinner according to the state of the variable.

In case you want to have a debounce on your requests (so it won't show up every time, even for requests that last 100ms), you can add a timer in the switchMap() like so:

...

switchMap(() => {          
    const result$ = this.exampleDatabase!.getRepoIssues(this.sort.active, this.sort.direction, this.paginator.pageIndex);
    timer(100).pipe(takeUntil(result$)).subscribe(t => this.isLoadingResults = true);
}),

...
heunetik
  • 567
  • 1
  • 8
  • 21