Upgrade to Pro — share decks privately, control downloads, hide ads and more …

NgRx Refactoring Patterns enhanced with ngrx-ducks

NgRx Refactoring Patterns enhanced with ngrx-ducks

In this talk, you will learn about the following mechanisms to improve the maintainability of your Redux Store.

- Property Pattern
- createReducer Factory
- @ngrx/entity
- @ngrx/router-store
- ngrx-data
- ngrx-ducks (sneak peak)

Gregor Woiwode

September 06, 2018
Tweet

More Decks by Gregor Woiwode

Other Decks in Programming

Transcript

  1. Redux Refactoring Patterns
    @GregOnNet
    co-IT.eu GmbH
    We Empower Outstanding Business Solutions
    +

    View Slide

  2. MY FRIEND OSCAR
    Meet Oscar
    1. My Colleage
    2. He works with
    3. Passionated about Software Development

    View Slide

  3. MY FRIEND OSCAR
    Meet Oscar
    1. …He is not an early morning person.

    View Slide

  4. Your Code Should
    Always
    Feel
    Like
    Home.

    View Slide

  5. @GregOnNet
    Gregor Woiwode
    CTO

    View Slide

  6. co-IT.eu GmbH
    We Empower Outstanding Business Solutions
    “ Collect your questions for the Q&A session, please.
    @GregOnNet

    View Slide

  7. AGENDA
    Our Review With Oscar
    1. Property Pattern
    2. @ngrx/entity
    3. @ngrx/router-store
    4. selectors
    5. ngrx-data
    6.???

    View Slide

  8. REDUX ARCHITECTURE
    Recap
    ACTION
    STATE
    COMPONENT STORE
    Dispatch
    Send To
    Mutate
    Render
    Inspired by @ToddMotto
    REDUCERS

    View Slide

  9. export function reducer(state = initialState, action: NotesActions): State {
    switch (action.type) {
    case NotesActionTypes.CreateNoteSuccess: {
    return {
    ...state,
    notes: [...state.notes, action.payload],
    loaded: true,
    loading: false
    }
    }
    case NotesActionTypes.LoadNotesSuccess: {
    return {
    ...state,
    notes: ...action.payload,
    loaded: true,
    loading: false
    }
    /* ... */
    Redundant Code

    View Slide

  10. export function reducer(state = initialState, action: NotesActions): State {
    switch (action.type) {
    case NotesActionTypes.CreateNoteSuccess: {
    return {
    ...state,
    notes: [...state.notes, action.payload],
    loaded: true,
    loading: false
    }
    }
    case NotesActionTypes.LoadNotesSuccess: {
    return {
    ...state,
    notes: ...action.payload,
    loaded: true,
    loading: false
    }
    /* ... */
    Redundant Code

    View Slide

  11. /* ... */
    case NotesActionTypes.LoadAdditionalNotesSuccess: {
    return {
    ...state,
    notes: [...state.notes, ...action.payload],
    loaded: true,
    loading: false
    }
    }
    case NotesActionTypes.DeleteSingleNoteSuccess: {
    return {
    ...state,
    notes: state.notes.filter(note => note.guid !== action.payload.guid),
    loaded: true,
    loading: false
    }
    }
    /* ... */
    Remove By Filtering

    View Slide

  12. OSCAR‘S PROBLEM
    Large Switch-Case-Statements
    1. Lot‘s of list operations we may not need every time
    2. Hard to maintain
    3. Less readable

    View Slide

  13. Property Pattern
    SIMPLIFY STATE MUTATIONS

    View Slide

  14. PROPERTY PATTERN
    Convert Collections To Dictionaries
    export interface State {
    notes: Note[];
    }
    export interface State {
    notes: { [key: string]: Note };
    }
    List to Object

    View Slide

  15. PROPERTY PATTERN
    Delete Single Object
    return {
    ...state,
    notes: state.notes.filter(note =>
    note.guid !== action.payload.guid
    )
    }
    const {
    [action.payload.guid]: deleted,
    ...withoutDeleted
    } = state.notes;
    return {
    ...state,
    notes: withoutDeleted,
    }

    View Slide

  16. MY FRIEND OSCAR
    Oscar asks…
    Maybe accessing data is more efficient.
    But the code complexity has increased.
    How can we benefit from this approach?

    View Slide

  17. PROPERTY PATTERN
    Review
    Less Collection Operations
    More Performant Operations
    Less Readable Code

    View Slide

  18. @ngrx/entity
    SIMPLIFY REDUCERS

    View Slide

  19. @ngrx/entity
    Entity State
    export interface State extends EntityState {}
    export interface State {
    ids: string[];
    entities: { [id: string]: T };
    }
    Leverages
    Property
    Pattern

    View Slide

  20. @ngrx/entity
    Entity Adapter
    import { createEntityAdapter } from '@ngrx/entity';
    const adapter = createEntityAdapter();
    The mighty helper

    View Slide

  21. @ngrx/entity
    Entity Mutators
    Operations
    for single
    objects and
    collections

    View Slide

  22. @ngrx/entity
    Simplify Mutations
    return adapter.removeOne(
    action.payload,
    state
    );
    const {
    [action.payload.guid]: deleted,
    ...withoutDeleted
    } = state.notes;
    return {
    ...state,
    notes: withoutDeleted,
    }
    1. Enusres Immutability
    2. Applies State Mutation

    View Slide

  23. @ngrx/entity
    Entity Selectors
    const adapter = createEntityAdapter();
    export const {
    selectAll,
    selectEntities,
    selectIds,
    selectTotal
    } = adapter.getSelectors();

    View Slide

  24. @ngrx/entity
    Use Entity Selectors
    export class NotesDashboardComponent {
    notes$: Observable;
    constructor(private store: Store) {
    this.notes$ = this.store.pipe(select(fromNotes.selectAll));
    }
    }

    View Slide

  25. Demo
    ENTITY ADAPTER

    View Slide

  26. PROPERTY PATTERN
    Review
    Less Code Duplication
    Reusable Mutators & Selectors

    View Slide

  27. MY FRIEND OSCAR
    Oscar asks…
    @ngrx/entity is nice, thanks.
    Can we tidy up these Switch-Case-Statements now?

    View Slide

  28. createReducer
    SIMPLIFY REDUCER INFRASTRUCTURE

    View Slide

  29. CREATE REDUCER
    Switch-Case-Anatomy
    Infrastructure UI-Logic
    case: a
    case: b
    case: …

    View Slide

  30. CREATE REDUCER
    Factory Pattern To The Rescue
    const mutators: ActionHandlers = {
    [NotesActionTypes.CreateNote]: notes.addOne,
    [NotesActionTypes.LoadNotesSuccessful]: notes.addMany
    };
    export function reducer(state = initialState, action) {
    return createReducer(mutators)(state, action);
    }
    Refer to last slide for the source

    View Slide

  31. Demo
    REMOVE SWITCH-CASE-STATEMENTS

    View Slide

  32. PROPERTY PATTERN
    Review
    Reduced Infrastructure Code
    Reducers do not need to be
    enhanced as Requirements
    change

    View Slide

  33. MY FRIEND OSCAR
    Oscar asks…
    Small things can have a great impact to quality.
    But wait since you are here…

    View Slide

  34. OSCAR‘S PROBLEM
    He thinks he breakse the Redux Pattern
    Shouldn’t the store be able to answer every question
    of a Component?

    View Slide

  35. SINGLE SOURCE OF TRUTH
    How to deal with location state?
    /* ... */
    export class NoteDetailsComponent {
    note$: Observable;
    constructor(
    private route: ActivatedRoute,
    private store: Store) {
    this.note$ = this.route.params.pipe(
    switchMap(params => this.store.pipe(state =>
    state.notes.entities[params.guid])
    )
    );
    }
    }

    View Slide

  36. @ngrx/router-store
    SIMPLIFY LOCATION STATE HANDLING

    View Slide

  37. @ngrx/router-store
    Setup
    import { StoreRouterConnectingModule } from '@ngrx/router-store';
    @NgModule({
    imports: [
    /* ... */
    StoreRouterConnectingModule,
    ]
    /* ... */
    })
    export class AppModule {}

    View Slide

  38. @ngrx/router-store
    Types & Reducer Come For Free
    import * as fromRouter from '@ngrx/router-store';
    export interface State {
    routerReducer: fromRouter.RouterReducerState;
    }
    export const reducers: ActionReducerMap = {
    routerReducer: fromRouter.routerReducer
    };

    View Slide

  39. @ngrx/router-store
    Enhance The State Tree
    Store
    Notes
    Router State provided by
    @ngrx/router-store

    View Slide

  40. @ngrx/router-store
    Access The Router State
    import { createFeatureSelector } from '@ngrx/store';
    export const getRouterState = createFeatureSelector('routerReducer');

    View Slide

  41. @ngrx/router-store
    Read Router State
    export const currentDetails = createSelector(
    getEntities,
    getRouterState,
    (notes, router) => entities[router.state.params.guid]
    );
    RoutingComponent
    select

    View Slide

  42. @ngrx/router-store
    The Reducer‘s Outcome
    Let’s traverse through the
    whole state tree!
    Of Cause Not!

    View Slide

  43. Demo
    COMPRESS ROUTER STATE

    View Slide

  44. PROPERTY PATTERN
    Review
    Store is Single Source of Truth again
    Reduced Complexity of Component
    Component does not rely on ActivatedRoute anymore

    View Slide

  45. MY FRIEND OSCAR
    Oscar asks…
    This feels more correct than before, thanks.
    Wait! There is one very last thing…

    View Slide

  46. OSCAR‘S PROBLEM
    I Can See A Revenent Pattern….
    We only do CRUD operations.
    Do we need to repeat ourselves for every entity?

    View Slide

  47. “ I see reccurring patterns…

    View Slide

  48. Looking for the
    right answer…

    View Slide

  49. ngrx-data
    SIMPLIFY STORE USING CONVENTIONS

    View Slide

  50. Know, Can
    Do…
    Put Your
    Know-How
    Into Action!

    View Slide

  51. ngrx-data
    Overview
    Actions
    Reducers
    Effects
    Data Services
    Dynamically sets up…

    View Slide

  52. Configure an entity
    export const entityMetadata: EntityMetadataMap = {
    Note: {
    selectId: (note: Note) => note.guid
    }
    };
    export const pluralNames = { Note: 'Notes' };
    ngrx-data

    View Slide

  53. Setup Entity Cache
    ngrx-data
    NgrxDataModule.forRoot({
    entityMetadata,
    pluralNames
    }),

    View Slide

  54. ngrx-data
    Data Services
    import { Note } from '../../models/note';
    @Injectable()
    export class NotesBoard extends EntityCollectionServiceBase {
    constructor(entityServiceFactory: EntityCollectionServiceFactory) {
    super('Note', entityServiceFactory);
    }
    }

    View Slide

  55. Convention Based Data Services
    ngrx-data
    localhost:4200/api/notes/
    root
    entityName
    parameters

    View Slide

  56. ngrx-data
    Built-In Functions Of A Data Service
    Mutators &
    Selectors for the
    given entity.

    View Slide

  57. Demo
    CONFIGURE NGRX-DATA

    View Slide

  58. ngrx-data
    Get Rid Of Unneeded Files
    Delete
    Delete
    Custom
    Configuration

    View Slide

  59. PROPERTY PATTERN
    Review
    Less Files to maintain
    Scales good for many entities
    Highly Configurable
    Handles Loading State automatically
    High Learning Curve
    Lot‘s of things happen behind the scenes

    View Slide

  60. ngrx-ducks
    Leverage OCP for Redux

    View Slide

  61. NGRX
    Criticism
    Touch lots of files
    Boilerplate vs. Explicit Setup
    Distributed Setup
    (Actions, Reducers, Selectors, Effects)
    It is easy to forget parts of the setup
    (ActionType)
    High Learning Curve
    Lot‘s of things happen behind the scenes

    View Slide

  62. NGRX
    Redux the bad parts
    REDUCER
    ACTION TYPES
    DISPATCHER
    ACTIONS
    EFFECT
    ACTION REDUCER MAP
    ACTIONS$
    ACTION CREATORS

    View Slide

  63. NGRX
    Redux the good parts
    ACTIONS

    View Slide

  64. Your Code Should
    Always
    Feel
    Like
    Home.

    View Slide

  65. NGRX
    What if…
    1. … we could automate the creation of an action
    2. … an Action knows about it‘s case reducer
    3. … an Acion could dispatch itself
    4. … we have one object strucure providing us every thing we need?

    View Slide

  66. NGRX
    Duck, Duck, Ducks, Re-Ducks

    View Slide

  67. Demo
    INTRODUCING NGRX-DUCKS

    View Slide

  68. PROPERTY PATTERN
    Review
    Consume Redux as a Service
    TypeScript Compiler saves us for
    mistakes
    Serves Open Closed Principle
    Supports AoT
    Supports AoT … `
    Setting up Dynamic Services is hard
    Could improve handling for Effects

    View Slide

  69. co-IT.eu GmbH
    We Empower Outstanding Business Solutions
    Thank you!
    @GregOnNet
    e.co-it.eu/dwx18-gift
    e.co-it.eu/dwx18-ng

    View Slide

  70. CREDITS
    Icons
    - from www.flaticon.com by Vectors Market
    - from www.flaticon.com by Freepik
    - from www.flaticon.com by Freepik

    View Slide