Skip to content
This repository has been archived by the owner on Jan 10, 2018. It is now read-only.

Using combineReducers fails with AoT compilation #190

Closed
brandonroberts opened this issue Aug 16, 2016 · 27 comments
Closed

Using combineReducers fails with AoT compilation #190

brandonroberts opened this issue Aug 16, 2016 · 27 comments
Labels

Comments

@brandonroberts
Copy link
Member

brandonroberts commented Aug 16, 2016

When running AoT compilation, if I use the combineReducers function, ngc throws an error.

Error encountered resolving symbol values statically. Calling function 'combineReducers', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol reducer

app.module.ts

import { reducer } from './reducer’;

@NgModule({
  imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
  ],
  bootstrap: [AppComponent]
})

reducer.ts

import { combineReducers } from '@ngrx/store';
import * as menu from './menu';

export interface State {
  menu: menu.state;
}

export const reducer = combineReducers({
  menu: menu.reducer
});

ngrx/store v 2.1.2

@wizardnet972
Copy link

wizardnet972 commented Aug 17, 2016

try to use:

import { combineReducers } from '@ngrx/store';
import { compose } from '@ngrx/core/compose';
import * as menu from './menu';

export interface State {
  menu: menu.state;
}

export const reducer = compose(combineReducers)({
   menu: menu.reducer
});

@brandonroberts
Copy link
Member Author

No dice

@mhamel06
Copy link

mhamel06 commented Sep 2, 2016

@brandonroberts I was able to get it to work by changing the initial reducer to useFactory instead of useValue and passing in my rootReducer factory. mhamel06@fdbb79a

This would be a breaking change for existing users but I think it is the simplest way to make ngrx/store work well with AoT compilation moving forward.

My code now looks like this

@NgModule({
  imports: [
...
    StoreModule.provideStore(rootReducer),
...
  ],
export function rootReducer(){
  return compose(combineReducers)({
     menu: menu.reducer
  });
}

@brandonroberts
Copy link
Member Author

@mhamel06 thanks for testing this out. I had the same idea but haven't had a chance to confirm it.

cc: @qdouble

@qdouble
Copy link

qdouble commented Sep 2, 2016

@brandonroberts @mhamel06 thanks, works great. I was trying this out something like this earlier but didn't import directly into provideStore so it wasn't working.

@qdouble
Copy link

qdouble commented Sep 2, 2016

might have spoke too soon, although it compiles, I'm getting undefined errors when routing to routes that use the store

@mhamel06
Copy link

mhamel06 commented Sep 2, 2016

I get undefined errors when I'm using the current @ngrx/store version but they go away for me when I change this line:
https://github.com/ngrx/store/blob/master/src/ng2.ts#L52
from:

{ provide: _INITIAL_REDUCER, useValue: _reducer }

to

{ provide: _INITIAL_REDUCER, useFactory: _reducer }

Without that change I get the following

zone.js:469Error: Uncaught (in promise): TypeError: Cannot read property '...' of undefined(…)

@brandonroberts
Copy link
Member Author

@mhamel06 cool. Easy fix

@mhamel06
Copy link

mhamel06 commented Sep 11, 2016

This worked for me with current @ngrx/store version:
https://github.com/ngrx/example-app/pull/43/files#diff-0c230c2c950beb1f5987a2aed0eeeab3R75

@DzmitryShylovich
Copy link

is there any reason why #190 (comment) is not merged into master?

@brandonroberts
Copy link
Member Author

The fix isn't needed as you can provide a function that returns the reducer to be compatible with AoT mode.

@DzmitryShylovich
Copy link

reducer to be compatible with AoT mode

for example? :)

but wouldn't it be nice to have a aot compatible lib out of the box?

@brandonroberts
Copy link
Member Author

It is AoT compatible out of the box. If you don't use AoT you don't have to use a function to return the root reducer. There could be some documentation added for the AoT requirement though.

@mhamel06
Copy link

From the example repo this worked for me:
https://github.com/ngrx/example-app/blob/bc6f113cc0907f986b6fedd5d0f5ccfed278219f/src/reducers/index.ts#L82-L100

const reducers = {
  search: fromSearch.reducer,
  books: fromBooks.reducer,
  collection: fromCollection.reducer,
  layout: fromLayout.reducer,
  router: fromRouter.routerReducer,
};

const developmentReducer = compose(storeFreeze, combineReducers)(reducers);
const productionReducer = combineReducers(reducers);

export function reducer(state: any, action: any) {
  if (PROD) {
    return productionReducer(state, action);
  }
  else {
    return developmentReducer(state, action);
  }
}
imports:[
...
StoreModule.provideStore(reducer),
...
]

@DzmitryShylovich
Copy link

ok, I've got it.

@brandonroberts
Copy link
Member Author

Closing since it works as designed with a function for AoT

@wethinkagile
Copy link

It should be noted that you should not only use an untyped function instead of typed const in the rootReducer like in @mhamel06 example, but also in the reducers themselves, e.g.:

Failing with ng2-final AoT:

export const settings: ActionReducer<SettingsModel> = (state:SettingsModel = new SettingsModel(), action:Action) => {

    switch (action.type) {
        case ActionType.SET_SETTINGS:
            return (action.payload !== null) ? action.payload : new SettingsModel();
        case ActionType.UPDATE_SETTINGS:
            console.log(Object.assign(state, {pin: Object.assign(state.pin, action.payload.pin)}));
            return Object.assign({}, state, {pin: Object.assign(state.pin, action.payload.pin)});
        default:
            return state;
    }
};

Working with ng2-final AoT:

export function settings (state:SettingsModel = new SettingsModel(), action:Action) {

    switch (action.type) {
        case ActionType.SET_SETTINGS:
            return (action.payload !== null) ? action.payload : new SettingsModel();
        case ActionType.UPDATE_SETTINGS:
            console.log(Object.assign(state, {pin: Object.assign(state.pin, action.payload.pin)}));
            return Object.assign({}, state, {pin: Object.assign(state.pin, action.payload.pin)});
        default:
            return state;
    }
};

@born2net
Copy link

born2net commented Dec 5, 2016

so in efforts to test the new --hrm feature I am running .22 with the following latest config:
ng serve --aot false --hmr -e=hmr
and while I get no error in compilation and everything seems to work fine, my fac does not get executed.
The setup is:


export function fac (ngRedux: NgRedux<any>, devTools: DevToolsExtension) {
    alert('running');
    const reducers = combineReducers({notify, sample_reducer});
    const middlewareEnhancer = applyMiddleware(<any>thunkMiddleware);
    const applyDevTools = () => devTools.isEnabled() ? devTools.enhancer : f => f;
    const enhancers: any = compose(middlewareEnhancer, applyDevTools);
    const store = createStore(reducers, enhancers);
    ngRedux.provideStore(store);
    return new AppStore(store);
}

export var providing = [{
    provide: AppStore, useFactory: fac, deps: [NgRedux, DevToolsExtension]
}, {
    provide: "OFFLINE_ENV",
    useValue: false
}, {
    provide: SampleActions,
    useClass: SampleActions
}];


@NgModule({
    declarations: [
        AppComponent,
        // MyComp
    ],
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        // MsLibModule.forRoot(),
        // NgReduxModule.forRoot(), //toggle
        MaterialModule.forRoot()
    ],
    providers: [providing],
    bootstrap: [AppComponent]
})
export class AppModule {
    constructor() {
    }
}

regards,

Sean.

@emreavsar
Copy link

for others struggling with AOT (especially because of using angular-cli) in combination with the comment of the current lazy loading reducers workaround from here: #197 (comment)

this is the working solution:

const eagerLoaded = {};

export function reducer(lazyLoaded = {}) {
  let allReducers = Object.assign(eagerLoaded, lazyLoaded);
  return combineReducers(allReducers);
}

@josh-sachs
Copy link

josh-sachs commented Mar 7, 2017

I cannot make this work... fails with "Calling function 'createReducer', function calls are not supported. "

file1
export const APP_CORE_STATE_REDUCERS = {...};

file2

import { APP_CORE_STATE_REDUCERS } from 'file1'
export function createReducer(asyncReducers = {}) {
    const allReducers = Object.assign(APP_CORE_STATE_REDUCERS, asyncReducers);
    return combineReducers(allReducers);
}

The following works JIT and fails AoT

import { createReducer } from 'file2'
import { StoreModule } from '@ngrx/store';
export const rootReducer = createReducer();
@NgModule({
     imports: [StoreModule.provideStore(rootReducer)]
}

The following fails JIT and works AoT

import { createReducer } from 'file2'
import { StoreModule } from '@ngrx/store';
@NgModule({
     imports: [StoreModule.provideStore(createReducer)]
}

The following works at JIT and AoT but doesn't work to solve for lazy loaded reducers #197 (comment)

import { APP_CORE_STATE_REDUCERS } from 'file1'
import { StoreModule } from '@ngrx/store';
@NgModule({
     imports: [StoreModule.provideStore(APP_CORE_STATE_REDUCERS)]
}

@josh-sachs
Copy link

What I wasn't able to gather immediately from this thread was that the factory method arguments need to be provided to the createReducer return value. I'm chiming in to hopefully help others.

export const EAGER_REDUCERS = {
    x: xReducer,
    y: yReducer,
    ....
}

export function createReducer(asyncReducers = {}) {    
    let allReducers = Object.assign(EAGER_REDUCERS, asyncReducers)
    return combineReducers(allReducers); 
}

export function appReducer(state: any, action: any) {
    return createReducer()(state, action);
}

@NgModule({
    imports: [StoreModule.provideStore(appReducer)],
}) export class AppCoreStateModule { };

more conversation here about lazy loading techniques: #197

@alsoicode
Copy link

When I follow the method outlined by @mhamel06 on Sept 14, I get undefined for all of my state. I actually have to call the function in .provideStore():

StoreModule.provideStore(rootReducer())

and my state isn't undefined, but it also fails AOT compilation.

@josh-sachs
Copy link

@alsoicode if you see my comment above that will make it work.

@alsoicode
Copy link

@josh-sachs I was able to get it working thanks to your comment. I ended up going with:

export function clearStore(reducer: Function) {
  return function(state: any, action: any) {
    if (action.type === AuthActions.LOGOUT_SUCCESS) {
      state = undefined;
    }

    return reducer(state, action);
  };
}

export function createReducer(asyncReducers = {}) {
  let allReducers = Object.assign(reducers, asyncReducers);
  return compose(clearStore, combineReducers)(allReducers);
}

export function appReducer(state: any, action: any) {
  return createReducer()(state, action);
}

as I needed to clear the store on logout.

@kesemdavid
Copy link

@josh-sachs Hello, I've successfully used your posted code and the error was gone, I cannot seem to figure out how the state is now being provided to the provideStore?

Initially i used StoreModule.provideStore(buildReducers(), buildInitialState()) following the d.ts.
I cannot see a provideStore(reducers:any) on the d.ts nor understand how does it know what is the initial state to use.

Please provide some explanation or direct me to the right place to understand.
Thanks in advance.

@josh-sachs
Copy link

@kesemdavid my post from march 18 is for the 2.x release branch. Can you confirm you aren't trying to follow the 4.x release typings? The process for providing store to the application has change significantly between the two versions.

@kesemdavid
Copy link

@josh-sachs How did it change exactly? Im using "@ngrx/store": "^2.2.3" and this is the only way I'm familiar with in order to provide a store to the application..
Can you direct me what exactly information you're looking for in order to provide the explanation i seek for?

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

No branches or pull requests