Search code examples
angulartypescriptngxs

NGXS lazy-loaded module has different state than the rest of the app


I have a CoreModule which is eagerly loaded and contains the main app state, and a ContractHolder.module which uses NgxsModule.forFeature([ContractHolderState]) to declare a new state to be added to the main one when it is loaded. However, the components in the lazy-loaded module see an initial state of {} (so not initialized as defined in ContractHolderState), and later this state is updated when ContractHolderState's @Action handlers update the state.

In the rest of the app, I see the app state containing the initialized ContractHolderState but it is not updated when ContractHolderState updates it!

So in short, my lazy-loaded module does not see the app state at all, but instead sees an empty object which is updated by the lazy state ContractHolderState, while the rest of the application sees the initial value defined for ContractHolderState but it is never updated.

Code:

Core.module, which defines the main app state and is imported by App.module:

const ngxsStates = [ConfigurationState, AuthenticationAuthorizationState];
@NgModule({
    imports: [
        NgxsStoragePluginModule.forRoot({
            key: appStateNames.authorization,
        }),
        NgxsModule.forRoot(ngxsStates, { developmentMode: !environment.production }), //  <--- NGXS states
        NgxsReduxDevtoolsPluginModule.forRoot(),
        NgxPermissionsModule.forRoot(),
    ],
    providers: [...],
    exports: [...],
    declarations: [...],
})
export class CoreModule {}

I have ContractHolder.module which is lazy-loaded:

@NgModule({
    declarations: [MainViewComponent],
    imports: [
        CommonModule,
        CoreModule, // <--- imports core module with the main state
        RouterModule.forChild([{
            path: '',
            component: MainViewComponent,
        }]),
        NgxsModule.forFeature([ContractHolderState]) // <--- declares state
    ],
})
export class ContractHolderModule { }

Here's the ContractHolderState:

@State<ContractHolderStateModel>({
    name: appStateNames.contractHolders,
    defaults: {
        contractHolders: [],
    },
})
export class ContractHolderState {
    @Selector()
    static contractHolders(state: ContractHolderStateModel): IContractHolder[] {
        return state.contractHolders;
    }
}

This is how ContractHolder.module is lazily loaded by the routing definition in app-routing.module:

const routes: Routes = [
    {
        path: 'home-page',
        component: PageComponent,
        children: [
            {
                path: 'ch',
                outlet: 'page',
                loadChildren:
    (): Promise<ContractHolderModule> => import('./contract-holder/contract-holder.module')
                    .then(m => m.ContractHolderModule) // <--- lazy-loaded ContractHolder.module
            },
        ]
    },
    {
        path: '**',
        component: LoginComponent
    }
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule],
})
export class AppRoutingModule {}

Now the main-view.component used in the contract holder module looks at the state via @Select(ContractHolderState.contractHolders) allCh$: Observable<IContractHolder[]>;. I also tried looking at this.store.snapshot() which returned an empty object. After ContractHolderState updated the state it is no longer an empty object, but the rest of the state (from the rest of the app) is never there.

While this is happening, the main app sees the full state but the ContractHolderState always has its default value of {contractHolders: []} and it is never updated.


Solution

  • The problem was with importing CoreModule which defines the main state more than once. We had multiple modules importing core module. I kept the core module imported by the App module and all other functionality is now in a new SharedModule.