This recipe is useful for cooking up chained API calls as a result of a single action.
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:
- POST a new repostiry
- GET the master branch of the new repository
- GET the files on the master branch
- 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.
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,
) {}
}
@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)