NgRx: Angular State Management Beginner’s Guide

How to Use NGRX

Madhura Jayashanka
Enlear Academy

--

Introduction

As your Angular app expands, managing its state can become a challenge. Using NgRx, a state management library that brings solution to the chaos, ensuring predictable state changes, improved testability, and enhanced scalability. Let’s dive into NgRx’s core concepts, build a counter app using Angular 15 to experience its practical usage, that make it a powerful tool for complex applications.

Project Repo Linkhttps://github.com/madhurajayashanka/ngrx-tut

Building the Counter App

src
├── app
│ ├── counter
│ │ ├── counter.component.html
│ │ ├── counter.component.scss
│ │ ├── counter.component.spec.ts
│ │ └── counter.component.ts
│ ├── states
│ │ ├── counter
│ │ │ ├── counter.actions.ts
│ │ │ ├── counter.reducer.ts
│ │ │ └── counter.selector.ts
│ │ └── app.state.ts
│ ├── app.component.html
│ └── app.module.ts

Let’s start by creating a simple Counter App using Angular. We’ll use the Angular CLI to generate the Counter component and style it for a clean and intuitive user interface.

ng new counterAppWithNGRX //initialize a new angular project
ng g c counter   //generate a new component called counter
<!-- counter.component.html -->
<div class="counter-container">
<h1>Angular Counter App</h1>
<div class="counter">
<button (click)="decrement()">-</button>
<span>{{ counts|async }}</span> //asynchronously change the state
<button (click)="increment()">+</button>
</div>
<div class="controls">
<button (click)="reset()">Reset</button>
</div>
</div>
//counter.component.css
.counter-container {
text-align: center;
margin-top: 50px;
}

.counter {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}

.controls {
margin-top: 20px;
}

button {
font-size: 1.5em;
padding: 10px;
margin: 0 10px;
cursor: pointer;
}

span {
font-size: 2em;
}
// counter.component.ts
import { Component } from '@angular/core';
import { Observable } from "rxjs";
import { Store } from "@ngrx/store";
import { AppState } from "../states/app.state";
import { selectCount } from "../states/counter/counter.selector";
import { decrement, increment, reset } from "../states/counter/counter.actions";

@Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: './counter.component.scss'
})
export class CounterComponent {
counts: Observable<number>;
constructor(private store: Store<AppState>) {
this.counts = store.select(selectCount);
}
increment() {
this.store.dispatch(increment());
}
decrement() {
this.store.dispatch(decrement());
}
reset() {
this.store.dispatch(reset());
}
}

Initializing NgRx Architecture

Actions

In NgRx, actions are the triggers that initiate state changes. We’ve defined three actions, increment, decrement, and reset to capture the different ways the counter state can be modified.

// 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');

Reducers

Reducers specify how the state should change in response to actions. Here, we’ve created a simple reducer for our counter, handling increments, decrements, and resets.

// counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';

export interface CounterState {
count: number;
}
export const initialCounterState: CounterState = {
count: 0,
};
export const counterReducer = createReducer(
initialCounterState,
on(increment, (state) => ({ ...state, count: state.count + 1 })),
on(decrement, (state) => ({ ...state, count: state.count - 1 })),
on(reset, (state) => ({ ...state, count: 0 }))
);

Selectors

Selectors allow us to retrieve specific pieces of state from the store. Here, we’re selecting the count from the counter state.

// counter.selector.ts
import { AppState } from "../app.state";
import { createSelector } from "@ngrx/store";

export const selectCounterState = (state: AppState) => state.counter;
export const selectCount = createSelector(
selectCounterState,
(state) => state.count
);

Bringing it All Together

With the Counter component, actions, reducers, and selectors in place, we can now integrate Ngrx into our Angular application. Let’s modify the app.module.ts file to include the Ngrx store and our counter reducer.

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CounterComponent } from './counter/counter.component';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from "./states/counter/counter.reducer";
@NgModule({
declarations: [
AppComponent,
CounterComponent
],
imports: [
BrowserModule,
AppRoutingModule,
StoreModule.forRoot({ counter: counterReducer }) //<-- put the reducer here
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

This modification enables the Ngrx store with our counter reducer, creating a centralized store for managing the state of our Angular application.

ng serve

Conclusion

To wrap things up, integrating Ngrx with Angular offers a reliable way to handle state in your apps. You can create Angular apps that are scalable and maintained by understanding and using actions, reducers, and selectors. Discover more, incorporate these ideas into your work, and use Ngrx to experience the power of reactive state management.
👨‍💻 Happy coding!

References

Ngrx store Docs

Angular Docs

GitHub Project Repository

--

--