Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Your Code Should Always Feel Like Home.

Slide 5

Slide 5 text

@GregOnNet Gregor Woiwode CTO

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

/* ... */ 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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Property Pattern SIMPLIFY STATE MUTATIONS

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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, }

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

@ngrx/entity SIMPLIFY REDUCERS

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

@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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Demo ENTITY ADAPTER

Slide 26

Slide 26 text

PROPERTY PATTERN Review Less Code Duplication Reusable Mutators & Selectors

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

createReducer SIMPLIFY REDUCER INFRASTRUCTURE

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Demo REMOVE SWITCH-CASE-STATEMENTS

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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]) ) ); } }

Slide 36

Slide 36 text

@ngrx/router-store SIMPLIFY LOCATION STATE HANDLING

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

@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 };

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Demo COMPRESS ROUTER STATE

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

“ I see reccurring patterns…

Slide 48

Slide 48 text

Looking for the right answer…

Slide 49

Slide 49 text

ngrx-data SIMPLIFY STORE USING CONVENTIONS

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

Demo CONFIGURE NGRX-DATA

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

ngrx-ducks Leverage OCP for Redux

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

NGRX Redux the good parts ACTIONS

Slide 64

Slide 64 text

Your Code Should Always Feel Like Home.

Slide 65

Slide 65 text

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?

Slide 66

Slide 66 text

NGRX Duck, Duck, Ducks, Re-Ducks

Slide 67

Slide 67 text

Demo INTRODUCING NGRX-DUCKS

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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