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

Reducers are completely ignored in some cases with AOT #189

Closed
victornoel opened this issue Jul 26, 2017 · 42 comments
Closed

Reducers are completely ignored in some cases with AOT #189

victornoel opened this issue Jul 26, 2017 · 42 comments

Comments

@victornoel
Copy link

victornoel commented Jul 26, 2017

I'm submitting a...


[x] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request

What is the current behavior?

This was originally reported in #147 but it was closed by #153 even though the bug discussed there wasn't fixed.

I have a complex application with ngrx 4, only one root store (feature stores), some root effects, and lazyloading via the angular router.

When using AOT, at starts everything works fine but once the application is triggering a route (the component is lazy loaded but I'm not sure it is related since the store is already fully loaded at that point…) the actions are sent and visible via the redux devtools, but no change happens to the store.
I added some logging in one of the metareducers and it's clear that it doesn't even get there.

IMPORTANT: the problem is not about AOT making the application throwing errors, but about AOT making the application fail silently! Please refrain from reporting other issues here :)

Expected behavior:

Things should work :)

Minimal reproduction of the problem with instructions:

I couldn't reproduce it on a small repro, but my application is publicly available.

$ git clone https://gitlab.com/linagora/petals-cockpit.git
$ cd petals-cockpit
$ git checkout 9994938472d2994f07fac6903f2f5735fd5a8be3
$ cd frontend
$ yarn
$ ng serve --prod -e=dev-e2e

Login with admin/admin: nothing happens!
While if you run it with ng serve -e=dev-e2e and login, it will load as expected.

Here is for the record my setup:

export const reducers: ActionReducerMap<IStore> = {
  ui: UiReducer.reducer,
  users: UsersReducer.reducer,
  workspaces: WorkspacesReducer.reducer,
  buses: BusesReducer.reducer,
  busesInProgress: BusesInProgressReducer.reducer,
  containers: ContainersReducer.reducer,
  components: ComponentsReducer.reducer,
  serviceUnits: ServiceUnitsReducer.reducer,
  serviceAssemblies: ServiceAssembliesReducer.reducer,
  sharedLibraries: SharedLibrariesReducer.reducer,
};

// if environment is != from production
// use storeFreeze to avoid state mutation
const metaReducersDev = [storeFreeze, enableBatching];

const metaReducersProd = [enableBatching];

export const metaReducers = environment.production
  ? metaReducersProd
  : metaReducersDev;

And then use them as follows:

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, { metaReducers }),
    EffectsModule.forRoot([
      WorkspacesEffects,
      BusesInProgressEffects,
      UsersEffects,
      UiEffects,
      BusesEffects,
      ContainersEffects,
      ComponentsEffects,
      ServiceAssembliesEffects,
      ServiceUnitsEffects,
      SharedLibrariesEffects,
    ]),
    // it'd be nice to have the possibility to activate redux devtools
    // even if we're in prod but only with the extension
    // since ngrx v4, no idea how to do that
    !environment.production
      ? StoreDevtoolsModule.instrument({ maxAge: 50 })
      : [],
    ...
],
  providers,
})
export class CoreModule {}

CoreModule is then imported in the main AppModule.

Version of affected browser(s),operating system(s), npm, node and ngrx:

node 8, typescript 2.4, ngrx 4.0.5, angular 4.1.3 and angular-cli 1.3.1.

@victornoel
Copy link
Author

And actually, it would be wiser to use the stable version of ngrx, because, as I told you in #153 (comment), #153 does break the build in some cases!

@maxime1992
Copy link
Contributor

maxime1992 commented Jul 26, 2017

Also, we've tried to remove all the lazy loading in this app with @victornoel by making named functions exporting the required modules and using those functions with loadChildren but it didn't change anything. So the problem is probably not related to lazy loading

@victornoel
Copy link
Author

I updated the description to reference the correct commit which compiles with ngrx 4.0.1

@victornoel
Copy link
Author

I referenced the other problem in #191.

@ShiftySituation
Copy link

ShiftySituation commented Jul 26, 2017

I've noticed a similar behavior in version 4. In my case, the reducer is only being fired for the last dispatch when there are multiple actions being dispatched from an effect. (not sure if it has to be an effect that dispatches the reducer action to cause this)

Work flow is as follows:
subscribe to the selector
dispatch Update action which is an effect that will...
dispatch UpdateSuccess action which is a reducer
subscription fires

I've added logging to verify that the Update action is dispatching the UpdateSuccess action but in the event of multiple Update actions being fired, even though each Update is dispatching the UpdateSuccess action, the logs in the UpdateSuccess action are only fired for the last UpdateSuccess action that is dispatched. It's almost like there is a debounce causing previous requests to be ignored.

@Bretto
Copy link

Bretto commented Jul 26, 2017

My AOT build is broken aswell...

@brandonroberts
Copy link
Member

Does anyone have a small reproduction of this issue?

@ShiftySituation
Copy link

ShiftySituation commented Jul 28, 2017

@brandonroberts you wouldn't happen to have a plunker with ngrx 4 that I can use to reproduce it, would you? I ask because all I see is version 2 in npmcdn.

@brandonroberts
Copy link
Member

@ShiftySituation sure. Here is a plunker template http://plnkr.co/edit/tpl:757r6L

@ShiftySituation
Copy link

ShiftySituation commented Jul 28, 2017

@brandonroberts http://plnkr.co/edit/HQ5m02PYDxeIfsG180dz?p=preview

instead of calling 'increment' reducer directly, it sends it to an effect, which then calls the 'increment' reducer. I've added a delay in the effect so the 'increment' button can be clicked multiple times and you'll notice in the logs that the reducer only fires once.

@littleStudent
Copy link

@ShiftySituation you should use a mergeMap instead of a switchMap.
if you change that in your effect (you plnkr example) it handles all actions fine.

@ShiftySituation
Copy link

@littleStudent you are 100% right... it fixed the issue in my code as well. To conclude, my issue is a non-issue and therefore not related to the original issue.

@Christian24
Copy link

I have the same problem. Coming from #250. I noticed something interesting: While my app does no longer crash with the nightly that fixes #250, the reducers function that I inject via an InjectionToken still seems to be not executed.

export function getReducers() {
  console.log("Reducers function")
  return {
    entities: entityReducer, variables: variableReducer,
    selectedVariable: selectVariableReducer,
    cost: costReducer,
    scenario: scenarioReducer, solution: solutionReducer,
    level: difficultyReducer, levels: levelsReducer,
    connectionState: connectionStateReducer, session: sessionReducer,
    message: annaMessageReducer, messages: annaMessagesReducer,
    moveVariable: moveVariableReducer
  };
}

And I provide it like so:

 {
      provide: REDUCER_TOKEN,
      useFactory: getReducers
    }

However, in my log Reducers function never appears. While when I disable AOT it does.

brandonroberts added a commit that referenced this issue Aug 10, 2017
The injector behavior is different when compiled through AOT such that
provided tokens aren't resolved before the factory function is executed.
This fix uses the injector to get the reducers provided through tokens.

Reference #189
MikeRyanDev pushed a commit that referenced this issue Aug 10, 2017
…#259)

The injector behavior is different when compiled through AOT such that
provided tokens aren't resolved before the factory function is executed.
This fix uses the injector to get the reducers provided through tokens.

Reference #189
@Christian24
Copy link

With last night's nightly build it works for me perfectly with AOT now.

@halfmatthalfcat
Copy link

Can confirm this now works in the nightly build for me as well.

@cormacrelf
Copy link

cormacrelf commented Aug 14, 2017

Hm, for me (nightly store) it's still broken.

Set a breakpoint in _createStoreReducers (store.es5.js), and inspect the values. injector is an injector, but reducers is actually my AppModule class. As in the constructor function. That's what came back from resolveNgModuleDep.

The dep being fetched was _INITIAL_REDUCERS, which is { useValue: reducers } in Store.forRoot(reducers, config).

I found a workaround, which is:

    { provide: _INITIAL_REDUCERS, useFactory: reducerMapFactory },
    { provide: INITIAL_REDUCERS, useFactory: reducerMapFactory },

(Note: cli 1.3.0, ngrx nightly, angular rest = 4.3.4, zone.js 0.8.16.)

@Bretto
Copy link

Bretto commented Aug 18, 2017

My AOT build is still broken too...
Angular 4.3.4
Ngrx Platform 4.0.4

Uncaught TypeError: Cannot convert undefined or null to object
    at Function.keys (<anonymous>)
export const ReducerTokenTest = new InjectionToken('Registered Reducers Test');

export const reducers = {
  videos: TntReducer.registerReducer(VideoReducers, videosInitState),
  comments: TntReducer.registerReducer(CommentReducers, commentsInitState),
  tags: TntReducer.registerReducer(TagReducers, tagsInitState),
  search: TntReducer.registerReducer(SearchReducers, searchInitState),
  toasts: TntReducer.registerReducer(ToastReducer, toastInitState),
  app: TntReducer.registerReducer(AppReducers, appInitState)
};


export const ReducerProvider = [
  {provide: ReducerTokenTest, useValue: reducers}
];
StoreModule.forRoot(ReducerTokenTest, {metaReducers: MetaReducers}),

@brandonroberts
Copy link
Member

@Bretto use a factory to provide your reducers instead of a value.

export function getReducers() {
  return reducers;
}

export const ReducerProvider = [
  { provide: ReducerTokenTest, useFactory: getReducers }
];

@cormacrelf
Copy link

@brandonroberts is it correct to say the you shouldn't be able to tell the difference between a useValue- and a useFactory-injected value? I thought the injector black-boxed how a value was produced.

@brandonroberts
Copy link
Member

brandonroberts commented Aug 18, 2017

@cormacrelf I would say that's correct. I've found using a factory to be more reliable though in regards to AoT.

@JozefFlakus
Copy link

I had this problem with 4.0.0 - 4.0.2 but seems like 4.0.3 fixed the problem (in case of mine).

@Bretto
Copy link

Bretto commented Aug 20, 2017

@brandonroberts Correct it fix my build. Thanks !

@victornoel
Copy link
Author

@brandonroberts for me it is still broken (I am not using a reducer token, but I don't think it is related: the store is correctly setup, some reducers are executed at first and then it stops working).

You can do a repro with the following:

$ git clone https://gitlab.com/linagora/petals-cockpit.git
$ cd petals-cockpit
$ git checkout 9994938472d2994f07fac6903f2f5735fd5a8be3
$ cd frontend
$ yarn
$ ng serve --prod -e=dev-e2e

Login with admin/admin: nothing happens (but some reducers were triggered before, look at the events in the store devtools plugin) while if you do it with ng serve -e=dev-e2e the reducers are triggered as expected).

@tschuege
Copy link

tschuege commented Aug 21, 2017

@Bretto It seems that I have the same issue as you had. How exactly did you solve it?
Just as @brandonroberts wrote

export const ReducerProvider = [
  { provide: ReducerTokenTest, useFactory: getReducers }
];

and then in the module:
StoreModule.forRoot(ReducerProvider, {metaReducers: MetaReducers})
?

@halfmatthalfcat
Copy link

halfmatthalfcat commented Aug 21, 2017

@tschuege:

const reducerToken: InjectionToken: InjectionToken<ActionReducerMap<YourStateType>> = 
   new InjectionToken<ActionReducerMap<YourStateType>>('Reducers');

export const reducerProvider = { provide: reducerToken, useFactory: getReducers };

...in app.module

imports: [
   StoreModule.forRoot(reducerToken, {...}),
   ...
],
providers: [
   reducerProvider,
   ...
]

@tschuege
Copy link

tschuege commented Aug 21, 2017

@halfmatthalfcat thanks!

But what is "YourStateType"?

I have an interface

export interface AllReducers {
  reducer1: any,
  redicer2: any
}
export const reducerToken: InjectionToken: InjectionToken<ActionReducerMap<AllReducers>> = new InjectionToken<ActionReducerMap<AllReducers>>('Reducers');

but then I get the ide error:

Cannot use new with an expression whose type lacks a call or construct signature.

@halfmatthalfcat
Copy link

YourStateType is just the type of your state that you'd put in the type definition for ActionReducerMap.

@Bretto
Copy link

Bretto commented Aug 21, 2017

@tschuege

StoreModule.forRoot(ReducerTokenTest, {metaReducers: MetaReducers}),
export const ReducerTokenTest = new InjectionToken('Registered Reducers Test');
export const ReducerProvider = [
  {provide: ReducerTokenTest, useFactory: getReducers}
];
 providers: [
    ReducerProvider,
   ...]

@tschuege
Copy link

tschuege commented Aug 22, 2017

@Bretto and @halfmatthalfcat
Thanks for your help.
But it still doesn't work. I have no metaReducers, so I just do
StoreModule.forRoot(ReducerTokenTest, {});
in the imports of the app.module

It works on the first load (empty cache and hard reload). But after, when I do a normal reload I still get TypeError: Cannot convert undefined or null to object
Without AOT it all works fine

@victornoel
Copy link
Author

victornoel commented Aug 22, 2017 via email

@victornoel
Copy link
Author

I updated the description to clarify what is the problem and put the repro steps in it as well as the correct environment versions :)

@maxisam
Copy link
Contributor

maxisam commented Aug 29, 2017

@victornoel my case was solved by using function(){} instead of arrow function in createSelector().

It seems like AOT is picky about arrow function. You might wanna check that.

@brandonroberts
Copy link
Member

@victornoel can you update your reproduction branch to a more recent version of Angular, @ngrx/effects version 4.0.5, and @ngrx/store version 4.0.3?

@victornoel
Copy link
Author

victornoel commented Aug 30, 2017 via email

@brandonroberts
Copy link
Member

brandonroberts commented Aug 30, 2017

@victornoel your package.json in the description is pinned to store 4.0.0, effects 4.0.1 and Angular 4.1.3 https://gitlab.com/linagora/petals-cockpit/blob/9994938472d2994f07fac6903f2f5735fd5a8be3/frontend/package.json

@victornoel
Copy link
Author

@brandonroberts sorry, I was sure I did it… but it seems it is not the case :)
Unfortunately, using the latest version of ngrx deps gives me this error during compile with AOT:

ERROR in /home/vnoel/Linagora/Petals/dev/git/petals-cockpit/frontend/src/$$_gendir/node_modules/@ngrx/store/store.ngfactory.ts (51,65): Argument of type '{}' is not assignable to parameter of type 'StoreFeature<any, any>[]'.
  Property 'includes' is missing in type '{}'.

I will try to see if I can fix this, I will ping you when it's ok (or if you have a solution? :)

Concerning Angular itself, we don't want to use a newer version because of angular/angular#18129 but I can upgrade it for you to test if needed (because it only impacts dev mode).

@brandonroberts
Copy link
Member

@victornoel I believe the type error is a bug in the compiler in version 4.1.3. The error goes away if you upgrade to a version of Angular greater than 4.1.

@victornoel
Copy link
Author

@brandonroberts good news, indeed:

  1. it compiles with angular >4.1
  2. this issue is fixed by latest version of ngrx

Sorry for not realising it sooner, I was convinced I already tried with latest :)

@brandonroberts
Copy link
Member

Great!

@lufonius
Copy link

lufonius commented Mar 12, 2019

It doesn't seem like this is fixed. I read about a workaround with Object.assign(token, reducers) before initialising the module, but that also did not work for me. I made a reproduction, just download it, install deps and run it with ng serve and then ng serve --aot. In my case, the reducer completely vanishes in aot mode (see store devtools or the console output). I injected the feature reducers as written in the docs.

Edit: Angular Version 7.2.8, @ngrx/store 7.3.0

@timdeschryver
Copy link
Member

@lufonius move the feature module to its own separate file and it should work.

@lufonius
Copy link

@timdeschryver hi, yes it's working now. Thanks a lot! I did not move it to a separate file as I had the same problem with a project at work, but the feature used to be in a separate file there. not quite sure now, what the problem is ... having a look at it :)

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

No branches or pull requests