angular-redux / store

Angular 2+ bindings for Redux
MIT License
1.34k stars 202 forks source link

How do I work with multiple epics? #265

Closed alinnert closed 7 years ago

alinnert commented 7 years ago

I'm missing an example of how to work with multiple epics. I know there's a combineEpics function in redux-observable. But epics look different in ng2-redux. Normally epics are functions. Here they are classes. That's why examples for redux-observable don't help a lot. The linked example app by Rangle also doesn't really answer that question.

How do I register more than one epic as store middleware? And can a class contain more than one epic or should there be one class per epic?

alinnert commented 7 years ago

Okay, after fiddling a little I found the solution:

Yes, you can have multiple epics per class.

To combine multiple epics do this:

import { createEpicMiddleware, combineEpics } from "redux-observable";

@NgModule({
  declarations: [ /* ... */ ],
  imports: [ /* ... */ ],
  providers: [
    MyEpics,
    MoreEpics,
    /* ... */
  ],
  bootstrap: [ /* ... */ ]
})
export class AppModule {
  constructor(
    ngRedux: NgRedux<IAppState>,
    myEpics: MyEpics,
    moreEpics: MoreEpics,
  )
  {
    const epics = combineEpics(
      myEpics.login,
      myEpics.logout,
      moreEpics.loadData,
      moreEpics.loadMoreData,
    );

    const middleware = [ createEpicMiddleware(epics) ];

    ngRedux.configureStore(rootReducer, initialAppState, middleware);
  }
}
SethDavenport commented 7 years ago

Hi @alinnert - epics with ng2-redux are exactly the same as for redux-observable - they are functions in both places.

In our example, our epic is a member of a class, but that's just for the sake of convenience: classes make it easier to access stuff from Angular2's dependency injector.

So your code above makes sense.

jiayihu commented 7 years ago

Hi @SethDavenport I'm trying to find a way to export all epics as single epic/variable/module. I'm used to combining stuff within a folder in index.ts and export a single variable.

Since in angular-redux epics are services I cannot just do export default combineEpics(MyEpics, MoreEpics) like I do with reducers. As shown by @alinnert I have to import all epics in AppModule and combine them there, but it's not a ideal because it's easy to forget to add a new epic whereas adding in effects/index.ts feels more natural.

Do you have any solution?

cmelion commented 7 years ago

I'm starting to like the approach taken in the animals example:

@Injectable()
export class RootEpics {
  constructor(private animalEpics: AnimalEpics) {}

  public createEpics() {
    return [
      this.animalEpics.createEpic(ANIMAL_TYPES.ELEPHANT),
      this.animalEpics.createEpic(ANIMAL_TYPES.LION),
    ];
  }
}

Since there is nothing very "animal" specific about the epics (standard ajax) a more general approach might be something like:

@Injectable()
export class RootEpics {
  constructor(private itemEpics: ItemEpics) {
  }

  public createEpics() {
    return [
      this.itemEpics.createAjaxEpic(ITEM_TYPES.INFO),
      this.itemEpics.createAjaxEpic(ITEM_TYPES.TOKEN),
      this.itemEpics.createAjaxEpic(ITEM_TYPES.USER),
      /* If the payload from LOAD_SUCCEEDED contains a JWT then navigate to '/' (home) */
      this.itemEpics.createSimpleEpic({
        action: {
          type: '@angular-redux/router::UPDATE_LOCATION',
          payload: '/'
        },
        filter: 'jwt',
        trigger: ItemActions.LOAD_SUCCEEDED
      }),
    ];
  }
}

https://github.com/arielpartners/capdash2/blob/master/webapp/src/app/store/root.epics.ts

SethDavenport commented 7 years ago

Yeah at a fundamental level an epic is just a function that takes an observable of actions and maps it to a different observable of actions.

Our examples attach those functions as arrow functions to services so that we can inject stuff for them to use (e.g. Http). But as long as you have a reference to that arrow function that's all that redux cares about. So you can combine them however you want.

Or you can make a 'higher order' function that creates several epics based on some parameters as you have done with createAjaxEpic.

Then you just need to decide how to attach those epics to redux: either by calling createEpicMiddleware a bunch of times on member functions from several services and adding all the middlewares to the store; or by calling combineEpics first and creating one middleware.