NgRx Store is a state management library for Angular applications, inspired by Redux. It provides a single source of truth for the application state, making it easier to manage and debug complex state interactions. In this section, we will cover the following topics:
- Introduction to NgRx Store
- Setting Up NgRx Store
- Defining Actions
- Creating Reducers
- Using Selectors
- Dispatching Actions and Selecting State
- Practical Example
- Exercises
- Introduction to NgRx Store
NgRx Store is based on the principles of Redux, which include:
- Single Source of Truth: The state of the entire application is stored in a single object tree within a store.
- State is Read-Only: The only way to change the state is by dispatching actions.
- Changes are Made with Pure Functions: Reducers are pure functions that take the current state and an action, and return a new state.
- Setting Up NgRx Store
To set up NgRx Store in your Angular application, follow these steps:
-
Install NgRx Store:
npm install @ngrx/store
-
Add StoreModule to AppModule:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { StoreModule } from '@ngrx/store'; import { AppComponent } from './app.component'; import { reducers } from './reducers'; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, StoreModule.forRoot(reducers) ], providers: [], bootstrap: [AppComponent] }) export class AppModule {}
- Defining Actions
Actions are payloads of information that send data from your application to your store. They are the only source of information for the store.
- Create an Action:
import { createAction, props } from '@ngrx/store'; export const increment = createAction('[Counter Component] Increment'); export const decrement = createAction('[Counter Component] Decrement'); export const reset = createAction('[Counter Component] Reset');
- Creating Reducers
Reducers specify how the application's state changes in response to actions sent to the store.
- Create a Reducer:
import { createReducer, on } from '@ngrx/store'; import { increment, decrement, reset } from './counter.actions'; export const initialState = 0; const _counterReducer = createReducer( initialState, on(increment, state => state + 1), on(decrement, state => state - 1), on(reset, state => 0) ); export function counterReducer(state, action) { return _counterReducer(state, action); }
- Using Selectors
Selectors are pure functions used for obtaining slices of store state.
- Create a Selector:
import { createSelector, createFeatureSelector } from '@ngrx/store'; export const selectCounter = createFeatureSelector<number>('counter');
- Dispatching Actions and Selecting State
To interact with the store, you can dispatch actions and select state in your components.
-
Dispatching Actions:
import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { increment, decrement, reset } from './counter.actions'; @Component({ selector: 'app-counter', templateUrl: './counter.component.html' }) export class CounterComponent { constructor(private store: Store<{ counter: number }>) {} increment() { this.store.dispatch(increment()); } decrement() { this.store.dispatch(decrement()); } reset() { this.store.dispatch(reset()); } }
-
Selecting State:
import { Component } from '@angular/core'; import { Store, select } from '@ngrx/store'; import { Observable } from 'rxjs'; import { selectCounter } from './counter.selectors'; @Component({ selector: 'app-counter', templateUrl: './counter.component.html' }) export class CounterComponent { count$: Observable<number>; constructor(private store: Store<{ counter: number }>) { this.count$ = store.pipe(select(selectCounter)); } }
- Practical Example
Let's create a simple counter application using NgRx Store.
-
Define Actions:
// counter.actions.ts import { createAction } from '@ngrx/store'; export const increment = createAction('[Counter Component] Increment'); export const decrement = createAction('[Counter Component] Decrement'); export const reset = createAction('[Counter Component] Reset');
-
Create Reducer:
// counter.reducer.ts import { createReducer, on } from '@ngrx/store'; import { increment, decrement, reset } from './counter.actions'; export const initialState = 0; const _counterReducer = createReducer( initialState, on(increment, state => state + 1), on(decrement, state => state - 1), on(reset, state => 0) ); export function counterReducer(state, action) { return _counterReducer(state, action); }
-
Create Selector:
// counter.selectors.ts import { createFeatureSelector } from '@ngrx/store'; export const selectCounter = createFeatureSelector<number>('counter');
-
Update AppModule:
// app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { StoreModule } from '@ngrx/store'; import { AppComponent } from './app.component'; import { counterReducer } from './counter.reducer'; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, StoreModule.forRoot({ counter: counterReducer }) ], providers: [], bootstrap: [AppComponent] }) export class AppModule {}
-
Create Counter Component:
// counter.component.ts import { Component } from '@angular/core'; import { Store, select } from '@ngrx/store'; import { Observable } from 'rxjs'; import { increment, decrement, reset } from './counter.actions'; import { selectCounter } from './counter.selectors'; @Component({ selector: 'app-counter', templateUrl: './counter.component.html' }) export class CounterComponent { count$: Observable<number>; constructor(private store: Store<{ counter: number }>) { this.count$ = store.pipe(select(selectCounter)); } increment() { this.store.dispatch(increment()); } decrement() { this.store.dispatch(decrement()); } reset() { this.store.dispatch(reset()); } }
-
Counter Component Template:
<!-- counter.component.html --> <div> <h1>Counter: {{ count$ | async }}</h1> <button (click)="increment()">Increment</button> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset</button> </div>
- Exercises
Exercise 1: Add a New Action
Task: Add a new action to double the counter value.
-
Define the Action:
export const double = createAction('[Counter Component] Double');
-
Update the Reducer:
const _counterReducer = createReducer( initialState, on(increment, state => state + 1), on(decrement, state => state - 1), on(reset, state => 0), on(double, state => state * 2) );
-
Update the Component:
double() { this.store.dispatch(double()); }
-
Update the Template:
<button (click)="double()">Double</button>
Exercise 2: Add a New Feature
Task: Add a new feature to track the number of times the counter has been incremented.
-
Define the State:
export interface AppState { counter: number; incrementCount: number; }
-
Update the Reducer:
export const initialState: AppState = { counter: 0, incrementCount: 0 }; const _counterReducer = createReducer( initialState, on(increment, state => ({ ...state, counter: state.counter + 1, incrementCount: state.incrementCount + 1 })), on(decrement, state => ({ ...state, counter: state.counter - 1 })), on(reset, state => ({ ...state, counter: 0, incrementCount: 0 })) ); export function counterReducer(state, action) { return _counterReducer(state, action); }
-
Update the Selector:
export const selectIncrementCount = createSelector( selectCounter, (state: AppState) => state.incrementCount );
-
Update the Component:
incrementCount$: Observable<number>; constructor(private store: Store<AppState>) { this.count$ = store.pipe(select(selectCounter)); this.incrementCount$ = store.pipe(select(selectIncrementCount)); }
-
Update the Template:
<h2>Increment Count: {{ incrementCount$ | async }}</h2>
Conclusion
In this section, we covered the basics of NgRx Store, including setting it up, defining actions, creating reducers, using selectors, and dispatching actions. We also provided a practical example and exercises to reinforce the concepts. Understanding NgRx Store is crucial for managing state in large Angular applications, and it provides a robust and scalable solution for state management.
Angular Course
Module 1: Introduction to Angular
- What is Angular?
- Setting Up the Development Environment
- Angular Architecture
- First Angular Application
Module 2: Angular Components
- Understanding Components
- Creating Components
- Component Templates
- Component Styles
- Component Interaction
Module 3: Data Binding and Directives
- Interpolation and Property Binding
- Event Binding
- Two-Way Data Binding
- Built-in Directives
- Custom Directives
Module 4: Services and Dependency Injection
Module 5: Routing and Navigation
Module 6: Forms in Angular
Module 7: HTTP Client and Observables
- Introduction to HTTP Client
- Making HTTP Requests
- Handling HTTP Responses
- Using Observables
- Error Handling
Module 8: State Management
- Introduction to State Management
- Using Services for State Management
- NgRx Store
- NgRx Effects
- NgRx Entity
Module 9: Testing in Angular
Module 10: Advanced Angular Concepts
- Angular Universal
- Performance Optimization
- Internationalization (i18n)
- Custom Pipes
- Angular Animations