Skip to content

Instantly share code, notes, and snippets.

@peterbsmyth
Last active May 21, 2020 13:39
Show Gist options
  • Star 33 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save peterbsmyth/ce94c0a5ddceb99bab24a761731d1f07 to your computer and use it in GitHub Desktop.
Save peterbsmyth/ce94c0a5ddceb99bab24a761731d1f07 to your computer and use it in GitHub Desktop.
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
Copy link

@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
Copy link

Why did you need Scheduler?

@peterbsmyth
Copy link
Author

@djindjic not needed, just left-over. removing

@perjerz
Copy link

perjerz commented Apr 17, 2018

there is no example of Actions for Reducer.

@eapenjohn
Copy link

eapenjohn commented Jun 21, 2018

how do you handle the error?

@flolu
Copy link

flolu commented Aug 8, 2018

How to add error handling to this effect?

@CHBaker
Copy link

CHBaker commented Mar 19, 2019

@eapenjohn @skatestyle you can add error handling with catchError from rxjs,

catchError((error) => {
                const errorObject = new Error(`${ error.code }, ${ error.message }`);
                return throwError(
                    errorObject
                );
            })

@Mesya82
Copy link

Mesya82 commented Sep 16, 2019

How can I handle error of every single call? I tried to use catchError on forkJoin but then it tries to go to next one (instead of just stopping) and throws an exception ('undefined is not a function').

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