Nano Stores in Angular: how to make the state management simpler
Tags: angular, webdev, opensource
Published: 2023-04-21 15:05:15

**TLDR:** Angular now has [integration](https://github.com/nanostores/angular) for [Nano Stores](https://github.com/nanostores/nanostores), an open source state manager based on the idea of atomic tree-shakable stores and direct manipulation. It is very small (from 334 bytes), has zero dependencies, and promotes moving logic from components to stores. With such stores, you don't need to call the selector function for all components on every store change, which makes them noticeably fast.

Here is a small example of how we can use Nano Stores in an Angular component:
```js
import { Component } from '@angular/core';
import { atom } from 'nanostores';
import { NanostoresService } from '@nanostores/angular';
import { Observable, switchMap } from 'rxjs';

const user$ = atom<{ id: string; name: string; }>(
  { id: '0', name: 'John'}
);

@Component({
  selector: "app-root",
  template: '<p *ngIf="(currentUser$ | async) as user">{{ user.name }}</p>'
})
export class AppComponent {
  currentUser$: Observable<IUser> = this.nanostores.useStore(user$);

  constructor(private nanostores: NanostoresService) { }
};
```

How can the appearance of Nano Stores in Angular improve our developer experience with state management? Let's start from the beginning.

Nowadays the concept of state management is crucial for client-side development: we need a reliable source of truth to manage data in our applications. 

And in Angular, there exist specific approaches to manage state. In this area, there are a lot of implementation details that we’ve all become accustomed to over the years.

## What do we have now?

When developing projects with Angular, we usually manage data state in the services, not in components or modules. But, using this approach, when the number of features increases, the number of services also increases and management becomes increasingly complicated. Because of that, we need a convenient approach to handle this process, which should also be predictable in order to reduce the number of possible bugs.

Angular developers have typically been used to implementing state management logic using [RxJS](https://rxjs.dev/) or concepts like [NgRx](https://ngrx.io/)/[NGXS](https://ngxs.io/) inspired by Redux, because these were the most popular approaches. Let's try to write a simple application with state management using these instruments and understand if they are really convenient to use.

### RxJS

Inside Angular, we already have a built-in option: [RxJS](https://rxjs.dev/) (Reactive Extensions for JavaScript), a library for reactive programming, remains the most popular solution to compose callback-based or asynchronous code. We can also handle changing the state of our project by using RxJS operators, and it is quite a common solution, and isn’t too bad for many cases.

Let's build a familiar example: a TODO List application. We’ll consider three functions for the list of tasks: adding a new task, deleting an existing task, and toggling the status of the task from undone to done, or vice versa.

Here’s our HTML:
```html
<input #newTask/>
<button (click)="addTask(newTask.value)">Add</button>
<ul class="todo-list" *ngIf="(tasks$ | async) as tasks">
  <li *ngFor="let task of tasks">
    <button (click)="toggleStatus(this.task.id)">Toggle Status</button>
    <span [ngClass]="{'done': this.task.isDone }">{{this.task.name}}</span>
    <button (click)="deleteTask(this.task.id)">Delete</button>
  </li>
</ul>
```
Then, we can define a service with basic methods for state management (which we can extend in the future with a more detailed implementation):
```js
import { BehaviorSubject, distinctUntilChanged, map, Observable } from 'rxjs';

// This type will be passed when extending the StateService
export class StateService<T> {
  // This field stores the current state's value and emits it to the new subscribers
  protected state$: BehaviorSubject<T>;

  // Initializes the state$ with some initial value
  constructor(initialState: T) {
    this.state$ = new BehaviorSubject<T>(initialState);
  }

  // Returns the current value from the state
  get state(): T {
    return this.state$.getValue();
  }

  // Emits the new state value
  setState(newState: Partial<T>) {
    this.state$.next({...this.state,...newState });
  }

  /**  Called when state$ emits a new value.
  Operator distinctUntilChanged() will skip emissions
  until the piece of state obtain a new value or object reference */
  select<K>(mapFn: (state: T) => K): Observable<K> {
    return this.state$.asObservable().pipe(
      map((state: T) => mapFn(state)),
      distinctUntilChanged()
    );
  }
}
```
Notice that there are no mentions of the actual TODO tasks in this part of the code. So, you can use this service later for new features that are not related to the TODO list.

Now we can extend `StateService` with a more practical service, `TodosStateService`:
```js
@Injectable()
export class TodosStateService extends StateService<{ tasks: Task[] }> {
  /** In the constructor we call the constructor of StateService and pass the initial state (empty array).
   * Injected instance of service with method getTasks() expose an API to receive the tasks. */
  constructor(service: TodoListService) {
    super({ tasks: [] });
    service.getTasks().subscribe(tasks => this.state$.next({ tasks }));
  }

  getTasks(): Task[] {
    return this.state.tasks;
  }

 // Exposes the corresponding state data to subscribers
  todos$: Observable<Task[]> = this.select(state => state.tasks);
}
```
Here’s the structure for a TODO list Task:
```js
type Task = {
  id: string;
  name: string;
  isDone: boolean;
}
```
In the `todoList.component.ts` we need to inject instances of two services: `TodosStateService` and `TodoListService`.
`TodoListService` is a service that calls API methods for receiving, adding, deleting, and changing tasks:
```js
constructor(
    private todosStateService: TodosStateService,
    private service: TodoListService,
  ) { }
```
And we need to subscribe to `$todos` in the `TodosStateService`. We can do this by declaring the component field like this:
```js
tasks$: Observable<Task[]> = this.todosStateService.todos$;
```
But don't forget to unsubscribe from this subscription when the component has been destroyed to prevent possible memory leaks. (You can do this any way you prefer, for example [using RxJS operator takeUntil](https://www.techiediaries.com/angular-13-tutorial/unsubscribe-observables-rxjs-takeuntil/).)

Finally, let's implement actions with our tasks in the list: adding, deleting, and status toggling. I also suggest trying to implement this using the optimistic UI pattern: we’ll update the UI even before receiving a server response:
```js
addTask(name: string) {
    const newTask = {id: '...', name, isDone: false};
    this.todosStateService.setState({tasks: [...this.todosStateService.getTasks(), newTask]});
    this.service.addTask(newTask);
}

deleteTask(id: string) {
    this.todosStateService.setState({
        tasks: this.todosStateService.getTasks().filter(task => task.id !== id)
    });
    this.service.deleteTask(id);
}

toggleStatus(id: string) {
    this.todosStateService.setState({ tasks: this.todosStateService.getTasks()
        .map(task => ({...task, isDone: task.id === id ? !task.isDone : task.isDone }))
    });
    this.service.toggleStatus(id);
}
```
These functions will invoke the `setState` method in the service that maintains the state of tasks.

That's all for the RxJS approach; it’s scalable but the biggest drawback is the large number of routine tasks. We need to create an entirely new service for the whole app, then create a new one that extends it each time when we need a feature with new entities.

### NgRx

Let's next try to develop the same state management example with [NgRx](https://ngrx.io/), a very popular state management library for Angular. It was inspired by Redux and based on such concepts as Stores, Actions, Effects, and Reducers.

First of all, we’ll create a new file with actions. In NgRx, Actions are unique events that happen throughout the application. For simplicity in this case, we can define actions for a single task (adding, deleting, toggling status), and also the action to receive all tasks from the server (we can do it also by [Effects](https://v7.ngrx.io/guide/effects), but let's stick with  the easiest approach for this small example):
```js
import { createAction, props } from '@ngrx/store';

export const LoadTasks = createAction('Load Tasks', props<{tasks: Task[]}>());
export const Add = createAction('Add', props<{task: Task}>());
export const Delete = createAction('Delete', props<{id: string;}>());
export const ToggleStatus = createAction(Toggle Status', props<{id: string;}>());
```

And we need the Reducer for the tasks that will manage the state of our tasks:
```js
import { createReducer, on } from '@ngrx/store';

export const taskReducer = createReducer([],
  on(LoadTasks, (_, action) => (action.tasks)),
  on(Add, (state, action) => ([...state, action.task])),
  on(Delete, (state, action) => state.filter(task => task.id !== action.id)),
  on(ToggleStatus, (state, action) => state.map(task => ({...task, isDone: task.id === action.id ? !task.isDone : task.isDone }))),
);
```
(Did you notice we already have a lot of boilerplate for simple things?)

We need to add the `StoreModule.forRoot` function in the imports array of `NgModule` and pass the object that contains the `taskReducer`. The `StoreModule.forRoot()` method registers the providers needed to access the global store throughout the application.
```js
import { StoreModule } from '@ngrx/store';
imports: [
    StoreModule.forRoot({todoState: taskReducer}),
    ...
],
```
Well, now we can create the component with tasks. We have exactly the same HTML that we had for RxJS example, but the code in `component.ts` will be different.

After creating the new component, we need to inject  the instance of `TodoListService` (we already used this in the previous example), and the Store service to dispatch the task actions:
```js
constructor(private service: TodoListService, private store: Store<{ todoState: Array<Task> }>) { }
```
Then, we can subscribe to task changes in the `ngOnInit` method to receive the tasks from the API:
```js
ngOnInit() {
    this.service.getTasks().subscribe(tasks => todos$.set(tasks));
}
```
Keep in mind that, in this case, we also probably want to unsubscribe from the subscription when the component has been destroyed.

We’re nearly finished. Let's add a field to the component with the actual state of tasks. We need to select them from the store using the `select` method we recently created:
```js
tasks$: Observable<Task[]> = this.store.select(state => state.todoState);
```
With regards to Optimistic UI, all we need to do is to add the following code for the actions with tasks:
```js
addTask(name: string) {
    const newTask = {id: '...', name, isDone: false};
    todos$.set([...todos$.get(), newTask]);
    this.service.addTask(newTask);
}

deleteTask(id: string) {
    todos$.set(todos$.get().filter(task => task.id !== id));
    this.service.deleteTask(id);
}

toggleStatus(id: string) {
    todos$.set(todos$.get().map(
        task => ({...task, isDone: task.id === id ? !task.isDone : task.isDone })
    ));
    this.service.toggleStatus(id);
}
```
All in all, we have changes in four different files, `module.ts`, `component.ts`, `actions.ts`, and `reducer.ts` (and we haven't even used Effects for even more simplicity.) We already have a lot of repeating actions to build just the basic features, imagine the result if we needed to add extended ones.

Other than that, [NgRx is a heavy library](https://bundlephobia.com/package/@ngrx/store@8.4.0) for your bundle and we need to spend time to properly understand how all these concepts, like Actions, Reducers, Selectors, and so on, work.

But we can try another option to manage the state and make our life simpler.

### Nano Stores

Let's try the same example with the TODO List application to see how to use Nanostores in this simple case — and how convenient it can be.

First of all, we need to provide injection tokens in our `NgModule` to make the Nano Stores work:

```js
import { NANOSTORES, NanostoresService } from '@nanostores/angular';

@NgModule({ providers: [{ provide: NANOSTORES, useClass: NanostoresService }], ... })
```

Then we need to create a new atomic store to hold the tasks. The initial state for the tasks will be the empty array:
```js
import { atom } from 'nanostores';
const todos$ = atom<Task[]>([]);
```
And here we also can define functions that we will invoke to update the value in our store:
```js
export const addTask = (newTask: Task) => {
 todos$.set([...todos$.get(), newTask]);
}

export const deleteTask = (id: string) => {
 todos$.set(todos$.get().filter(task => task.id !== id));
}

export const toggleStatus = (id: string) => {
 todos$.set(todos$.get().map(
   task => ({...task, isDone: task.id === id ? !task.isDone : task.isDone })
 ));
}
```

It's time to create the component with tasks (the HTML is all the same as in the previous section). We will inject the instance of our usual `TodoListService`, and also the instance of `NanostoresService`:
```js
constructor(private service: TodoListService, private nanostores: NanostoresService) { }
```
Now we can add a field into the component with the actual state of tasks using the `useStore` method in the injected instance of `NanostoresService`:
```js
tasks$: Observable<Task[]> = this.nanostores.useStore(todos$);
```
And then, in `ngOnInit`, we can subscribe to the task changes method to receive the tasks from the server side and set them to the store:
```js
ngOnInit() {
    this.service.getTasks().subscribe(tasks => todos$.set(tasks));
}
```
All that is left to do is to write the part of the component where we’ll call the functions that interact with the store (which we already wrote), and invoke the API methods:
```js
addTask(name: string) {
    const newTask = {id: '...', name, isDone: false};
    addTask(newTask);
    this.service.addTask(newTask);
}

deleteTask(id: string) {
    deleteTask(id);
    this.service.deleteTask(id);
}

toggleStatus(id: string) {
    toggleStatus(id);
    this.service.toggleStatus(id);
}
```
And that's all. No additional files, utility services, and no boilerplate. Don't forget to unsubscribe from the initial subscription, as was described in previous sections.

## Why is Nanostores the handiest way to manage state?

After solving this simple task with three different approaches and seeing the brevity and beauty of Nanostores, let's sum up the results. Why is Nanostores the best option to manage the state of an application in Angular?
* It was designed to move logic from components to stores – less logic in components, clearer and more predictable development process
* No boilerplate, short and clear syntax
* It’s much smaller than NgRx and its analogs. For instance, even the base NgRx package  [@ngrx/store](https://bundlephobia.com/package/@ngrx/store@15.4.0), has a minified bundle size **3 times more** than [nanostores](https://bundlephobia.com/package/nanostores@0.8.0) and its [integration](https://bundlephobia.com/package/@nanostores/angular@0.0.4) combined (using the [bundlephobia](https://bundlephobia.com/) metric)
No complex concepts like Reducers, Effects, or Selectors, which must be deeply understood before using. You just need to deal with small atomic stores
* [Nanostores](https://github.com/nanostores/nanostores) has a growing community and its own developing ecosystem: we already have a tiny [router](https://github.com/nanostores/router) for stores, a [persistent store](https://github.com/nanostores/persistent), and a tiny [data fetcher](https://github.com/nanostores/query) 

I believe that with open source projects like this, Angular can soon get rid of boilerplate, complex definitions, and huge packages. Try using [Nanostores](https://evilmartians.com/opensource/nano-stores) in your Angular project to check out all these advantages for yourself.