2
 ngOnInit(): void {
    this.store.dispatch(new patients.Load([]));
    this.patients$ = this.store.select(fromPatients.getAll);

    this.patients$.map(p =>{ //  patients$: Observable<Patient[]>;
      this.rows = p.map(pat => { //I use this on the front end
        return {
          username: pat.username,
          id: pat.id,
          hasAlert: this.hasAlerts(pat), //made this an observable for the async pipe in view
          settings: "Settings"
        };
      });
      this.table.recalculatePages();
      console.log(this.rows);
      console.log("This happens first");
    }).subscribe();

  }
  hasAlerts(pat: Patient): Observable<boolean> {
    var shouldAlert$ = Observable.of(false);//this value is always taken

      this.observations2$ = this.dataService.fetchItems<Observation>(
        "Observation",
        null,
        pat.id// How would i pass this in with mergeMap()?  
      );

      this.observations2$.subscribe(curObservation => {
        if (curObservation.category.coding[0].code == "GlucoseEvent"){ 
          shouldAlert$ = Observable.of(true);
          console.log("should alert$", shouldAlert$);
        }
      });

    console.log("this happens second");
    return shouldAlert$;
  }

In the code above I parse an observable called patients$ that has an array of patients. I am then mapping those patients to an object array called this.rows that I show on my client.

My problem involves the hasAlert property which processes another observable itself in the method call to hasAlerts(). This method call to hasAlerts() does not happen synchronously so the console.log("this happens first"); occurs before my hasAlerts method can do the logic in the If statement to decide whether it should be set to true or false, instead it just uses the value it is initialized to in the first line of hasAlerts(). Confirmed by the console.log("this happens second"); being displayed second.

hasAlerts() could return a boolean not an observable, I was trying to see if that would solve my problem using an asycn pipe on the front end (it didnt).

I believe the way to solve this involves using mergemap however I am not sure how i would pass in the pat.id that the hasAlerts method needs? Or maybe this is not the correct approach to solve my current problem with the asynchronous execution of this code.

I am currently trying to use this this question about mergemap to solve my issue but passing the pat.id that the second observable in hasAlerts I haven't figured out yet. 1

updated code following Piccis idea.

this.patients$.map(p =>{ //  patients$: Observable<Patient[]>;
      this.rows = p.map(pat => { //I use this on the front end
        return {
          username: pat.username,
          id: pat.id,
          hasAlert: false, //set the default value
          settings: "Settings"
        };
      })
    }).do(data => console.log("data1",data))
  //   .switchMap(p => Observable.from(p))
  //   .do(data => console.log("data2",data)) // creates a stream of Observable<Patient>
  //   .mergeMap(patient => this.dataService.fetchItems<Observation>(
  //       "Observation",
  //       null,
  //       "pat..frank"//patient[0].id//"pat..frank"//patient.id// patient should be your guy          
  //     )
  //     .map(curObservation => {
  //       console.log("currOBS",curObservation); 

  //       if (curObservation.category.coding[0].code == "GlucoseEvent"){
  //         var shouldAlert$ = true;
  //         console.log("should alert$", shouldAlert$);
  //       }
  //     })
  //   ).do(data => console.log(data))
  //  .toArray()
   .subscribe(
      patients => {
          this.table.recalculatePages();
          console.log(this.rows);
      }
   )

Data1 returns an array of patients. I need to comment out the middle because there is a syntax error with the switchmap line saying " Argument of type 'void' is not assignable to parameter of type 'ArrayLike<{}>'"

Frank Visaggio
  • 3,642
  • 9
  • 34
  • 71

3 Answers3

1

You are looking in the right direction with mergeMap, but you need to rework a bit your code.

You start with this.patients$ which is an Observable.

From here you need to create a stream of Observable. This can be done with the static method from of Observable.

Now you are able to manager each single Patient and fetch his/her curObservation via the service fetchItems.

Eventually you recreate your array and then subscribe.

The final result could look like something along these lines

    ngOnInit(): void {
            this.store.dispatch(new patients.Load([]));
            this.patients$ = this.store.select(fromPatients.getAll);

            this.patients$.map(p =>{ //  patients$: Observable<Patient[]>;
              this.rows = p.map(pat => { //I use this on the front end
                return {
                  username: pat.username,
                  id: pat.id,
                  hasAlert: false, //set the default value
                  settings: "Settings"
                };
              });
            })
            .do(data => consoel.log(data))  // check  whether data is an array of Patients as it is supposed to be
            .switchMap(p => Observable.from(p)) // creates a stream of Observable<Patient>
            .do(data => console.log(data))  // check whether data is a single Patient
            .mergeMap(patient => this.dataService.fetchItems<Observation>(
                "Observation",
                null,
                patient.id// patient should be your guy          
              )
              .map(curObservation => {
                if (curObservation.category.coding[0].code == "GlucoseEvent"){ 
                  shouldAlert$ = true;
                  console.log("should alert$", shouldAlert$);
                }
              })
            )
           .toArray()
           .subscribe(
              patients => {
                  this.table.recalculatePages();
                  console.log(this.rows);
              }
           )
        }

UPDATE - THE BASIC MECHANISM

If we remove all specifics of your case, the basic mechanism which is implemented in the snippet above is the following

import {Observable} from 'rxjs';

const obs1 = Observable.of([1, 2, 3, 4, 5, 6]);

obs1
.switchMap(n => Observable.from(n))
.mergeMap(n => Observable.of(n*2))
.toArray()
.subscribe(console.log)
Picci
  • 16,775
  • 13
  • 70
  • 113
  • 1
    very helpful. I noticed the (p) in the from gives me the following error Argument of type 'void' is not assignable to parameter of type 'ArrayLike<{}>'. on that switchmap line what is the difference between the p before the => and the p after ? – Frank Visaggio Apr 06 '18 at 18:36
  • I think I may need to add a .toArray() before the .switchMap() – Frank Visaggio Apr 06 '18 at 18:48
  • The .toArray() appears to have fixed the first error about argument of type void is not assignable. However currently the patient.id gives me an error of "Property 'id' does not exist on type 'void'." would patient[0].id make more sense there? Does that make sense? – Frank Visaggio Apr 06 '18 at 18:55
  • 1
    It is not easy to reproduce your specific situation. I suggest you put a simple `.do(data => console.log(data))` statement after every step of the processing to see which data you have in each step. `patient[0]` does NOT seem a good idea – Picci Apr 06 '18 at 19:24
  • you are correct I do have an array of patients in the first do thats a great idea. However my switchmap has the same syntax error of "ERROR in Argument of type 'void' is not assignable to parameter of type 'ArrayLike<{}>'." as described in the first comment. – Frank Visaggio Apr 06 '18 at 19:36
  • Tje first p in the switchMap is of the type void when i hover over it in VSCode however if I add .toArray() after the first do the syntax error goes away but the data is already an array. – Frank Visaggio Apr 06 '18 at 19:50
  • What do you see on the console after the first `do`? is it an array of Patients (with some Patients inside) or is it undefined or null? – Picci Apr 06 '18 at 20:18
  • Its an array of patients. However That is only if i comment out everything So i need to comment out the switchmap. Theres a syntax error in the switchmap part. – Frank Visaggio Apr 06 '18 at 20:20
  • I have updated the suggested code in my answer. The first `switchMap` comes after the first `do`. You need to check what you get after the first `do`. Is it an Array of Patients – Picci Apr 06 '18 at 20:26
  • yes I did. I do get an array of patients there. i also updated my original post to add the snippet you suggest that returns the array of patients but illustrating what part has the syntax error (in the comments) – Frank Visaggio Apr 06 '18 at 20:28
  • 1
    I have update my answer with the basic mechanism suggested. I hope this helps – Picci Apr 06 '18 at 20:53
1

The basic problem is combining two async calls, can be done with zip().

(Note, I originally posted a solution with forkJoin(), but this does not work with ngrx select(), since the select never completes - therefore forkJoin never fires).

Convert the Observable<Patient[]> returned from the first fetch to Observable<Patient>, as it is more convenient for the zip operator.

Next problem is 2nd async is dependent on result of 1st async (pat.id) - build that one with concatMap().

(Note, I originally suggested mergeMap(), however concatMap() guarantees the same ordering of hasAlert$ as in patient$. This is important, since this.dataService.fetchItems() may return individual fetches out of order).

import { zip } from 'rxjs/observable/zip';
...

ngOnInit(): void {
  this.store.dispatch(new patients.Load([]));

  const patient$ = this.store.select(fromPatients.getAll)
    .mergeMap(value => value); // convert from Observable<patients[]> to Observable<patient>

  const hasAlert$ = patient$.concatMap(patient => {
    return this.dataService.fetchItems<Observation>('Observation' null, patient.id)
      .map(curObservation => curObservation.category.coding[0].code === 'GlucoseEvent')
    );
  })

  zip(patient$, hasAlert$)  // combine values from both asyncs
    .map(([patient, hasAlert]) => {
      return {
        username: patient.username,
        id: patient.id,
        hasAlert,
        settings: "Settings"
      };
    })
    .toArray()
    .subscribe(rows => {
      this.rows = rows;
      this.table.recalculatePages();
    }); 
}

Testing the Rx logic of the answer snippet.

console.clear();
const { zip, from, of } = Rx.Observable;
/* in Angular with Rxjs v5.5, use 
  import { zip } from 'rxjs/observable/zip';
  import { from } from 'rxjs/observable/of';
  import { of } from 'rxjs/observable/from';
*/

// Simulate other observables
const storeSelectFromPatientsGetAll$ = () =>
  of([{id: 1, username: 'Fred'}, {id: 2, username: 'Joan'}]);
const dataServiceFetchItems$ = (type, something, id) =>
  of({ category: { coding: [{code: 'GlucoseEvent'}] }})

// Testing the ngOnInit code
const patient$ = storeSelectFromPatientsGetAll$()
  .mergeMap(value => value);

const hasAlert$ = patient$.concatMap(patient => {
  return dataServiceFetchItems$('Observation', null, patient.id)
    .map(curObservation => curObservation.category.coding[0].code === 'GlucoseEvent');
});

zip(patient$, hasAlert$)  // wait for resolution of both asyncs
  .map(([patient, hasAlert]) => {
    return {
      username: patient.username,
      id: patient.id,
      hasAlert,
      settings: 'Settings'
    };
  })
  .toArray()
  .subscribe(rows => {
    console.log(rows);
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.8/Rx.js"></script>
Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
  • Sorry, a couple of small adjustments needed there. Difficult to do it correctly outside of an IDE. – Richard Matsen Apr 06 '18 at 20:34
  • not a problem. I am trying this out currently working through one error error "Property 'forkJoin' does not exist on type 'Observable[]'." for the forkjoin line that i am trying to fix. I think it is how i am importing forkjoin in this angular 4 ngrx project. – Frank Visaggio Apr 06 '18 at 20:38
  • Are you using the `Observable.forkJoin()` variation of the syntax? Note the import statement I used - this is more convenient (presume your rxjs v is 5.5 or greater) – Richard Matsen Apr 06 '18 at 20:40
  • upgrading from 5.4-> 5.5 fixes that. Currently looking into cannot find name id: for that line line. has alerts and the settings property says the same thing. Sorry about not putting up a plunkr the stores are pretty large and I didnt stub them out to simplify it. – Frank Visaggio Apr 06 '18 at 20:42
  • Can't quite pick the error, but it looks like it's throwing during the mapping of patients to rows (since `id:` only appears here) - or is it throwing on the template? – Richard Matsen Apr 06 '18 at 22:31
  • I put a .do(data=>console.log(data) before the .toArray for the to see the output I am getting from the zip. I only see my first patient displayed twice to the console and it never goes to the second patient. Is it possible that this zip is occurring before the hasAlert$ and Patient$ is processed? This is in my scenario, Your example works well as illustrated. – Frank Visaggio Apr 09 '18 at 15:26
  • `zip` works like a zipper - pairing up teeth, so it can't emit a patient without a corresponding hasAlert (and vice versa). I'll look at it further. – Richard Matsen Apr 09 '18 at 18:37
  • I noticed from my mergeMap for the patients even though I only have 2 patients, the first patient is returned 3 times, then the second patient is returned once then the first patient is returned again. Could this be my issue? – Frank Visaggio Apr 09 '18 at 18:40
  • Perhaps - could be `fromPatients.getAll` doesn't behave the same as my simulation of it. – Richard Matsen Apr 09 '18 at 18:46
  • [@ngrx/store-devtools](https://github.com/ngrx/platform/blob/master/docs/store-devtools/README.md) will give you a look at the store. – Richard Matsen Apr 09 '18 at 18:50
  • BTW (it just occurred to me) - this code would be better in the store than in the component `ngOnInit()` which should just fire the load command and subscribe to a getter which extracts the combined result. – Richard Matsen Apr 09 '18 at 18:57
0

This was how I was able to get this logic to work for me, via a different approach. I need to go through the ultimate angular ngrx video and a few rxjs tutorials to better understand where I was going wrong in the other two answers because the examples provided worked well.

The approach that worked for me below

  • use filter when processing the observations from the data service in the hasAlerts method and add any observations that meet the criteria to that and return it.

  • set the hasAlerts property to false, and then call the hasAlerts() method for that given patient and modify that property on the row and then return the row.

    ngOnInit(): void {
    this.store.dispatch(new patients.Load([]));
    this.patients$ = this.store.select(fromPatients.getAll);
    
    this.patients$.map(p =>{ //  patients$: Observable<Patient[]>;
      this.rows = p.map(pat => { //I use this on the front end
        var rowx= {
          username: pat.username,
          id: pat.id,
          hasAlert: false, //made this an observable for the async pipe in view
          settings: "Settings"
        };
    
        this.hasAlerts(pat).do(x => {
            observations++;
            if (observations>0)
            {
              rowX.hasAlert=true;
            }
          }).subscribe();
        return rowX;
      });
    }).subscribe(
    ()=>{
    
    },
    ()=>{
      this.table.recalculatePages();
    });
    
    }
     hasAlerts(pat: Patient): Observable<Observation> {
    
      var obs$ = this.dataService.fetchItems<Observation>(
        "Observation",
        null,
        pat.id
      ).filer(function(curObservation){
         if (curObservation.category.coding[0].code == "GlucoseEvent"){ 
           return true;
         }
         else{
           return false;
         }
      });
    
    return obs$;
    }
    
Frank Visaggio
  • 3,642
  • 9
  • 34
  • 71