Loading...

how to implement state using angular and rxjs

Published
09-04-2020

Intro

State Management is a very important consideration when developing Angular Apps. State Management means keeping data consistent through the application, as well as tracking the current set of values in collections, tracking newly created and perhaps unsaved data, and the selections the user has made.  Making sure those values are consistent, available and communicated effectively throughout the App becomes increasingly complicated as an App gets larger.

Using Input and Output/Event Emitters is only viable with a small number of components. For example, if a user makes a selection in a select list component, the selected value may have to be communicated to a component several levels up the DOM and then several levels down the DOM. Making sure all data is consistent and communicated properly becomes very problematic with just Input and Output/Event Emitters.

Angular has a built in library called RxJS which can be used for communicating data and selections, and so can be used for State Management. This article will describe a basic pattern for using RxJS for State Management in an Angular App. This article is part of a series in which an Angular App and an API have been created. The starting point for the Angular App can downloaded from here. The API for the data can be downloaded from here.

setup

If you have been following along your Angular and API projects should be working together happily and you can skip this section. If you have just downloaded the repos from Github you will need to do the following setup process.

To run from the API you will need to have installed DotNetCore 3.1.

If you are using Visual Studio run the API using F5. Navigate to the country endpoint and you will see something like the following:

Note the port number. You will need it in the next step to configure the Angular project to call the API correctly.

If you are running the API from the command line (open a command prompt at the project level and type "dotnet run"), the API will be running at https://localhost:5001/. 

In the Angular project, edit the "environment.ts" to replace the existing apiUrl line which maps to the internal API. You should replace the existing line with the following, using your port number (either 5001 or whatever Visual Studio allocated - 44399 in the above image), so that it maps to the DotnetCore API:

apiUrl:"https://localhost:44399/"

the App at the moment

Currently when we run the App, select the Stocks view, then select a country, the App retrieves our list of stocks for the country selected. However, when we go to the Funds view we need to do the same thing again. The value of the country selected in the Stocks view isn't used when we went to the Funds view.

Most of the time, users will expect that the selection made in the Stocks view should be kept when the Funds view is selected. In other words, the country that the user selected should be stored in State and when the Funds component loads it should use that value to retrieve data for that country.

RxJS provides a structure that fits here perfectly. It's called the BehaviorSubject. To make our App testable and maintainable, State Management shouldn't be in a service with any http functionality. So we will create a new service for State Management and use BehaviorSubject to store and communicate selections.

Create the state-store service

In a terminal in your IDE, use the Angular CLI to create a service by typing:

ng g s services\state-store

Edit the "state-store.service"

Add a BehaviorSubject and a default value to hold the selected country:

private unselectedValue:number = -1;
private selectedCountrySubj = new BehaviorSubject(this.unselectedValue);

 

 

Add, a method that returns an observable so components can subscribe to the latest value of our BehaviorSubject:  

/** When components subscribe to the selected value it can be observable */
getSelectedCountry(): Observable {
   return this.selectedCountrySubj.asObservable();
}
 

Finally add a method that components can use to set the value of the BehaviorSubject:  

setSelectedCountry(selectedValue:number) {
    this.selectedCountrySubj.next(selectedValue);
}

Amend country list to set the selected value on the state store:

Country list contains a select list of countries. When a selection is made we want it to be communicated to any components listening. Also we want to store the value so that any components that are initialised after the selection is made are initialised with the selected country where appropriate.

Add state-store service into the constructor parameters declaration so that the constructor becomes:

constructor(private financialsService:FinancialsService, private stateStore:StateStoreService) {

}

Paste the following to replace the onChange method:

onChange(){
  this.stateStore.setSelectedCountry(this.SelectedCountryId);   
}

Amend the StockList component to use the new State Management service:

When a selection is made in the country list we want to use that value to retrieve our stock list. 

Amend the constructor so that we inject the state store service and subscribe to the getSelectedCountry method:

constructor(private financialsService:FinancialsService, private stateStore:StateStoreService) {
    this.stateStore.getSelectedCountry().subscribe(x => {
      this.getData(x);
    });
}

Amend the Fund List component to use the new State Management service:

When a selection is made in the country list we want to use that value to retrieve our stock list. Also, if the component loads after a country selection has already been made we want to use the value that was set previously. Since we used the BehaviorSubject class of RxJS we have exactly that functionality.

Amend the constructor so that we inject the state store service and subscribe to the getSelectedCountry method:

constructor(private financialsService:FinancialsService, private stateStore:StateStoreService) {
    this.stateStore.getSelectedCountry().subscribe(x => {
      this.getData(x);
    });
}

Tidy up

At the end of the previous article the financials service contained a variable to store the selected country, as well as a method to set the selected country.  Since we are now using our State Management service to do this we need to remove the following code from "financials.service":

selectedCountry = new Subject();  
selectCountry(countryId:number){this.selectedCountry.next(countryId);
}

Summary

We have used RxJS to create a basic State Management service for our Angular app. This framework can actually be taken quite a long way before we need to consider more complicated alternatives such as NGRX or NGXS.

The Git Repo for the finished Angular App is here.


Latest posts