State Management In Angular Applications

State Management in Angular Applications In these days, when people speak about state management in Front-End applications most of the time they are talking about Redux, but there are some interesting alternatives that should be taken into account. For this post, I will talk about some of those alternatives, focusing on Angular-ready implementations.

NgRx

NgRx is a Redux implementation made with RxJS, so, if you know Redux, and you know RxJS, you won’t have any problem working with it. It’s the most popular State Management library for Angular, but also comes with all the Redux trade-offs such as the amount boilerplate code needed to setup your project. Some of the features it offers as seen in it’s official website are:

  • Managing global and local state.
  • Isolation of side effects to promote a cleaner component architecture.
  • Entity collection management.
  • Integration with the Angular Router.
  • Developer tooling that enhances developer experience when building many different types of applications.

I won’t show NgRx code because as I said, it’s essentialy Redux + RxJS.

NGXS

I’m really in love with this library. NGXS is modeled after the CQRS pattern popularly implemented in libraries like Redux and NgRx but reduces boilerplate by using modern TypeScript features such as classes and decorators.

In NGXS you manage your state with the following pieces of code:

Actions

Actions are just the same than Redux Actions:

export class AddAnimal {
  static readonly type = '[Zoo] Add Animal';
  constructor(public name: string) {}
}

StateModel

The StateModel class is just a representation of the data you will store.

export class ZooStateModel {
  animals: Animal[];
}

State

The State class is the Service where you will define Selectors and Action Handlers.

@State<ZooStateModel>({
  name: 'zoo',
  defaults: {
    animals: []
  }
})
@Injectable()
export class ZooState {

  /** You will be able to subscribe this selector in your components **/
  @Selector()
  static pandas(state: ZooStateModel) {
    return state.animals.filter(animal => animal.indexOf('panda') > -1);
  }

  /** This method will be called when you dispatch a FeedAnimals Action **/
  @Action(AddAnimal)
  feedAnimals(
    { setState, getState }: StateContext<ZooStateModel>,
    { payload }: AddAnimal
  ) {
    const animals = getState().animals;
    setState({
      animals: animals.concat(payload)
    });
  }
}

Component

In your components, you can use the State selectors, and also inject the Store so you can dispatch actions.

@Component({
  selector: 'app-zoo',
  templateUrl: './zoo.component.html'
})
export class ZooComponent {
  
  @Select(ZooState.pandas) pandas$: Observable<string[]>;
  
  constructor(private store: Store) {}
  
  addAnimal(name: string) {
    this.store.dispatch(new AddAnimal(name));
  }
}

Akita

Akita is a state management pattern, built on top of RxJS, which takes the idea of multiple data stores from Flux and the immutable updates from Redux, along with the concept of streaming data, to create the Observable Data Store model.

Akita solves the State Management problem with this pieces:

State

In Akita, the State is the same than NGXS’s StateModel. Just a representation of the data you will store.

export class ZooState {
  animals: Animal[];
}

Store

Unlike NGXS that has a single Store with multiple States with Action Handlers, Akita has multiple Stores. You can create a Store just extending the Store class.

import { Store, StoreConfig } from '@datorama/akita';

@StoreConfig({ name: 'zoo' })
export class ZooStore extends Store<ZooState> {
  constructor() {
    super([]);
  }
}

Services

You then can add it to your services and work with it.

import { ZooStore } from './zoo.store';
 
export class ZooService {
  constructor(private zooStore: ZooStore) {}

  addAnimal(name: string) {
    this.zooStore.update(state => ({
      animals: state.animals.concat(name)
    }));
  }
}

Queries

For the Selectors part, Akita uses what they call Queries.

import { Query } from '@datorama/akita';
import { ZooState } from './zoo.state';
import { ZooStore } from './zoo.store';

export class ZooQuery extends Query<ZooState> {
  pandas$ = this.select(state => state.animals.filter(animal => animal.indexOf('panda') > -1));
  
  constructor(protected store: ZooStore) {
    super(store);
  }
}

As you can see, Akita has in common with NGXS that it tries to reduce the boilerplate code, but it does in a different way.

Another interesting thing about Akita is that it’s compatible with Vanilla JS so if you want to make framework agnostic code, you may be interested on this one.

TL;DR

If you’re planning to add State Management to your application, I think you should stop just adding Redux and evaluate other options that may fit better and also reduce complexity. One interesting thing about the libraries I chose for this article is that all of them have plugins for the Redux DevTools.

Written on February 2, 2021