Search code examples
rxjsangular8angular-promise

execute a sequence of GET calls to an API wait and do some treatments on the results then give the result as argumant to another methode for a POST


I am new to Angular and i am facing some difficulties with a task. I have an array of IDs that i want to execute the same GET Call over. And for every GET call result i have to do some operations and then add the result of every operation to some arrays. I managed to find a way to do it correctly. But my problem is, i can't manage to wait for the final result to be ready (after all the GET calls are done and the operations too) before giving it as an argument to another method that will send it with a POST call.

the method where i do the GET calls and the operations over every call's result (the problem occurs when i am in the rollBackSPN condition).

async getComponentIds(taskName: String, selectedComponents: IComponent[]) {
    const componentsId: number[] = [];
    const componentsWithoutParams: IComponent[] = [];
    let sendPortaPrecedente : boolean;

    if(taskName == "rollBackSPN"){
      from(selectedComponents).pipe(
        concatMap(component =>{
          return this.http.get<any>("Url"+component.idComponent).pipe(
            tap(val => {
              sendPortaPrecedente = true;

              for(const obj of val){
                if((obj.name == "z0bpqPrevious" && obj.value == null) || (obj.name == "datePortaPrevious" && obj.value == null) || (obj.name == "typePortaPrevious" && obj.value == null)){
                  sendPortaPrecedente = false;
                }
              }

              if(sendPortaPrecedente){
                componentsId.push(component.idComponent);
              }else{
                componentsWithoutParams.push(component);
              }
            }),
            catchError(err => {
              return of(err);
            })
          )
        })
      ).subscribe(val => {
        return { componentsId : componentsId, componentsWithoutParams : componentsWithoutParams, sendPortaPrecedente : sendPortaPrecedente};
      });

    }else{
      for (const component of selectedComponents) {
        componentsId.push(component.idComponent)
        return { componentsId : componentsId, componentsWithoutParams : componentsWithoutParams, sendPortaPrecedente : sendPortaPrecedente};
      }
    }
  }

The method where i pass the getComponentIds(taskName: String, selectedComponents: IComponent[]) result so it can be send with a POST call (again when i am in the rollBackSPN condition)

executeTask(serviceIdSi: string, actionIdSi: string, actionClassName: string, componentName: string, taskName: string,
     componentsId: number[], componentsWithoutParams: IComponent[], sendPortaPrecedente: boolean): Observable<any> {
    
    const url = this.taskUrl + `?serviceId=${serviceIdSi}` + `&actionId=${actionIdSi}` + `&actionClassName=${actionClassName}`
      + `&componentName=${componentName}` + `&taskName=${taskName}`;
    
    
      if(taskName == "rollBackSPN"){

        if(sendPortaPrecedente && componentsWithoutParams.length == 0){
          return this.http.post<any>(url, componentsId);

        }else{

          let errMessage = "Some Error Message"

          for(const component of componentsWithoutParams){
            errMessage = errMessage + component.idComponent +"\n";
          }
          throw throwError(errMessage);

        }

      }else{
        return this.http.post<any>(url, componentsId);

      }
    
  }

Both these methods are defined in a service called TaskService. And the service is called like this in a component UnitTaskButtonsComponent.

async launchUnitTask() {
    this.isLoading = true;
    this.isClosed = false;
    this.appComponent.currentComponentIndex = this.componentIndex;
    let res = await  this.taskService.getComponentIds(this.unitTaskLabel, this.selectedComponents);
      
      
    this.taskService.executeTask(this.appComponent.currentService.identifiantSi,
      this.appComponent.currentAction.identifiantSi,
      this.appComponent.currentAction.className,
      this.selectedComponents[0].name,
      this.unitTaskLabel,
      res.componentsId,
      res.componentsWithoutParams,
      res.sendPortaPrecedente).subscribe(
      data => this.executeTaskSuccess(),
      error => this.executeTaskError());
  }

"res" properties are always undefined when it's a rollBackSPN task.


Solution

  • The main issue here is that getComponentIds does not return a Promise. So awaiting does not work. I would suggest to change getComponentIds so that it returns an Observable instead.

    getComponentIds(taskName: string, selectedComponents: IComponent[]) {
      //                        ^^^^^^ use string instead of String
      return forkJoin(
        selectedComponents.map((component) => {
          return this.http.get<any>("Url" + component.idComponent).pipe(
            map((val) => {
              let sendPortaPrecedente = true;
    
              for (const obj of val) {
                if (
                  (obj.name == "z0bpqPrevious" && obj.value == null) ||
                  (obj.name == "datePortaPrevious" && obj.value == null) ||
                  (obj.name == "typePortaPrevious" && obj.value == null)
                ) {
                  sendPortaPrecedente = false;
                }
              }
    
              return { component, sendPortaPrecedente }
            }),
            catchError((err) => of(err))
          );
        })
      ).pipe(
        map((result) => {
          const componentsId: number[] = [];
          const componentsWithoutParams: IComponent[] = [];
    
          for (const val of result) {
            if (val.sendPortaPrecedente) {
              componentsId.push(val.component.idComponent);
            } else {
              componentsWithoutParams.push(val.component);
            }
          }
          return { componentsId, componentsWithoutParams };
        })
      );
    }
    
    • Instead of using concatMap, let's use a forkJoin. The forkJoin allows sending all requests in parallel and returns the result in an array. But we have to pass in an array of Observables. That's why we map over the selectedComponents.

    • In the lower map, we can now get the complete result of the http calls in the result parameter. Here we do the processing of the data. I was not really sure how to handle the sendPortaPrecedente. You will have to fill that in.

    • We simply return the whole Observable

    async launchUnitTask() {
      this.taskService
        .getComponentIds(this.unitTaskLabel, this.selectedComponents)
        .pipe(
          switchMap((res) => {
            this.taskService
              .executeTask(
                this.appComponent.currentService.identifiantSi,
                this.appComponent.currentAction.identifiantSi,
                this.appComponent.currentAction.className,
                this.selectedComponents[0].name,
                this.unitTaskLabel,
                res.componentsId,
                res.componentsWithoutParams,
                res.sendPortaPrecedente
              )
          })
        ).subscribe(
          (data) => this.executeTaskSuccess(),
          (error) => this.executeTaskError()
        );
    }
    

    In the launchUnitTask method, we don't use await anymore. Instead, we call getComponentIds and chain the call of executeTask with a switchMap.