Slide 1

Slide 1 text

State Management with NgRx Master centralized state management with Classic NgRx, using actions, reducers, effects, and selectors for scalable applications!

Slide 2

Slide 2 text

NgRx Classic Motivation What Is State?

Slide 3

Slide 3 text

NgRx Classic Motivation Dom Is State

Slide 4

Slide 4 text

NgRx Classic Motivation Browser Storage Is State

Slide 5

Slide 5 text

NgRx Classic Motivation Route Is State http://localhost:4200/users?page=1&count=30&sort=asc

Slide 6

Slide 6 text

NgRx Classic Motivation Presentational Components can have State

Slide 7

Slide 7 text

NgRx Classic Motivation Container Components can have State

Slide 8

Slide 8 text

NgRx Classic Motivation Features can have State

Slide 9

Slide 9 text

NgRx Classic Motivation App can have State

Slide 10

Slide 10 text

NgRx Classic Motivation You have to deal with state.

Slide 11

Slide 11 text

App Component 1 Component 2 Component 3 NgRx Classic Motivation

Slide 12

Slide 12 text

App Component 1 Component 2 Component 3 Service 1 Service 2 Service 3 NgRx Classic Motivation

Slide 13

Slide 13 text

App Component 1 Component 2 Component 3 Service 1 Service 2 Service 3 NgRx Classic Motivation

Slide 14

Slide 14 text

App Component 1 Component 2 Component 3 Service 1 Service 2 Service 3 NgRx Classic Motivation

Slide 15

Slide 15 text

App Component 1 Component 2 Component 3 State Service 1 Service 3 Service 2 Reducer Effects NgRx Classic

Slide 16

Slide 16 text

State { ... } NgRx Classic State

Slide 17

Slide 17 text

One "source of truth" Easy to save Easy to debug Undo/Redo Immutables and Observables, Signals Performance Reading: directly Writing: Via Reducer NgRx Classic State

Slide 18

Slide 18 text

State UI Actions Reducer Store defines triggers send to updates contains NgRx Classic

Slide 19

Slide 19 text

export interface TodoState { items: Todo[]; } export const initialState: TodoState = { items: [], }; NgRx Classic

Slide 20

Slide 20 text

export const TodosActions = createActionGroup({ source: '[Todo]', events: { 'Load All Todos': emptyProps(), 'Load All Todos Finished': props<{ products: Product[] }>(), 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), }, }); 1 2 3 4 5 6 7 8 NgRx Classic

Slide 21

Slide 21 text

export const TodosActions = createActionGroup({ source: '[Todo]', events: { 'Load All Todos': emptyProps(), 'Load All Todos Finished': props<{ products: Product[] }>(), 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), }, }); 1 2 3 4 5 6 7 8 'Load All Todos': emptyProps(), export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 4 'Load All Todos Finished': props<{ products: Product[] }>(), 5 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), 6 }, 7 }); 8 NgRx Classic

Slide 22

Slide 22 text

export const TodosActions = createActionGroup({ source: '[Todo]', events: { 'Load All Todos': emptyProps(), 'Load All Todos Finished': props<{ products: Product[] }>(), 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), }, }); 1 2 3 4 5 6 7 8 'Load All Todos': emptyProps(), export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 4 'Load All Todos Finished': props<{ products: Product[] }>(), 5 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), 6 }, 7 }); 8 'Load All Todos Finished': props<{ products: Product[] }>(), export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 'Load All Todos': emptyProps(), 4 5 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), 6 }, 7 }); 8 NgRx Classic

Slide 23

Slide 23 text

export const TodosActions = createActionGroup({ source: '[Todo]', events: { 'Load All Todos': emptyProps(), 'Load All Todos Finished': props<{ products: Product[] }>(), 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), }, }); 1 2 3 4 5 6 7 8 'Load All Todos': emptyProps(), export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 4 'Load All Todos Finished': props<{ products: Product[] }>(), 5 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), 6 }, 7 }); 8 'Load All Todos Finished': props<{ products: Product[] }>(), export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 'Load All Todos': emptyProps(), 4 5 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), 6 }, 7 }); 8 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 'Load All Todos': emptyProps(), 4 'Load All Todos Finished': props<{ products: Product[] }>(), 5 6 }, 7 }); 8 NgRx Classic

Slide 24

Slide 24 text

export const TodosActions = createActionGroup({ source: '[Todo]', events: { 'Load All Todos': emptyProps(), 'Load All Todos Finished': props<{ products: Product[] }>(), 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), }, }); 1 2 3 4 5 6 7 8 'Load All Todos': emptyProps(), export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 4 'Load All Todos Finished': props<{ products: Product[] }>(), 5 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), 6 }, 7 }); 8 'Load All Todos Finished': props<{ products: Product[] }>(), export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 'Load All Todos': emptyProps(), 4 5 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), 6 }, 7 }); 8 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 'Load All Todos': emptyProps(), 4 'Load All Todos Finished': props<{ products: Product[] }>(), 5 6 }, 7 }); 8 }, export const TodosActions = createActionGroup({ 1 source: '[Todo]', 2 events: { 3 'Load All Todos': emptyProps(), 4 'Load All Todos Finished': props<{ products: Product[] }>(), 5 'Load All Todos Failure': props<{ error: HttpErrorResponse }>(), 6 7 }); 8 NgRx Classic

Slide 25

Slide 25 text

Reducer Function that executes Action Pure Function (stateless, etc.) Each Reducer gets each Action Check whether Action is relevant This prevents cycles NgRx Classic

Slide 26

Slide 26 text

Reducer (currentState, action) => newState

Slide 27

Slide 27 text

export interface TodoState { items: Todo[]; } export const initialState: TodoState = { items: [], }; const todoReducerInternal = createReducer( initialState, ... ); NgRx Classic - Reducer

Slide 28

Slide 28 text

export interface TodoState { items: Todo[]; } export const initialState: TodoState = { items: [], }; const todoReducer = createReducer( initialState, on(todoActions.loadAllTodosFinished, (state, { payload }) => ({ ...state, items: [...payload] })), ); NgRx Classic - Reducer

Slide 29

Slide 29 text

export interface TodoState { items: Todo[]; } export const initialState: TodoState = { items: [], }; const todoReducer = createReducer( initialState, on(todoActions.addTodoFinished, (state, { payload }) => ({ ...state, items: [...state.items, payload] })), on(todoActions.loadAllTodosFinished, (state, { payload }) => ({ ...state, items: [...payload] })), on(todoActions.loadSingleTodoFinished, (state, { payload }) => ({ ...state, selectedItem: payload })) ); NgRx Classic - Reducer

Slide 30

Slide 30 text

export const routes: Routes = [ { path: 'todo', loadComponent: ..., providers: [ provideState("todos", todoReducer), ], } ]; 1 2 3 4 5 6 7 8 9 NgRx Classic - Feature Abstraction

Slide 31

Slide 31 text

export const routes: Routes = [ { path: 'todo', loadComponent: ..., providers: [ provideState("todos", todoReducer), ], } ]; 1 2 3 4 5 6 7 8 9 provideState("todos", todoReducer), ], export const routes: Routes = [ 1 { 2 path: 'todo', 3 loadComponent: ..., 4 providers: [ 5 6 7 } 8 ]; 9 NgRx Classic - Feature Abstraction

Slide 32

Slide 32 text

export const routes: Routes = [ { path: 'todo', loadComponent: ..., providers: [ provideState("todos", todoReducer), ], } ]; 1 2 3 4 5 6 7 8 9 NgRx Classic - Feature Abstraction { todos: { ... } }

Slide 33

Slide 33 text

export const routes: Routes = [ { path: 'todo', loadComponent: ..., providers: [ provideState("todos", todoReducer), ], } ]; 1 2 3 4 5 6 7 8 9 provideState("todos", todoReducer), export const routes: Routes = [ 1 { 2 path: 'todo', 3 loadComponent: ..., 4 providers: [ 5 6 ], 7 } 8 ]; 9 NgRx Classic - Feature Abstraction { todos: { ... } }

Slide 34

Slide 34 text

Always predictable !!! synchronous per default what about async? NgRx Classic - Reducer

Slide 35

Slide 35 text

NgRx Classic - Effects

Slide 36

Slide 36 text

Async? Effects!!! npm install @ngrx/effects --save NgRx Classic - Effects

Slide 37

Slide 37 text

export const updateTodo = createEffect( (actions$ = inject(Actions)) => { return actions$.pipe( // ... ); }, { functional: true } ); NgRx Classic - Effects

Slide 38

Slide 38 text

export const updateTodo = createEffect( ( actions$ = inject(Actions), todoService = inject(TodoApiService) ) => { return actions$.pipe( ofType(TodoActions.updateTodo), exhaustMap((todo) => { return todoService.update(todo).pipe( map((updatedTodo) => DoggosActions.rateDoggoFinished({ updatedTodo }); ) ); }) ); }, { functional: true } ); NgRx Classic - Effects

Slide 39

Slide 39 text

@Component({ ... }) export class ContentComponent implements OnInit { private readonly store = inject(Store); items$: Observable; ngOnInit() { ... } } NgRx Classic - Components

Slide 40

Slide 40 text

@Component({ ... }) export class ContentComponent implements OnInit { private readonly store = inject(Store); items$: Observable; ngOnInit() { this.items$ = this.store.pipe( map((state) => state.todoFeature.items) ); } } NgRx Classic - Components

Slide 41

Slide 41 text

Selectors Traverse the state tree Return pieces of state Memoized NgRx Classic - Selectors

Slide 42

Slide 42 text

export const featureName = 'todoFeature'; export const getTodoFeatureState = createFeatureSelector(featureName); NgRx Classic - Selectors

Slide 43

Slide 43 text

export const featureName = 'todoFeature'; export const getTodoFeatureState = createFeatureSelector(featureName); export const getAllItems = createSelector( getTodoFeatureState, (state: TodoState) => state.items ); NgRx Classic - Selectors

Slide 44

Slide 44 text

export const featureName = 'todoFeature'; export const getTodoFeatureState = createFeatureSelector(featureName); export const getAllItems = createSelector( getTodoFeatureState, (state: TodoState) => state.items ); export const getAllDoneItems = createSelector( getAllItems, (items: Todo[]) => tems.filter(x => x.done) ); export const getAllUndoneItems = createSelector( getAllItems, (items: Todo[]) => tems.filter(x => !x.done) ); NgRx Classic - Selectors

Slide 45

Slide 45 text

@Component({ ... }) export class ContentComponent implements OnInit { private readonly store = inject(Store); items$ = this.store.select(getAllUndoneItems); doneItems$ = this.store.select(getAllDoneItems); ngOnInit() { this.store.dispatch(todoActions.loadAllTodos()); } addTodo(item: string) { this.store.dispatch(todoActions.addTodo({ payload: item })); } markAsDone(item: Todo) { this.store.dispatch(todoActions.setAsDone({ payload: item })); } } NgRx Classic - Selectors

Slide 46

Slide 46 text

@Component({ ... }) export class ContentComponent implements OnInit { private readonly store = inject(Store); items = this.store.selectSignal(getAllUndoneItems); doneItems = this.store.selectSignal(getAllDoneItems); ngOnInit() { this.store.dispatch(todoActions.loadAllTodos()); } addTodo(item: string) { this.store.dispatch(todoActions.addTodo({ payload: item })); } markAsDone(item: Todo) { this.store.dispatch(todoActions.setAsDone({ payload: item })); } } NgRx Classic - Selectors

Slide 47

Slide 47 text

Demo & Tasks Lets code !