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

Introducing state management into an applicatio...

Introducing state management into an application with NgRx

In this workshop, we will gradually introduce state management with NgRx into an existing application. First, we demonstrate how this can be solved using the classic NgRx store and how it ensures clear and efficient management of the application state.

We then modernize the application by introducing the NgRx Signal Store, which offers new approaches to management and reactivity in state management.

Fabian Gosebrink

March 19, 2025
Tweet

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. State Management with NgRx Master centralized state management with Classic

    NgRx, using actions, reducers, effects, and selectors for scalable applications!
  2. App Component 1 Component 2 Component 3 Service 1 Service

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

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

    2 Service 3 NgRx Classic Motivation
  5. App Component 1 Component 2 Component 3 State Service 1

    Service 3 Service 2 Reducer Effects NgRx Classic
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. Reducer Function that executes Action Pure Function (stateless, etc.) Each

    Reducer gets each Action Check whether Action is relevant This prevents cycles NgRx Classic
  13. export interface TodoState { items: Todo[]; } export const initialState:

    TodoState = { items: [], }; const todoReducerInternal = createReducer( initialState, ... ); NgRx Classic - Reducer
  14. 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
  15. 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
  16. export const routes: Routes = [ { path: 'todo', loadComponent:

    ..., providers: [ provideState("todos", todoReducer), ], } ]; 1 2 3 4 5 6 7 8 9 NgRx Classic - Feature Abstraction
  17. 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
  18. 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: { ... } }
  19. 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: { ... } }
  20. export const updateTodo = createEffect( (actions$ = inject(Actions)) => {

    return actions$.pipe( // ... ); }, { functional: true } ); NgRx Classic - Effects
  21. 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
  22. @Component({ ... }) export class ContentComponent implements OnInit { private

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

    readonly store = inject(Store); items$: Observable<Todo[]>; ngOnInit() { this.items$ = this.store.pipe( map((state) => state.todoFeature.items) ); } } NgRx Classic - Components
  24. export const featureName = 'todoFeature'; export const getTodoFeatureState = createFeatureSelector<TodoState>(featureName);

    export const getAllItems = createSelector( getTodoFeatureState, (state: TodoState) => state.items ); NgRx Classic - Selectors
  25. export const featureName = 'todoFeature'; export const getTodoFeatureState = createFeatureSelector<TodoState>(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
  26. @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
  27. @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