State management is a crucial aspect of building robust and scalable web applications. It involves handling and synchronizing the application’s state across various components. Angular, being a popular front-end framework, offers several ways to manage state, such as using services, observables, and third-party libraries like NgRx. In this blog, we’ll explore a simple yet effective approach to state management using Angular services, signals, and LocalStorage.
Bellow is example of state management using NgRx
Understanding the Approach of state management using Signals
In this state management approach, we’ll use two service files: app-state.service.ts
and signal.service.ts
. The AppStateService
will be responsible for managing and retrieving application states, while the SignalService
will handle the signaling mechanism between components. The states will be stored both in memory and in the browser’s LocalStorage
.
Let’s dive into the code and understand each part step by step.
The SignalService
(signal.service.ts)
The SignalService
is responsible for managing signals that can notify subscribers whenever there is a change in a particular state. It uses RxJS’s Subject
and BehaviorSubject
to accomplish this.
// signal.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SignalService {
private signals: { [key: string]: Subject<any> } = {};
// Dispatch a signal to notify subscribers of a state change
dispatchSignal(signalName: string, data?: any) {
if (this.signals[signalName]) {
this.signals[signalName].next(data);
}
}
// Update the value of a signal (or create a new one if not exists)
updateSignalValue(key: string, newValue: string): void {
const signal = this.signals[key];
if (!signal) {
this.signals[key] = new BehaviorSubject<string>(newValue);
} else {
signal.next(newValue);
}
}
// Subscribe to a signal to receive state change notifications
subscribeToSignal(signalName: string): Subject<any> {
let signal = this.signals[signalName];
if (!signal) {
signal = new Subject<any>();
this.signals[signalName] = signal;
}
return signal;
}
}
The AppStateService
(app-state.service.ts)
The AppStateService
is responsible for managing application states and interacting with the SignalService
for state change notifications. It also uses LocalStorage
to persist the states, ensuring the state is retained even if the user refreshes the page or navigates away.
// app-state.service.ts
import { Injectable } from '@angular/core';
import { SignalService } from './signal.service';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AppStateService {
private states: { [key: string]: any } = {};
constructor(private signalService: SignalService) {
this.loadStatesFromLocalStorage();
}
// Load states from LocalStorage during service initialization
private loadStatesFromLocalStorage() {
const savedStates = localStorage.getItem('appStates');
if (savedStates) {
this.states = JSON.parse(savedStates);
for (const key in this.states) {
if (this.states.hasOwnProperty(key)) {
const value = this.states[key];
this.signalService.updateSignalValue(key, value);
}
}
}
}
// Get the state with a given key from the SignalService or LocalStorage
getState(key: string): Observable<string> {
const fromSignal = this.signalService.subscribeToSignal(key);
const fromLocal = localStorage.getItem(key);
return fromSignal || of(fromLocal || '');
}
// Set the state with a given key and value
setState<T>(key: string, newState: T) {
this.states[key] = newState;
this.saveStatesToLocalStorage();
this.signalService.dispatchSignal(key, newState);
}
// Save the current states to LocalStorage
private saveStatesToLocalStorage() {
localStorage.setItem('appStates', JSON.stringify(this.states));
}
}
Putting It All Together
With these two services in place, you can now manage your application states efficiently. Let’s see how you can use them in your components:
1: Inject the AppStateService
in your components.
import { Component, OnInit } from '@angular/core';
import { AppStateService } from './app-state.service';
@Component({
selector: 'app-your-component',
templateUrl: './your-component.component.html',
styleUrls: ['./your-component.component.css']
})
export class YourComponent implements OnInit {
stateValue: string;
constructor(private appStateService: AppStateService) { }
ngOnInit() {
// Subscribe to the state with key 'yourStateKey'
this.appStateService.getState('yourStateKey').subscribe((value) => {
this.stateValue = value;
});
}
// Update the state with key 'yourStateKey' when needed
updateState() {
const newValue = 'New State Value';
this.appStateService.setState('yourStateKey', newValue);
}
}
By using this approach, you have a centralized way to manage your application states, and the components can interact with the states without direct coupling. Any change to the state triggers notifications to all subscribed components, ensuring data consistency throughout your application.
Conclusion
In this blog, we explored a simple and effective approach to state management in Angular using services, signals, and LocalStorage. By adopting this approach, you can maintain a clear separation of concerns and ensure a more organized and scalable codebase. Additionally, the use of LocalStorage allows the application to persist states across page reloads, enhancing the user experience.
Remember that this is just one of many state management solutions in Angular, and the best approach depends on the complexity and specific requirements of your application. However, the combination of services, signals, and LocalStorage offers a great starting point for many projects. Happy coding!