Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register epics in Application #40

Closed
rubiktubik opened this issue Sep 1, 2018 · 7 comments
Closed

Register epics in Application #40

rubiktubik opened this issue Sep 1, 2018 · 7 comments

Comments

@rubiktubik
Copy link

Hi,

you wrote a Tutorial on how to register epics in a application but it seems not to work anymore.

I tried to reproduce the example and it starts with this error:
grafik

ActionObservable needs an argument T.

How can i use and register epics in my app.module? Especially with more then one epic.

export class AppModule {
  constructor(
    private ngRedux: NgRedux<IAppState>,
    private epics: SessionEpics,
  ) {
    const middleware = [createEpicMiddleware(this.epics.login)];
    ngRedux.configureStore(rootReducer, {}, middleware);
  }
}

Is this approach up-to-date in Version 9+?

Regards
rubiktubik

@joese
Copy link

joese commented Sep 2, 2018

Hi!

Using rxjs 6+, Angular 6+ and Angular-redux 9+ I'm using this aproach:

In the same class where epics are declared, declare an function returning an array of epics:

 public createEpics(): Epic<Action, Action, any, any>[] {
    return [
      this.viewOrgEpic(),
      this.searchEpic(),
.....

    ];
  }

  private viewOrgEpic(): Epic<OrganisationAPIViewAction, OrganisationAPIViewAction, IOrganisationState, any> {
    return (action$) => action$
      .ofType(OrganisationAPIActions.VIEW_ORGANISATION)
      .pipe(
        switchMap((action: OrganisationAPIViewAction) => this.service.get(action.meta.id)
          .pipe(
            map((item: IOrganisationItem) => this.actions.viewByIdSuccess(action.meta.id, item)),
            startWith(this.actions.viewStarted(action.meta.id))
          )),
        catchError(err => of(this.actions.viewFailed(err))),
      );
  }
........

Split up epics in several classes reflecting the architecture of app. Collect all epics in an RootEpic class:

@Injectable()
export class RootEpics {

  constructor(private organisationEpics: OrganisationAPIEpics, private authEpics: AuthEpics) {
  }

  public createEpics(): Epic<Action, Action, any, any>[] {
    return this.organisationEpics.createEpics()
      .concat(this.authEpics.createEpics())
......
  }
}

Define an rootReducer to assign different reducers to parts in the store:

export const rootReducer = composeReducers(
  defaultFormReducer(),
  combineReducers<IAppState, Action>({
    organisation: combineReducers<IOrganisationState, Action>({
      view: organisationViewReducer,
      ....
    }),
....
    auth: authReducer,
    router: routerReducer
  })
);

StoreModule is using epics and reducers in this way:

const middleware = createEpicMiddleware();
    store.configureStore(
      rootReducer,
      INITIAL_STATE,
      [createLogger(), middleware],
      isDevMode() && devTools.isEnabled() ? [devTools.enhancer()] : []
    );

    for (const epic of this.rootEpics.createEpics()) {
      middleware.run(epic);
    }

Not sure if this is the best/right way of doing it, but it works for me. Hope it helps at least.
Cheers

@igpeev
Copy link

igpeev commented Sep 13, 2018

@joese, FABULOUS !!!

@vikas-rb15
Copy link

@joese

Above code helped me to change root epic and store configure, but i am facing one more issue.

Currently i am working on migration from angular 5 to 6. While updating to redux 4 i am getting error as store.getState() is not a funtion

export interface IAppState {
	source1: IEmployee[],
	source2: IEmployee[],
}

export type FSAction = FluxStandardAction<any, MetaData | null | number>;

employeeValues_Epic: Epic<FSAction, IAppState> = (action$, store) => action$
    .ofType(APP_Actions.ActionTypes.APP_EMPLOYEE_VALUES).pipe(
      switchMap(data => {  
           let state1: IEmployee[] = [];
           try {
                console.log("in All domain  iteration");
                state1 = store.getState().source1.state;        
           } catch (error) {
               console.error(error);
        }
        return observableFrom(state1);
      }));

@joese
Copy link

joese commented Jan 8, 2019

If you are upgrading, you will then encounter that Epic interface has changed to:

Epic<Input extends Action = any, Output extends Input = Input, State = any, Dependencies = any>

getState is not a function, you should use the .value instead.
Try this:

employeeValues_Epic: Epic<FSAction, FSAction, IAppState, any> = (action$, state$) => action$
    .ofType(APP_Actions.ActionTypes.APP_EMPLOYEE_VALUES).pipe(
      switchMap(data => 
          someAction(state$.value.source1)));

An epic should return an action, not a value. A reducer should pick up the action and change state accordingly.

@vikas-rb15
Copy link

@joese Thank you very much. It helped me in solving my issue. Could you please give me reference documents for such epic middleware with rxjs to understand more

@myquery
Copy link

myquery commented Dec 28, 2019

  `public createEpics(): Epic<Action, 
  Action, any, any>[]`

Epic<Action...> could represent your action creators?

Epic<any...> what argument can be passed it.

  `const middleware = 
    createEpicMiddleware();
       store.configureStore(
       rootReducer,
       INITIAL_STATE,
        [createLogger(), middleware],
          isDevMode() && 
        devTools.isEnabled() ? 
            [devTools.enhancer()] : []
      );
       for (const epic of 
       this.rootEpics.createEpics()) {
        middleware.run(epic);
       }`

can someone explain what is happening here? the createMiddleware function was not passed any function as argument

@joese
Copy link

joese commented Jan 13, 2020

  `public createEpics(): Epic<Action, 
  Action, any, any>[]`

Epic<Action...> could represent your action creators?
No, Epic is not an action creator. It is rather a mechanism to be able to add side-effect to actions. Those side-effects could be returning one or more actions which in theory could trigger other epics defined for those action. You could even trigger other not redux related statements such as alerts etc.

When an Action is triggered that is declared to be caught in an Epic, that epic will be executed.

Epic<any...> what argument can be passed it.

Epic is generic: Epic<Input extends Action = any, Output extends Input = Input, State = any, Dependencies = any>. By that contract the first type should be any Class extending Action. The second should extend Input. State and Dependencies could be any class/interface.

  `const middleware = 
    createEpicMiddleware();
       store.configureStore(
       rootReducer,
       INITIAL_STATE,
        [createLogger(), middleware],
          isDevMode() && 
        devTools.isEnabled() ? 
            [devTools.enhancer()] : []
      );
       for (const epic of 
       this.rootEpics.createEpics()) {
        middleware.run(epic);
       }`

can someone explain what is happening here? the createMiddleware function was not passed any function as argument.

createMiddleware is returning an object of type EpicMiddleware<T, O, S, D>. It is implemented in redux-observable. That object is later used to run your epics.

NgRedux configureStore accepts an argument named middleware. You should pass the previously created middleware-object. This implementation for AppReduxModule should work:

@NgModule({
  imports: [
    NgReduxModule,
    NgReduxRouterModule.forRoot()
  ],
  providers: [
    NgReduxRouter,
    UserEpics
  ]
})
export class AppReduxModule {
  constructor(
    private readonly ngRedux: NgRedux<MyStateType>,
    private readonly devTools: DevToolsExtension,
    private readonly ngReduxRouter: NgReduxRoute
    private readonly userEpics: UserEpics
  ) {

    const epicMiddleware = createEpicMiddleware();
    const middlewares = [
      reduxActionClassMiddleware,
      epicMiddleware
    ];

    if (!environment.production) {
      middlewares.push(createLogger());
    }

    const epics = [].concat(
      this.userEpics.getEpics(),
      // Add other epics here
    );

    // Tell @angular-redux/store about our rootReducer and our initial state.
    // It will use this to create a redux store for us and wire up all the
    // events.
    ngRedux.configureStore(
      composeReducers(defaultFormReducer(), appReducer),
      INITIAL_STATE,
      middlewares,
      isDevMode() && devTools.isEnabled() ? [composeWithDevTools()] : []
    );

    for (const epic of epics) {
      epicMiddleware.run(epic);
    }

    if (ngReduxRouter) {
      ngReduxRouter.initialize();
    }
  }
}

Definition of UserEpics:

@Injectable()
export class UserEpics {

  constructor(private backend: BackendService,
              private router: Router,
              private redux: NgRedux<MyStateType>) {
  }

  getEpics = () => [].concat(
    this.loginUserName,
    this.catchLoginError,
  )

  loginUserName = ($action: ActionsObservable<LoginUsernameAction>) => {
    return $action.ofType(LoginUsernameAction.actionName)
      .pipe(
        mergeMap((action: LoginUsernameAction) =>
           this.backend.login(action.payload.username, action.payload.password)
              .pipe(
                mergeMap((userInfo: UserData) => concat(
                  of(new SetUserAction(userInfo)),
                  of(new LoggedInAction())
                  )
                ),
                catchError((err) => of(new LoginErrorAction()))
              )),
            catchError((err) => of(new LoginErrorAction()))
        );
  }

  catchLoginError = ($action: ActionsObservable<LoginErrorAction>) => {
    return $action.ofType(LoginErrorAction.actionName)
      .pipe(
        mergeMap((action: LoginErrorAction) => of(new UserError({label: 'Login failed', message: 'Provided credentials is not valid.'})))
      );
  }

}

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

No branches or pull requests

6 participants