Managing state in large-scale Angular applications can be complex and challenging. NgRx, a state management library that implements the Redux pattern, offers a robust solution by managing the application’s state in a predictable manner using a single source of truth. This guide explores the core concepts of NgRx and demonstrates how to implement it in an Angular application through a simple task manager example.
What is NgRx?
NgRx is a comprehensive framework designed for building reactive applications within the Angular ecosystem. It equips developers with robust tools to efficiently manage both state and side effects, utilizing core principles from Redux—a widely recognized JavaScript library for state management. Additionally, NgRx leverages RxJS, Angular’s reactive programming library, to establish a unidirectional data flow and a scalable state management system. This integration ensures that Angular applications maintain a clear, structured, and scalable data flow, enhancing both scalability and maintainability.
Core Concepts of NgRx
NgRx introduces several key concepts that help in managing state:
Actions
Actions are plain JavaScript objects that express an intent to change state. Each action has a type, which identifies the action being performed, and may include a payload, which carries the data necessary for the state update.
Reducers
Reducers are pure functions designed to take the current state of an application and an incoming action to produce a new state. These functions serve as the primary mechanism for determining how the state evolves in response to actions dispatched to the store. By adhering to predictable behavior without side effects, reducers ensure that the state transitions are transparent and maintainable.
Effects
Effects handle side effects. They listen for actions dispatched from the store, perform some operations, and return new actions either immediately or after an asynchronous process.
Selectors
Selectors are functions that allow you to select, derive, and compose pieces of state. They are particularly useful for pulling data from the store in a performant way.
Implementing NgRx: Task Manager Example
To illustrate how NgRx works in practice, let’s create a simple task manager within an Angular application. This task manager will allow users to add, remove, and toggle the completion status of tasks.
Setting Up NgRx
First, ensure NgRx is added to your Angular project:
npm install @ngrx/store @ngrx/effects
Defining the State
Define the state interface for our tasks:
// src/app/models/task.model.ts export interface Task { id: string; title: string; isComplete: boolean; } // src/app/state/app.state.ts import { Task } from '../models/task.model'; export interface AppState { tasks: Task[]; }
Actions
Define actions for managing tasks:
// src/app/state/task.actions.ts import { createAction, props } from '@ngrx/store'; import { Task } from '../models/task.model'; export const createTask = createAction( '[Task List] Add Task', props<{ task: Task }>() ); export const removeTask = createAction( '[Task List] Remove Task', props<{ id: string }>() ); export const toggleTask = createAction( '[Task List] Toggle Task', props<{ id: string }>() );
Reducer
Implement the reducer to handle actions:
// src/app/state/task.reducer.ts import { createReducer, on } from '@ngrx/store'; import { createTask, removeTask, toggleTask } from './task.actions'; import { Task } from '../models/task.model'; export const initialState: Task[] = []; export const taskReducer = createReducer( initialState, on(createTask, (state, { task }) => [...state, task]), on(removeTask, (state, { id }) => state.filter(task => task.id !== id)), on(toggleTask, (state, { id }) => state.map(task => { return task.id === id ? { ...task, isComplete: !task.isComplete } : task; })) );
Effects
Add an effect for fetching tasks asynchronously:
// src/app/state/task.effects.ts import { Injectable } from '@angular.core'; import { Actions, ofType, createEffect } from '@ngrx/effects'; import { of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { createTask } from './task.actions'; @Injectable() export class TaskEffects { loadTasks$ = createEffect(() => this.actions$.pipe( ofType('[Task Page] Load Tasks'), switchMap(() => { // Simulate an HTTP request const tasks = [{ id: '1', title: 'Initial Task', isComplete: false }]; return of(tasks).pipe( switchMap(tasks => tasks.map(task => createTask({ task }))) ); }) )); constructor(private actions$: Actions) {} }
Selectors
add selectors to query the state of tasks:
// src/app/state/task.selectors.ts import { createSelector } from '@ngrx/store'; import { AppState } from './app.state'; export const selectTasks = createSelector( (state: AppState) => state.tasks, (tasks) => tasks );
Integrating into Components
Integrate state management into an Angular component:
// src/app/components/task-list/task-list.component.ts import { Component } from '@angular.core'; import { Store } from '@ngrx/store; import { Observable } from 'rxjs'; import { Task } from '../../models/task.model; import { createTask, removeTask, toggleTask } from '../../state/task.actions'; import { selectTasks } from '../../state/task.selectors'; @Component({ selector: 'app-task-list', template: ` <ul> <li *ngFor="let task of tasks$ | async"> <input type="checkbox" [checked]="task.isComplete" (click)="toggle(task.id)"> {{ task.title }} <button> (click)="remove(task.id)">Remove</button> </li> </ul> <input #taskInput type="text"><button> (click)="add(taskInput.value)">Add Task</button> ` }) export class TaskListComponent { tasks$: Observable<Task[]> ; constructor(private store: Store<{ tasks: Task[] }>) { this.tasks$ = store.select(selectTasks); } add(title: string) { const task: Task = { id: Math.random().toString(36).substring(2, 9), title, isComplete: false }; this.store.dispatch(createTask({ task })); } remove(id: string) { this.store.dispatch(removeTask({ id })); } toggle(id: string) { this.store.dispatch(toggleTask({ id })); } }
Conclusion
This example demonstrates setting up NgRx in an Angular project, covering actions, reducers, effects, and selectors. It shows how you can manage a list of tasks, highlighting the power of NgRx for state management in Angular applications.
How to Create Responsive Web Designs
Introduction Responsive Web Design (RWD) ensures websites adapt seamlessly to different screen sizes, providing a consistent user experience across mobile phones, tablets, and desktops. In today’s multi-device world, RWD is crucial for offering a smooth, accessible...