CRM App - Part 12 State Management
Overview
Time: 5 mins
In this module, we will learn how to setup a basic State Management using RXJS Behavior Subjects
Learning Outcomes:
- How to implement state management in Angular using RXJS BehaviorSubject
Add a company counter to the AppComponent
Time: 5 mins
Let's add an indicator, next to the application title, to display the number of companies currently in our CRM application. In a real life scenario, the same method could be used to display a shopping cart for example, or any information relative to the current state of the application.
- open the app component and add a companyCount property, reading from our Company Service
src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { CompanyService } from './company/company.service';
import { Observable, map } from 'rxjs';
@Component({
selector: 'fbc-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
constructor(
private companyService: CompanyService
) {}
title = 'Melbourne :) ';
companyCount$!: Observable<number>;
ngOnInit(): void {
this.companyCount$ = this.companyService.getCompanies().pipe(map(c => c.length))
}
}
- Add a company in the navigation
src/app/app.component.html
...
<li class="nav-item" [routerLinkActive]="'active'">
<a class="nav-link" [routerLink]="['/company/list']">
Companies (<span id="company-count">{{ companyCount$ | async }}</span>)
</a>
</li>
...
-
Notice that we are now displaying the number of companies in the CRM when the page loads... but it does not update when we remove or add a company !
-
This is because there is no way for the app component to know that the list of companies has changed.
Note that we added an ID to the companyCount$ container span - this will be used later on when writing integration tests
Change CompanyService to use BehaviorSubject
Time: 15 mins
Let's update our Service to manage to use BehaviorSubject.
A BehaviorSubject is a special kind of Observable, being also an Observer. This allows us to not only subscribe to it, but also publish new values to the stream.
Instead of managing the Observable<Company[]> in each component, the service itself will hold the "State" of the company list, allowing different component to get updated in real time.
- add a BehaviorSubject<Company[]> property to the CompanyService
src/app/company/company.service.ts
companies$ : BehaviorSubject<Company[]> = new BehaviorSubject<Company[]>([])
- Change getCompanies() to loadCompanies(), updating our BehaviorSubject
src/app/company/company.service.ts
loadCompanies() {
this.httpClient
.get<Company[]>(`${this.API_BASE}/company`)
.pipe(
tap((x) => console.log('TAP - Service', x)),
finalize(() => console.log('Finalize: Complete')),
catchError((e) => this.errorHandler<Company[]>(e))
)
.subscribe((c) => {
this.companies$.next(c);
});
}
Note that we can apply here any rxjs operator
- Add a getCompanies() to return the BehaviorSubject
src/app/company/company.service.ts
getCompanies(): Observable<Company[]> {
return this.companies$;
}
- Change our service actions to reload the companies after each action
src/app/company/company.service.ts
getCompany(companyId: number): Observable<Company>{
return this.httpClient.get<Company>(`${this.API_BASE}/company/${companyId}`)
.pipe(
catchError((e) => this.errorHandler<Company>(e))
);
}
deleteCompany(id: number) {
this.httpClient
.delete<Company>(`${this.API_BASE}/company/${id}`)
.pipe(catchError((e) => this.errorHandler<Company>(e)))
.subscribe(() => this.loadCompanies());
}
addCompany(company: Company) {
this.httpClient
.post<Company>(`${this.API_BASE}/company`, company, {
headers: new HttpHeaders().set('content-type', 'application/json'),
})
.pipe(catchError((e) => this.errorHandler<Company>(e)))
.subscribe(() => this.loadCompanies());
}
updateCompany(company: Company) {
return this.httpClient
.put<Company>(`${this.API_BASE}/company/${company.id}`, company, {
headers: new HttpHeaders().set('content-type', 'application/json'),
})
.pipe(catchError((e) => this.errorHandler<Company>(e)))
.subscribe(() => this.loadCompanies());
}
src/app/company/company-edit/company-edit.component.ts
saveCompany(): void {
if (this.isNewCompany) {
this.companyService.addCompany(this.companyForm.value);
} else {
const newCompany: Company = {...this.companyForm.value, id: this.companyId}
this.companyService.updateCompany(newCompany);
}
this.router.navigateByUrl('/company/list');
}
- Finally, load the companies calling getCompanies() in the service constructor
src/app/company/company.service.ts
constructor(private httpClient: HttpClient) {
this.loadCompanies();
}
Observe the results
Time: 1 min
Now, our service itself holds the state of our company list. We just implemented our first state management.
As the service itself (singleton) holds the list of companies, the components subscribe to the same Observable. Whenever this observable changes, the new stream value is emitted and all the components (Observers) update accordingly.
More complex scenarios ?
See NGRx : https://docs.google.com/presentation/d/1rLOa7n8ML7LqYA8mzRG9jmC7rnj7d12Y/edit#slide=id.p59