Search code examples
angularasync-awaitobservablesynchronousangular-httpclient

What is the best way to make synchronous calls using Angular's HttpClient?


I have an array of objects of the following type.

processing-details.model.ts

export class ProcessingDetails{
     id: string;
     isProcessingOccured: boolean;
     isProcessingSuccessful:boolean;
     constructor(id,isProcessingOccured,isProcessingSuccessful) {
          this.id = id;
          this.isProcessingOccured = isProcessingOccured;
          this.isProcessingSuccessful = isProcessingSuccessful;
     }
}

The array is dynamically generated based on certain inputs. Once the array is generated, it looks as follows.

processingArray = [{id:1, isProcessingOccured: false, isProcessingSuccessful: false},
                   {id:2, isProcessingOccured: false, isProcessingSuccessful: false},
                   .....
                   {id:k, isProcessingOccured: false, isProcessingSuccessful: false}]

I have a REST end point which takes an object of type ProcessingDetails. The following is my service code.

automated-batch.service.ts

@Injectable()
export class AutomatedBatchService {
     constructor(@Inject('baseUrl') private baseUrl: string, private httpClient: HttpClient) {}

     public triggerProcessing(batchDetails:ProcessingDetails) {
         return this.httpClient.post(`${this.baseUrl}`,batchDetails);
     }
}

My goal is to call triggerProcessing for each element of processingArray in a synchronous manner. If triggerProcessing is called on one object of processingArray we set isProcessingOccured on that particular object as true. If the back end returns a success for the triggerProcessing call we made on the object, then we set isProcessingSuccessful to true. For example, let us assume processing was successful for id = 1 and id = 2. The array must look like follows:

    processingArray = [{id:1, isProcessingOccured: true, isProcessingSuccessful: true},
                       {id:2, isProcessingOccured: true, isProcessingSuccessful: true},
                       {id: 3, isProcessingOccured: false, isProcessingSuccessful:false }
                       .....
                       {id:k, isProcessingOccured: false, isProcessingSuccessful: false}]

If the processing for one object fails, we must not process the remainder of the array. For example, if processing fails for object {id: 3, isProcessingOccured: false, isProcessingSuccessful:false }, we must not trigger the service call from {id: 4, isProcessingOccured: false, isProcessingSuccessful:false } onwards.

I am currently using async/await to achieve this. Following is my code

processing-test.component.ts

import { Component } from "@angular/core";
@Component({
     selector: 'processing-test',
     templateUrl: './processing-test.component.html',
     styleUrls:['./processing-test.component.css'],
     providers: [AutomatedBatchService]
})
export class ProcessingTestComponent {
     constructor(private automatedBatchService: AutomatedBatchService) { }
     public isSuccess:boolean = true;
     public processingArray: Array<ProcessingDetails>= [];
     async startBatchRun() {
         for( var i = 0; i < this.processingArray.length; i++ ) {
              if(this.isSuccess){
                 await this.automatedBatchService.triggerProcessing(this.processingArray[i])
                 .toPromise()
                 .then(res => {
                     this.processingArray[i].isProcessingOccured = true
                     this.processingArray[i].isProcessingSuccessful = true
                 })
                 .catch(rej => {
                     this.isSuccess = false
                     this.processingArray[i].isProcessingOccured = true
                     this.processingArray[i].isProcessingSuccessful = false
                 });

             }else {
                 break;
             }            
         }
     }
}

Is this the best way to achieve this? Is there any way where I can completely avoid using Promises, async/await and achieve the same synchronous call using Observables?


Solution

  • You could use a combination of concat, catchError and finalize to process your requests in order and stop as soon as an error occurs:

    public process(batch: ProcessingDetails[]) {
      const processes = batch.map(details => this.httpClient.post(`${this.baseUrl}`, batchDetails).pipe(
        map(() => {
          details.isProcessingSuccessful = true;
          return batch;
        }),
        catchError(() => {
          details.isProcessingSuccessful = false;
          return throwError(false);
        }),
        finalize(() => {
          details.isProcessingOccured = true;
        }),
      ));
      return concat(...processes);
    }