Instantly share code, notes, and snippets.

Embed
What would you like to do?
Making chained API Calls using @ngrx/Effects

Making chained API Calls using @ngrx/Effects

Purpose

This recipe is useful for cooking up chained API calls as a result of a single action.

Description

In the below example, a single action called POST_REPO is dispatched and it's intention is to create a new repostiory on GitHub then update the README with new data after it is created.
For this to happen there are 4 API calls necessary to the GitHub API:

  1. POST a new repostiry
  2. GET the master branch of the new repository
  3. GET the files on the master branch
  4. PUT the README.md file

The POST_REPO's payload contains payload.repo with information needed for API call 1.
The response from API call 1 is necessary for API call 2.
The response from API call 2 is necessary for API call 3.
The response from API call 3 and payload.file, which has information needed to update the README.md file, is neccessary for API call 4.

Using Observable.ForkJoin makes this possible.

Example

import { Injectable } from '@angular/core';
import { Effect, Actions } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { handleError } from './handleError';


import { GithubService } from '../services/github.service';
import * as githubActions from '../actions/github';

@Injectable()
export class GitHubEffects {
  @Effect()
  postRepo$: Observable<Action> = this.actions$
    .ofType(githubActions.POST_REPO)
    .map((action: githubActions.PostRepo) => action.payload)
    // return the payload and POST the repo
    .switchMap((payload: any) => Observable.forkJoin([
      Observable.of(payload),
      this.githubService.postRepo(payload.repo)
    ]))
    // return the repo and the master branch as an array
    .switchMap((data: any) => {
      const [payload, repo] = data;
      return Observable.forkJoin([
        Observable.of(payload),
        Observable.of(repo),
        this.githubService.getMasterBranch(repo.name)
      ]);
    })
    // return the payload, the repo, and get the sha for README
    .switchMap((data: any) => {
      const [payload, repo, branch] = data;
      return Observable.forkJoin([
        Observable.of(payload),
        Observable.of(repo),
        this.githubService.getFiles(repo.name, branch)
          .map((files: any) => files.tree
            .filter(file => file.path === 'README.md')
            .map(file => file.sha)[0]
          )
      ]);
    })
    // update README with data from payload.file
    .switchMap((data: any) => {
      const [payload, repo, sha] = data;
      payload.file.sha = sha;
      return this.githubService.putFile(repo.name, payload.file);
    });

  constructor(
    private actions$: Actions,
    private githubService: GithubService,
  ) {}
}
@goelinsights

This comment has been minimized.

Copy link

goelinsights commented Jan 28, 2018

@peterbsmith2 this looks great. Mind refactoring it using enums for action types and pipeable (formerly lettable) operators to match the example-app? (Happy to help)

@djindjic

This comment has been minimized.

Copy link

djindjic commented Feb 12, 2018

Why did you need Scheduler?

@peterbsmith2

This comment has been minimized.

Copy link
Owner Author

peterbsmith2 commented Mar 13, 2018

@djindjic not needed, just left-over. removing

@perjerz3434

This comment has been minimized.

Copy link

perjerz3434 commented Apr 17, 2018

there is no example of Actions for Reducer.

@eapenjohn

This comment has been minimized.

Copy link

eapenjohn commented Jun 21, 2018

how do you handle the error?

@skatestyle

This comment has been minimized.

Copy link

skatestyle commented Aug 8, 2018

How to add error handling to this effect?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment