kostas.kapasakis
kostas.kapasakis

Reputation: 1142

Lazy Feature Modules architecture migration to NGRX: Angular 8

Hey guys currently i am working in an application with Lazy features modules architecture implemented. Below you can see how the project is structured.

enter image description here

Since the application is keep on growing we decided to migrate it to Ngrx.

As is a new pattern for me i am searching for migration guidelines but i can only find ngrx guidelines when creating a project from scratch.

Could you please give me some hints, guidelines,where should i be careful , and possibly some steps summary?

Thanks you.

Upvotes: 0

Views: 1741

Answers (1)

Andrew Allen
Andrew Allen

Reputation: 8032

guidelines

It's possible to lazy load your store but this has caused me more issues than benefits. For example getting a selected project based on the router url and loaded project entities which mixes two feature stores. The following article has provided me a nice way to split up the store whilst allow any part of the application access to store data:

https://itnext.io/ngrx-best-practices-for-enterprise-angular-applications-6f00bcdf36d7

For posterity the application structure looks like:

├── app
 │ ├── app-routing.module.ts
 │ ├── app.component.css
 │ ├── app.component.html
 │ ├── app.component.ts
 │ ├── app.module.ts
 │ ├── components
 │ ├── containers
 │ │    └── my-feature
 │ │         ├── my-feature.component.css
 │ │         ├── my-feature.component.html
 │ │         └── my-feature.component.ts
 │ ├── models
 │ │    ├── index.ts
 │ │    └── my-model.ts
 │ │    └── user.ts
 │ ├── root-store
 │ │    ├── index.ts
 │ │    ├── root-store.module.ts
 │ │    ├── selectors.ts
 │ │    ├── state.ts
 │ │    └── my-feature-store
 │ │    |    ├── actions.ts
 │ │    |    ├── effects.ts
 │ │    |    ├── index.ts
 │ │    |    ├── reducer.ts
 │ │    |    ├── selectors.ts
 │ │    |    ├── state.ts
 │ │    |    └── my-feature-store.module.ts
 │ │    └── my-other-feature-store
 │ │         ├── actions.ts
 │ │         ├── effects.ts
 │ │         ├── index.ts
 │ │         ├── reducer.ts
 │ │         ├── selectors.ts
 │ │         ├── state.ts
 │ │         └── my-other-feature-store.module.ts
 │ └── services
 │      └── data.service.ts
 ├── assets
 ├── browserslist
 ├── environments
 │ ├── environment.prod.ts
 │ └── environment.ts
 ├── index.html
 ├── main.ts
 ├── polyfills.ts
 ├── styles.css
 ├── test.ts
 ├── tsconfig.app.json
 ├── tsconfig.spec.json
 └── tslint.json

where should i be careful

Make sure your reducer returns the state unamended for undefined actions. You can test for this. There's no excuse not to test your reducers. These are pure functions and easy to test.

import * as fromProjects from './project.reducer'
import * as fromProjectState from './project.state'

describe('ProjectReducer', () => {
  describe('undefined action', () => {
    it('should return the default state', () => {
      const { initialState } = fromProjectState
      const action = {} as any
      const state = fromProjects.reducer(initialState, action)

      expect(state).toBe(initialState)
    })
  })
})

Take the time to ensure your actions types are correct - the errors are hard to debug. Due to boilerpate you'll likely copy and paste alot of code here. Again this can be tested.

describe('LoadProjectsFail', () => {
  it('should create an action', () => {
    const payload = { message: 'Load Error ' }
    const action = new fromProjects.LoadProjectsFail(payload)
    expect({ ...action }).toEqual({
      type: fromProjects.LOAD_PROJECTS_FAIL,
      payload,
    })
  })
})

Keep to the NgRx documentation - there's been a few changes and tutorials are normally at least one version behind. e.g.

this.store.pipe(select(projectSelectors.getProjectsLoading))
// versus
this.store.select(projectSelectors.getProjectsLoading)

steps summary

Pretty much the same as the link but a different order:

Root Store

  1. Write root-store.module.ts
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools'
import { environment } from 'src/environments/environment'
// import { MyFeatureStoreModule } from './my-feature-store/';
// import { MyOtherFeatureStoreModule } from './my-other-feature-store/';

@NgModule({
  imports: [
    CommonModule,
    // MyFeatureStoreModule,
    // MyOtherFeatureStoreModule,
    StoreModule.forRoot({}),
    EffectsModule.forRoot([])
    // Store devtools
    !environment.production
      ? StoreDevtoolsModule.instrument({
          name: 'My App',
        })
      : [],
  ],
  declarations: []
})
export class RootStoreModule {}

Add the following files which will start of pretty much empty:

  • index.ts

    import { RootStoreModule } from './root-store.module'
    import * as RootStoreState from './state'
    import * as RootStoreSelectors from './selectors'
    //export * from './employee'
    //export * from './project'
    //export * from './router'
    export { RootStoreState, RootStoreSelectors, RootStoreModule }

  • state.ts

    import { routerState } from './router'
    import { employeeState } from './employee'
    import { projectState } from './project'

    export interface State {
      router: routerState.State
      employees: employeeState.State
      projects: projectState.State
    }

  • selectors.ts

    import { createSelector } from '@ngrx/store'
    import { Project } from './project/project.model'
    import { routerSelectors } from './router'
    import { projectSelectors } from './project'

    export const getSelectedProject = createSelector(
      projectSelectors.getProjectsEntities,
      routerSelectors.getRouterState,
      (entities, router): Project => {
        return router.state && entities[router.state.params.id]
      }
    )

  1. Import RootStoreModule into app.module.ts

Feature Store

  1. Define your Feature State
  2. Define Feature Actions
  3. Write your Feature Effects
  4. Write your Feature Reducer (Test first if possible)
  5. Write your Feature Module
  6. Write your index.ts, add to root index.ts
  7. Add Feature State to Root State
  8. Declare Feature Module as part of Root Store module.
  9. Define Feature Selectors
  10. Define any Root Selectors (mixes feature selectors)

Stackblitz

Upvotes: 3

Related Questions