A simple Angular state management system
February 09, 2023
State management is an interesting topic. You probably need it to some extend in most applications but the solutions
to implement are endless. From a very basic public static variable to complex ngrx setups there is everything out there.
Very common libraries are for example:
- ngrx: probably the most sophisticated and feature rich solution
- Elf: from the awesome guys who already did stuff like spectator, until-destroy or transloco
- Akita
- @rx-angular/state: very lightweight in comparison to the others
Don’t get me wrong, all those libraries are awesome and definitely have their place. I used most of them at some point in time. However, sometimes they just feel too big with too much overhead.
In this post I will show you how you can implement a very basic state management system by yourself (and that you don’t always need a library).
Basic store setup
import {BehaviorSubject, Observable} from 'rxjs';
import {scan} from 'rxjs/operators';
export abstract class DataService<T> {
protected subject: BehaviorSubject<T>;
protected data: Observable<T>;
protected constructor(initialValue: T) {
this.subject = new BehaviorSubject<T>(initialValue);
this.data = this.subject
.pipe(scan((acc: T, curr: T) => ({...acc, ...curr}), initialValue));
}
public getData(): Observable<T> {
return this.data;
}
public setData(data: T): void {
this.subject.next(data);
}
}
At the very core we have a BehaviorSubject
because we want to have an initial value and we are only interested in the
latest value that will be stored in the state.
And the second thing is our Observable
that uses the scan operator
in order to aggregate new values together with our previous value.
Store implementation
Now we can just create a service that extends our abstract class. We only need to define the initial value and the interface that defines the structure of the data that should be stored.
@Injectable()
export class ApplicationDataService extends DataService<IAppData> {
constructor() {
// define your default values here
super({hasChildren: true});
}
}
interface IAppData {
timezone?: string;
mood?: string;
hasChildren?: boolean;
}
You can create as many services as you need. It probably makes sense to create separate services for different parts of the application.
Use a store
We now can inject our service in the component we need and use the getData
and setData
methods to have access to our data.
The intersting part here is that you don’t need to update the entire object in your store. You can just update single values
that you need.
And since we are dealing with observables here a little knowledge of rxjs is very handy here if you are only interested in a
specific property and not the entire data object.
@Component({
selector: 'my-component',
template: '{{(myData | async)?.mood}}',
providers: [ApplicationDataService],
})
class MyComponent {
public myData: IAppData;
constructor(private dataService: ApplicationDataService) {
this.myData = this.dataService.getData();
this.dataService.setData({
mood: 'happy',
timezone: 'europe',
});
}
}
Example and what comes next
You can check all this in action in this stackblitz.
This is just a very basic and simple data store that allows you to add and get data from your own data service. And it’s very easy to extend
from this point on. for example you can add a clear
/reset
method to the DataService
if you have a longer living service that is not
tied to a specific component. This could look something like
public clear(): void {
const data: T = this.subject.getValue();
for (var member in data) {
data[member] = undefined;
}
this.setData(data);
}
You can even add stuff to only modify single entries instead of entire objects or modify existing stores instead of just always updating the entire thing. But at a certain point it might be easier to just use a library if your setup requires a complex state management ;)
Personal Blog written by Nicolas Gehlert, software developer from Freiburg im Breisgau. Developer & Papa. Github | Twitter
Add a comment
Comments
There are no comments available for this blog post yet