DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • How to Effortlessly Host Your Angular Website on GitHub Pages
  • React, Angular, and Vue.js: What’s the Technical Difference?
  • Auto-Scaling a Spring Boot Native App With Nomad
  • Difference Between Bootstrap and AngularJS in 2022

Trending

  • Chaos Engineering for Microservices
  • Testing SingleStore's MCP Server
  • Simplify Authorization in Ruby on Rails With the Power of Pundit Gem
  • Scalability 101: How to Build, Measure, and Improve It
  1. DZone
  2. Coding
  3. JavaScript
  4. Angular Tutorial: State Management with NgRx

Angular Tutorial: State Management with NgRx

A tutorial on how to use NgRx to work with state in an Angular appliation, with all the TypeScript you need to get started.

By 
Rohana Liyanarachchi user avatar
Rohana Liyanarachchi
·
Updated Oct. 05, 20 · Tutorial
Likes (13)
Comment
Save
Tweet
Share
217.3K Views

Join the DZone community and get the full member experience.

Join For Free

Do we need state management in every Angular application? Maybe not always, so how do we implement state management elegantly for the applications in which we do need it? NgRx is one of the libraries used for application state management. It is a Redux implementation for Angular.

First, let’s look at the problem we are trying to solve, and then understand the concepts behind the NgRx, and, finally, we'll jump into the coding.

Application State

What is the application state? Theoretically, it is the entire memory of the application, but, typically, it is the data received via API calls, user inputs, presentation UI State, app preferences, etc. Simply put, it is the data that can differentiate two instances of the same application. A simple concrete example of an application state would be a list of customers maintained in an application.

The Problem We're Trying to Solve

For simplicity, let's assume we have a list of customers in the application, and that is the state that we are trying to manage. Some API calls and user inputs could change the state ( i.e. the list ) by adding or removing customers. The state change should be reflected in the UI and other dependent components. Surely, in this particular case, we can have a global variable to hold the list and then add/remove customers from/to it and then write the code to update the UI and dependencies. But, there are many pitfalls in that design that are not the focus of this article. The NgRx is a great design for most of state management requirements. Indeed, it has a bit of boilerplate in comparison with other competitors like NGXS, Akita, or plain RxJS.

NgRx App State Management

Let’s look at the NgRx implementation — there are several components to understand. 

  • Store: The store is what holds the app's state. 

  • Action: A unique event dispatched from components and services that describe how the state should be changed. For example, ‘Add Customer’ can be an action that will change the state (i.e. add a new customer to the list).

  • Reducer: All the state changes happen inside the reducer; it responds to the action and, based on that action, it will create a new immutable state and return it to the store.

  • Selector: Selector is a function used for obtaining a part of the state from the store.

  • Effect: A mechanism that listens for dispatched actions in an observable stream, processes the server response, and returns new actions either immediately or asynchronously to the reducer to change the state. Please note that we are not using 'effect' in this example app.

This is the interaction between those components in NgRx.

The user interface and other components dispatch actions. Actions can have a payload that needs to change the state. The reducer creates a new state as described by the specified action and returns it to the store. Once the store updates with the new state, it will notify the UI and all dependent components. Each UI reacts to the state change and its view gets updated to reflect the changes. 

This is the representation of our example state, the customer list.

The “Add New Customer” UI control dispatches the AddCustomer action with the new customer as a payload in that action. The reducer takes the AddCustomer action and the new customer comes as a payload and creates a new immutable collection with the existing customers. Then, it will update the store with the new customer list. Finally, it will notify the UI using the Observables and the UI gets updated with the new customers' list.

Finally, the Coding


The code for this app is available here.

Prerequisites:

Make sure Node.js and Angular CLI are installed. Information can be found below links

  • Nodejs: https://nodejs.org/

  • Angular CLI: https://cli.angular.io/

To find out the Node.js and Angular CLI versions in your machine, run

node --version

ng --version

1. Create an Angular App With Angular CLI

Let's create a new Angular Application 

ng new angular-state-management --style=scss --routing=false

It will create all the required files and install the dependencies. This will take a few minutes.

2. Load the Project Into the IDE (I'm Using IntelliJ IDEA)

3. Run the App

Let's run the app created by the CLI, just to make sure everything has been created correctly so far.

cd angular-state-management 

npm start

Check that the app is running on http://localhost:4200/.

4. Install NgRx and Tools

NgRx Schematics provides scaffolding. NgRx commands get integrated into the Angular CLI, and most of the NgRx elements can be created using angular CLI. So, let's add NgRx Schematics. (You can use a new terminal window or exit out from the running angular app by pressing the Ctrl+C key )

ng add @ngrx/schematics@latest

Configure the Schematics so that NgRx commands are available in Angular CLI by default.

ng config cli.defaultCollection @ngrx/schematics

Let’s install the NgRx,  dependencies, and dev-tools now.

npm install @ngrx/store --save 

npm install @ngrx/effects --save 

npm install @ngrx/entity --save 

npm install @ngrx/store-devtools --save

Notice that package.json has been updated.

5. Add an NgRx Store to the App

Let’s generate NgRx Store in the app.

ng generate @ngrx/schematics:store State --root --module app.module.ts

Notice that the following line has been added to the app.module.ts

StoreModule.forRoot(reducers, { metaReducers }),

6. Create a sub Module for Customer

Let's create a separate module for Customers following the 'separation of concerns' principle.

 ng generate module Customer

7. Create a Customer model

Now, we are starting to add some code. First, let’s create a 'Customer' using the CLI.

ng generate class models/customer

As another option, you can add it using the editor. 

The customer.ts file has been created in the src\app\models folder. Add 'name' property to it.

TypeScript
 




x


 
1
export class Customer {
2
  name = '';
3
}



8. Add Actions

Now, we are going to add NgRx-related code. As our diagram above shows, the state that we are going to manage is the collection of customers. We can change the collection of the customer's state using the actions. For this particular case, we have one action that can change the state:

We are not generating failure action, So just select 'N'.

ng generate action customer/store/action/Customer

? Should we generate success and failure actions? No

? Do you want to use the create function? Yes

CREATE src/app/ngrx/customer.actions.spec.ts (218 bytes)

CREATE src/app/ngrx/customer.actions.ts (132 bytes)

Create a TypeScript file, customer.actions.ts, in the src/app folder for customer actions using the editor. 

Add the following code to the customer.actions.ts file:

TypeScript
 




xxxxxxxxxx
1


 
1
import { createAction, props } from '@ngrx/store';
2
import {Customer} from '../../../models/customer';
3
 
          
4
export const addCustomer = createAction(
5
  '[Customer] Add Customer',
6
  (customer: Customer) => ({customer})
7
);



9. Add a Customer Reducer

Let’s add the reducer; all state changes are happening inside the reducer based on the selected ‘Action.’ If the state is changing, then the reducer will create a new customer rather than mutating the existing customer list. In the case of the state change, the reducer is always going to return a newly created customer list object. 

Generate reducer for Customers. Again, say "No" to adding failure, success actions.

ng generate reducer customer/store/reducer/Customer

? Should we add success and failure actions to the reducer? No

? Do you want to use the create function? Yes

CREATE src/app/ngrx/customer.reducer.spec.ts (334 bytes)

CREATE src/app/ngrx/customer.reducer.ts (237 bytes)

Add code to the reducer.

TypeScript
 




xxxxxxxxxx
1
26


 
1
import {Action, createReducer, on} from '@ngrx/store';
2
import * as CustomerActions from '../action/customer.actions';
3
import {Customer} from '../../../models/customer';
4
 
          
5
export const customerFeatureKey = 'customer';
6
 
          
7
export interface CustomerState {
8
  customers: Customer[];
9
}
10
 
          
11
export const initialState: CustomerState = {
12
  customers: []
13
};
14
 
          
15
export const customerReducer = createReducer(
16
  initialState,
17
  on(CustomerActions.addCustomer,
18
    (state: CustomerState, {customer}) =>
19
      ({...state,
20
        customers: [...state.customers, customer]
21
      }))
22
);
23
 
          
24
export function reducer(state: CustomerState | undefined, action: Action): any {
25
  return customerReducer(state, action);
26
}



10. Add Selector

Generate a selector to get the customers from the Store.

ng generate selector customer/store/selector/Customer

Modify the code as below:

TypeScript
 




xxxxxxxxxx
1
12


 
1
import {createFeatureSelector, createSelector} from '@ngrx/store';
2
import * as fromCustomer from '../reducer/customer.reducer';
3
 
          
4
export const selectCustomerState = createFeatureSelector<fromCustomer.CustomerState>(
5
  fromCustomer.customerFeatureKey,
6
);
7
 
          
8
 
          
9
export const selectCustomers = createSelector(
10
  selectCustomerState,
11
  (state: fromCustomer.CustomerState) => state.customers
12
);



11. Add a UI Component for View Customers


Generate CustomerView component

ng generate component customer/CustomerView

Add code to the customers-view.compoment.ts file.

Declare the customers that are observable at the top of the class and modify the constructor and use the selector to get Observable of Customers

customers$: Observable<Customer[]> 
constructor(private store: Store<CustomerState>) { 
 this.customers$ = this.store.pipe(select(selectCustomers));
} 

With required dependencies at the top, the customers-view.compoment.ts file should look like this now:

TypeScript
 




xxxxxxxxxx
1
20


 
1
import {Component} from '@angular/core';
2
import {Observable} from 'rxjs';
3
import {Customer} from '../../models/customer';
4
import {select, Store} from '@ngrx/store';
5
import {selectCustomers} from '../store/selector/customer.selectors';
6
import {CustomerState} from '../store/reducer/customer.reducer';
7
 
          
8
@Component({
9
  selector: 'app-customer-view',
10
  templateUrl: './customer-view.component.html',
11
  styleUrls: ['./customer-view.component.scss']
12
})
13
export class CustomerViewComponent {
14
 
          
15
  customers$: Observable<Customer[]>;
16
 
          
17
  constructor(private store: Store<CustomerState>) {
18
    this.customers$ = this.store.pipe(select(selectCustomers));
19
  }
20
}



Add the following code to the customers-view.compoment.html file.

HTML
 




xxxxxxxxxx
1


 
1
<h4>List of Customers</h4>
2
<div *ngFor="let customer of customers$ | async; let i=index">
3
  <span >{{i+1}}.</span> {{customer.name}}
4
</div>



12. Add UI Controls to Add New Customers


Generate UI control component to Add Customers

ng generate component customer/CustomerAdd

Add code to  dispatch the add customer action 

  this.store.dispatch(addCustomer(customer));

With all modification the customer-add.component.ts

Java
 




xxxxxxxxxx
1
12


 
1
export class CustomerAddComponent {
2
 
          
3
  constructor(private store: Store<CustomerState>) {
4
  }
5
 
          
6
  addCustomer(customerName: string): void {
7
    const customer = new Customer();
8
    customer.name = customerName;
9
    this.store.dispatch(addCustomer(customer));
10
  }
11
}



And the customer-add.component.html file.

HTML
 




xxxxxxxxxx
1


 
1
<h4>Add New Customer</h4>
2
<input #box >
3
<button (click)="addCustomer(box.value)">Add</button>



13. Add StoreModule for Feature in Customer Module


Add Store module in the CustomerModule

StoreModule.forFeature(customerFeatureKey, reducer),

Also, add the exports 

exports: [CustomerViewComponent,CustomerAddComponent]

The customer.module.ts will look like this:

TypeScript
 




xxxxxxxxxx
1
21


 
1
import {NgModule} from '@angular/core';
2
import {CommonModule} from '@angular/common';
3
import {CustomerViewComponent} from './customer-view/customer-view.component';
4
import {CustomerAddComponent} from './customer-add/customer-add.component';
5
import {StoreModule} from '@ngrx/store';
6
import {customerFeatureKey, reducer} from './store/reducer/customer.reducer';
7
 
          
8
 
          
9
@NgModule({
10
  declarations: [CustomerViewComponent, CustomerAddComponent],
11
  imports: [
12
    CommonModule,
13
    StoreModule.forFeature(customerFeatureKey, reducer),
14
  ],
15
  exports: [
16
    CustomerViewComponent,
17
    CustomerAddComponent
18
  ]
19
})
20
export class CustomerModule {
21
}


 

13. Import the CustomerModule in the AppModule

 Import the CustomerModule into the AppModule

TypeScript
 




xxxxxxxxxx
1
24


 
1
import { BrowserModule } from '@angular/platform-browser';
2
import { NgModule } from '@angular/core';
3
 
          
4
import { AppComponent } from './app.component';
5
import { StoreModule } from '@ngrx/store';
6
import { reducers, metaReducers } from './reducers';
7
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
8
import { environment } from '../environments/environment';
9
import {CustomerModule} from './customer/customer.module';
10
 
          
11
@NgModule({
12
  declarations: [
13
    AppComponent
14
  ],
15
  imports: [
16
    BrowserModule,
17
    StoreModule.forRoot(reducers, { metaReducers }),
18
    !environment.production ? StoreDevtoolsModule.instrument() : [],
19
    CustomerModule
20
  ],
21
  providers: [],
22
  bootstrap: [AppComponent]
23
})
24
export class AppModule { }


11. Update the App Component With CustomerView and CustomerAdd Component

Now update the app.component.html file by removing the default content and embedding both the 'app-customers-view' and 'app-customer-add' components.

The app.component.html file should look like this:

HTML
 




xxxxxxxxxx
1


 
1
<div style="text-align:center">
2
  <h1>
3
    Welcome to {{ title }}!
4
  </h1>
5
</div>
6
<app-customer-view></app-customer-view>
7
<app-customer-add></app-customer-add>



12. Run the App


Our coding is done! Let's run the app again (if it is not already running).

npm start


The app will look like this:


The code for this app is available here.

Thank you, your feedback on this article is highly appreciated.

AngularJS app application Command-line interface TypeScript

Opinions expressed by DZone contributors are their own.

Related

  • How to Effortlessly Host Your Angular Website on GitHub Pages
  • React, Angular, and Vue.js: What’s the Technical Difference?
  • Auto-Scaling a Spring Boot Native App With Nomad
  • Difference Between Bootstrap and AngularJS in 2022

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!