1

I have an Issue object, one property of which is an array of Tool objects. Issues and Tools are two different tables in my database.

In my issues-log.component I am dispatching an action to search for issues that match my search query. This works just fine.

The next thing I've been trying to do, is to take the list of issues and for each issue id, pass that to my tools service so that I can get the tools related to that issue.

The way I was able to get that working is to add another effect that listens for the ISSUE.SEARCH.COMPLETE action. Then I looped through the array of issues in my tools service so that I could call the API service for each issue id and add the tools property to that issue. This seems wrong in a couple ways. One is that for large lists of issues, it takes a long time to for all the tools to load and if I try to kick off another issues search I have to wait for the tools to load from the previous one before my app responds. Two, is that it seems wrong to pass the whole issues array into the tools service when I only need one issue id at a time for my tools API to get the list of tools associated with that issue; this doesn't make my tools service easily re-usable in other places in my app.

I would prefer not to wait until my API call to get the list of issues is done before I can start getting the tools associated with each issue id. Is it possible to add code in my issuesSearch$ effect, where commented below, to start adding the tools as my list of issues is being built?

Component:

@Component({
selector: issue-log,
template: `
  <issue-search (search)="search($event)></issue-search>
  <issue-list [issues]=$issues | async></issue-list>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class IssueLogComponent {
  issues$: Observable<Issue[]>;

  constructor(private store: Store<fromRoot.State>) {
    this.issues$ = store.select(fromRoot.getIssueSearchResults);
  }

  search(query) {
    this.store.dispatch(new issue.IssueSearch(query));
  }
}

Effect:

@Effect() issueSearch$: Observable<Action> = this.actions$
  .ofType(issue.ISSUE_SEARCH)
  .debounceTime(300)
  .map(toPayload)
  .switchMap(query => {
    if (query === '') {
      return empty();
    }
    const nextSearch$ = this.actions$.ofType(issue.ISSUE_SEARCH).skip(1);
    return this.issueService.getIssuesFromQuery(query) //calls API service
      .takeUntil(nextSearch$)
      .mergeMap((res: Issue[]) => {
        // How do I make another API call here, passing data from each element of res array?
        return Observable.from([
          new issue.IssueSearchComplete(res)
        ])
      })
      .catch(() => of(new issue.IssueSearchComplete([])));
});

I also tried calling my tools service from inside my issues service, but don't think this is the right approach either.

ksimonson10
  • 102
  • 7
  • I think this [post](https://stackoverflow.com/questions/35268482/chaining-rxjs-observables-from-http-data-in-angular2-with-typescript?rq=1) is helpful in the direction to use .flatMap or maybe .forkJoin, but I'm still not sure about wrapping that in a loop to get each element of my response. Maybe use a .forEach? – ksimonson10 Jul 10 '17 at 14:05
  • So you want to get issues and be able to access those issues immediately, while simultaneously using the data in each of those issues to make more asynchronous calls in the background? – Lansana Camara Jul 10 '17 at 14:08
  • Yes, is that possible? – ksimonson10 Jul 10 '17 at 14:09
  • It is possible. Let me write up an example answer. – Lansana Camara Jul 10 '17 at 14:09

1 Answers1

1

Since you need to access the results of your issues immediately BEFORE making other asynchronous requests using the data of those issues, you may want to use an RxJS Subject of some sort.

You can observe for issues on that subject, and also subscribe to the observable of the result of all final calls.

Conceptual breakdown:

  1. We fetch issues
  2. We pass the issues to another observable stream so they can be accessed immediately
  3. We use the issues to make other async calls
  4. We return the result of the final calls, which too can be subscribed to

So you will have to subscribe to two observable streams with this approach.

Here is a simple, contrived example:

@Injectable()
export class FooService {
    issuesSubject: BehaviorSubject<Issue[]> = new BehaviorSubject([]);

    get issues$(): Observable<Issue[]> {
        return this.issuesSubject.asObservable();
    }

    getIssuesAndMakeOtherAsyncCalls(): Observable<any> {
       return this.issueService
                  .getIssuesFromQuery(query)
                  .flatMap((issues: Issue[]) => {
                      // This is where you add issues to the issues stream
                      this.issuesSubject.next(issues);

                      // This is where you make other HTTP calls using the issues
                      return this.makeSomeOtherCallsUsingIssues(issues);
                  })
    }
}

In your component:

@Component({})
export class FooComponent implements OnInit {
    ngOnInit() {
        this.issueService.getIssuesAndMakeOtherAsyncCalls().subscribe(res => {
            // `res` will be the result of the `makeSomeOtherCallsUsingIssues(issues)` call in the service
        });

        this.issueService.issues$.subscribe((issues: Issue[]) => {
            // You will get issues here when the `this.issuesSubject.next(issues)` line is called in your service
        });
    }
}

Does that work for you?

Lansana Camara
  • 9,497
  • 11
  • 47
  • 87
  • Thank you for the answer! I'm trying to follow, but are there some typos in your method names? I'm having a little trouble. In the component, `this.issueService.getAllIssues()`, should that be `getIssuesAndMakeOtherAsyncCalls()`? – ksimonson10 Jul 10 '17 at 19:40
  • Ah sorry about that, yes, it should be! Fixed. – Lansana Camara Jul 10 '17 at 21:13
  • Thanks again for your answer, it doesn't address the part that is the greatest issue for me though, which is that I actually need to access a property in each element of the issues array and pass that into my other async call. I think the problem is in my data structure. I think I need to store my issues and my tools as separate lists and then figure out in my view how to combine them appropriately. – ksimonson10 Jul 12 '17 at 23:08