0
votes

I am working with Angular/rxjs 6. I am having a hard time trying to get an observable sequence to run correctly. Here is the pseudo code for what I am trying to accomplish.

Receive a request to change systems:

  1. Check permissions
  2. Get API1 URL from another observable
  3. Call API1 to request a system change
  4. If there was a problem quit to let user know
  5. Call API2 to request a system change
  6. If there was a problem quit to let user know
  7. Save results
  8. Navigate to new system

I am not sure if my current approach is correct. It does not seem elegant because there is a lot going on and it is hard to follow. Some of this works but the response from the last http call does not return to the .toPromise().then().

Component:

changeSystem(systemID: string) {
   if(! this._service.changeSystem(systemID))
      this.toastError("A problem occurred");
}

Service:

public changeSystem(systemID: string): boolean {
   if (!systemID) {
      this.log("System ID is missing");
      return false;
   }

   if(this.currentSystemID === systemID) {
      return true;
   } else {
      this.setNewSystem(systemID).toPromise()
                                 .then(success => {  // <-- the code here never runs
         if (success)
            this.router.navigate(['/dashboard/', systemID])
         else
            return false;
      }
   }
}

private setNewSystem(systemID: string): Observable<boolean> {
   if (!this.isAuthorized(systemID)) {
      this.log('system is not authorized for user');
      return Observable.of(false);
    }

    return this.setAlphaSystem(systemID).pipe(
      concatMap(alphaResult => {
        if (!alphaResult) {
          this.log('failed to set alpha system');
          return Observable.of(false);
        } else {
          return this.setBetaSystem(systemID);
        }
    }));
}

private setAlphaSystem(systemID: string): Observable<boolean> {    
   return this.alphaSystemRootUrl$.pipe( 
      concatMap(alphaUrl => {
        const url = alphaUrl + 'pages/' + systemID + '/Home/ChangeSystem/';
        return this.http.get(url, { headers: { 'Anonymous': 'true' }, withCredentials: true })
          .map(data => {
            let response = JSON.parse(JSON.stringify(data));
            if (response.error) {
              return false;
            } else {
              return true;
            }
          }).catch(err => {
            this.log('Failed change system request on alpha: ' + JSON.stringify(err));
            return Observable.of(false);
          });
   }));
}

private setBetaSystem(systemID: string): Observable<boolean> {
    return this.changeBetaSystem(systemID).map(systemData => {
      this.saveSystemData(systemData);
      return true;
    }).catch((err: ErrorInfo) => {
      this.log('Failed change system request on beta: ' + err.message);
      return Observable.of(false);
    });
}

private changeBetaSystem(systemID: string): Observable<SystemData> {
    const json = JSON.stringify(systemID);
    return this.http.post<SystemData>("api/System/ChangeSystem", json, this.httpOptions)
      .pipe(catchError(new ErrorInfo().parseObservableResponseError));
}

Do I need a switchmap in setBetaSystem instead of map?

I saw some examples where the component subscribed to a behavior subject that the service functions updated as the processing continued. Should I try that?

Any help is much appreciated!

1

1 Answers

0
votes

After a whole lot of trial and error I got a working solution using another answer as an idea to keep track of the result through a subject.

https://stackoverflow.com/a/60670835/738690

It is not very pretty but it at least it works.

import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; 
import { map } from 'rxjs/operators';

class service {

/*
WORKING mock http observables:
private changeSystemOnAlpha = of({"success":"true"});
private changeSystemOnBeta = of({"name":"system1", "url":"http://goferit"});


ERROR mock http observables:
private changeSystemOnAlpha = throwError('OOPS');
private changeSystemOnBeta = throwError('OHNOES');
*/

private changeSystemOnAlpha = of({"success":"true"}); //mock http
private changeSystemOnBeta = of({"name":"system one", "url":"http://goferit"});  //mock http

private currentSystemID: string = null;


// these members are for tracking the progress of a system change
private setSystemStatus = new BehaviorSubject<boolean>(true);
private get setSystemStatus$() {
  return this.setSystemStatus.asObservable();
}

private setSystem(systemID: string): Observable<boolean> {
    console.log('service.setSystem-> set system request for [' + systemID + ']');


    // first call to alpha
    this.changeSystemOnAlpha.subscribe(result => {

      // log
      console.log('service.setSystem-> System changed on alpha to [' + systemID + ']');

      // next call beta
      this.changeSystemOnBeta.subscribe(system => {

        // log, save, emit true
        console.log("service.setSystem-> System changed on beta to [" + systemID + "]");
        this.saveSystemData(system);
        this.currentSystemID =  systemID;
        this.setSystemStatus.next(true);

      }, error => {
          console.log('service.setSystem-> An error occured changing the system on the beta host: ' + error);
          this.setSystemStatus.next(false);

      });
    }, error => {
        console.log('auth.service.setSystem-> An error occured changing the system on the alpha host: ' + error);
        this.setSystemStatus.next(false);

    });

    // return ref to behavior/observable
    return this.setSystemStatus$;
  }


 public changeSystem(systemID: string, navigate: boolean = false): Observable<boolean> {
    console.log("service.changeSystem-> Request to change system to [" + systemID + "] made using the current system of [" + this.currentSystemID + "]");

    // see if we are actually changing the system, check params, get current system and compare
    if (!systemID) {
      console.log("service.changeSystem-> request made using null system");
      return of(false);
    }

    if (!this.currentSystemID || this.currentSystemID.toLowerCase() !== systemID.toLowerCase()) {
      return this.setSystem(systemID).pipe(map(success => {
        console.log("service.changeSystem-> auth.service.setsystem([" + systemID + "]) result is [" + success + "]");

        if (success && navigate) {
            console.log("service.changeSystem-> router.navigate to dashboard/[" + systemID + "]");
            this.router.navigate(['/dashboard/', systemID])
        }

        // we are done
        return success;
      }));
    } else {
        // dont need to change systems
      return of(true);
    }
  }

  private saveSystemData(data) {
    console.log("service.saveSystemData-> saving system data for [" + data.name + "]");
  }


}


// Testing like we are calling service from components over time
let testService = new service();
let systemRequests = ["10","","10","20"];
let delay = 0;
const appDiv: HTMLElement = document.getElementById('app');

for(let systemID of systemRequests) {
  delay+=500;
  setTimeout( () => 
    testService.changeSystem(systemID).subscribe(success => {
      console.log("changeSystem('"+systemID+"') resulted in ["+success+"]");
      if (success)
        appDiv.innerHTML = systemID;      
    })
   , delay );
}

Here is my stackblitz.