State as a Service with NGXS ⚔️

Ivanov Maxim
NGXS Stories
Published in
9 min readMay 26, 2020

Introduction

Hello everyone, my name is Maxim Ivanov. I am a member of the NGXS team and the organizer NGXS-Labs. I’ve been with the team for 2 years and I’ve learned a lot. All this time I’ve been searching for new approaches for state management.

In Angular applications, basically everyone uses NGRX. However, I’d like to share my experience back when we had a medium-large enterprise application. We started with NGRX, but it quickly became clear that:

  • NGRX code is difficult for teammates to understand and write;
  • NGRX is boilerplate hell. You spend lot of time with it;
  • The concept of “Effects” is good, but it just adds extra layers of complexity which could be simplified.
NG Colombia

Even though, for a long time there are such as NGXS and Akita. Akita is the only solution not powered by a Redux-like pattern. Akita is simply a wrapper for rxjs which to reduce your boilerplate code, it doesn’t contain much more than NGRX or NGXS. That is why our team is trying to find new solutions and experimenting with NGXS Labs.

Implement Flux/Redux with Angular + RxJS

Redux was popularized by React, so it got embedded into the collective consciousness of a whole crop of newer web developers. Angular does not use a state management system on purpose. Therefore you can use Angular with RxJS today instead any state managers.

Angular services are singletons, which means you can have services tracking state across your application.

store.ts
counter.store.ts

So, we implemented our own Flux pattern with Angular and RxJS. Well, why is this approach bad? Why can’t we only write state management with Angular and RxJS? Let’s analyze it a bit:

Pros

  • You know how application manage your data streams;
  • Your services are tree-shakable (import of services only on demand);
  • You will receive minimum code (rxjs + angular providers) in your bundle after compiled;
  • Also, you can invent your powerful solutions and control it with RxJS hight-order containers (AsyncSubject, ReplaySubject, BehaviorSubject, Subject).

Cons

  • If you have many projects you will need to reuse your own solution: create documentation for new developers and periodically support your solution (fix bugs, add new features);
  • This approach does not offer features that state management like NGRX, NGXS, or Akita offers out-of-the-box, for example, forms or router synchronization, offline persistence, lazy-loading, an entity manager, cross-state selection, etc.

It is not always, however, that a state management framework solution is needed. For very small applications, it is easier to create using plain RxJS, if you are quite skilled.

Evaluate NGRX alternative

First, let’s look at NGXS.

NGXS is a state management pattern + library for Angular. It acts as a single source of truth for your application’s state, providing simple rules for predictable state mutations. 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.

CQRS stands for Command Query Responsibility Segregation

Command–query separation (CQS) is a principle of imperative computer programming.

It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both.

CQRS fits well with event-based programming models.

So we see that can only changed state with by sending command.

counter.state.ts

And for display data in the component we just need add selector.

counter.component.ts

We know that NGXS appeared in 2018 and is just a rethinking of NGRX. I have been working with NGXS for 2 years today and can give some appreciation.

Based on Google Trends, NGRX is obviously the most searched for state manager. The reason is likely that it was the first Redux implementation available for Angular. If we talk about the pros and cons of the approach, then it is impossible to identify a criteria. So we’ll just try to compare NGXS with classic RxJS.

Pros

  • You have a CQRS ready-made solution of out-of-the-box;
  • Your states are services, this means Angular services can be injected into state classes making it easier to take advantage of more Angular features;
  • NGXS is a RxJS wrapper to let users take advantage of the benefits of Observables but in many cases treat them as an implementation detail of the library rather than a pre-requisite;
  • Immutable data out-of-the-box for application;
  • The store and unidirectional data flow greatly reduce coupling between parts of your application. This reduced coupling reduces the complexity of your application since each part only cares about specific states;
  • Since this pattern (Redux) is widely adopted and simple, it is easy for new team members experienced with state management to ramp up quickly;
  • You can persist some of the app’s state to local storage and restore it after a refresh. This can be really nifty;
  • Debuggable for days. By logging actions and state, it is easy to understand coding errors, network errors and other forms of bugs that might come up during production.

We’ll talk about the cons a little later in comparison with Akita.

Akita

As I said above that Akita is the only solution not powered by a Redux-like pattern. 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.

However, in Akita the CQRS pattern is much better implemented than in NGXS. But at the same time you don’t operate with command/actions, you use a high-level API (setState, update, etc).

High-level Akita Principles

  • The Store is a single object that contains the store state and serves as the “single source of truth.”;
  • The only way to change the state is by calling setState() or one of the update methods based on it (high-level API);
  • A component should NOT get data from the store directly but instead use a Query (CQRS principles);
  • Asynchronous logic and update calls should be encapsulated in facade services.

In fact, all this sounds very promising. Sometimes this is exactly what I miss in NGXS.

Pros

  • Same advantages as NGXS;
  • High-level API (Angular way);
  • Akita use the Flux instead the Redux pattern. Basically, Flux has multiple stores by design that compute state change in response to UI/API interactions with components and broadcast these changes as events that components can subscribe to.

NGXS Data plugin (persistence API)

or npm install @ngxs-labs/data

The main purpose of this plugin is to provide the data layer of abstraction for states.

Also automates the creation of actions, dispatchers, and selectors for each entity type.

High level architecture

NGXS Persistence API is an extension based the Repository Design Pattern that offers a gentle introduction to NGXS by simplifying management of entities or plain data while reducing the amount of explicitness.

Simple use NGXS

In fact, I solved the problems NGXS that I wanted to talk about in the form of a developed plugin. The main idea is to achieve the same advantages with NGXS as Akita. Some of the problems that we will talk about may seem insignificant to you. But we should still pay some attention to them.

Problem 1. Boilerplate hell

Of course, NGXS reduces the amount of code compared to NGRX. However, you still have to do some things over and over the same thing:

❌ Basic actions (setState, reset):

As you know, there can be many states in your project! And it turns out that every time you have to create basic actions (setState, reset) for these states. This is not very convenient when the project is too large and you have to refactor it.

✅ NGXS Data solution:

All you need to do is add a StateRepository decorator over the state class and inherit from the NgxsDataRepository<T> class.

In this case, since the new approach saves you from creating actions, you just need to use the public API. This is very convenient because you can use typed methods on your states.

Problem 2. Snapshot(s)

❌ selectSnapshot

Very often we just want to get some value without subscribing to the state.

In the NGXS you need to create selectors every time to get state snapshots.

✅ NGXS Data solution:

In the data plugin this does not need to be done. You have a getState() method, but also a ready-made snapshot.

Problem 3. Lifecycle

This item is more likely not about the problem, but about the lack of some hooks.

💛 NGXS Lifecycle

✅ NGXS Data solution:

  • ngxsDataDoCheck() — Called after ngxsAfterBootstrap() and called every time a state is reinitialized after a state reset.
  • ngxsDataAfterReset() — Called every time after reset().

Now you can track your state changes after reset. And also resubscribe to some kind of business logic in case of a new state initialization.

Problem 4. Storage plugin

❌ Only localStorage or sessionStorage

However, in some other way, you cannot more flexibly configure saving your state, which is not very convenient! By default, all your states will be stored in localStorage. And you cannot combine localStorage and sessionStorage without additional adapters.

✅ NGXS Data solution:

Persistence API

Problem 5. Entity plugin

Entity provides an API to manipulate and query entity collections:

  • Provides performant CRUD operations for managing entity collections;
  • Extensible type-safe adapters for selecting entity information;
  • Entity promotes the use of plain JavaScript objects when managing collections. ES6 class instances will be; transformed into plain JavaScript objects when entities are managed in a collection.

What is an Entity?

In NGXS, we store different types of state in the store, and this typically includes:

  • business data, such as for example Courses or Lessons, in the case of an online course platform;
  • some UI state, such as for example UI user preferences.

An Entity represents some sort of business data, so Course and Lesson are examples of entity types.

In our code, an entity is defined as a Typescript type definition. For example, in an online course system, the most important entities would be Course and Lesson, defined with these two custom object types:

❌ NGXS has no plugin by default for entity managments

✅ NGXS Data solution:

NgxsDataEntityCollectionsRepository API

Problem 6. Unit testing

Unit testing is easy with NGXS. To perform a unit test we just dispatch the events, listen to the changes and perform our expectation. A basic test looks like this:

❌ NGXS has no testing tools

✅ NGXS Data solution:

As you can see, testing states is much easier.

Resume

Today we learned that NGXS is a very extensible state manager. You can easily work with your states as with ordinary Angular services. In this case you do not need to rewrite the codebase, because under the hood the same actions are called as in regular NGXS.

--

--

Ivanov Maxim
NGXS Stories

Code 🤖, Bike 🚵 and Music 🎶 Teams: @splincodewd ★ @Angular-RU ★ @ngxs ★ github.com/splincode