DEV Community 👩‍💻👨‍💻

Cover image for 31% less code: RxAngular vs StateAdapt 1. Todo MVC
Mike Pearson for This is Angular

Posted on • Updated on

31% less code: RxAngular vs StateAdapt 1. Todo MVC

YouTube video for this article

I implemented Todo MVC with StateAdapt and the code decreased by 31%.


State change logic

The main difference is how state change logic is defined in StateAdapt. Some may not like how compact it is. However, the compactness enables portability, so I like it. Here it is in StateAdapt:

  todosAdapter = createAdapter<Todo[]>()({
    create: (todos, text: Todo['text']) => [
        id: Math.round(Math.random() * 100000),
        done: false,
    remove: (todos, { id }: Todo) => todos.filter((todo) => !== id),
    update: (todos, { id, text, done }: Todo) => => ( !== id ? todo : { id, text, done })),
    toggleAll: (todos, done: Todo['done']) => => ({ ...todo, done })),
    clearCompleted: (todos) => todos.filter(({ done }) => !done),
    selectors: {
      completed: (todos) => todos.filter(({ done }) => done),
      active: (todos) => todos.filter(({ done }) => !done),
Enter fullscreen mode Exit fullscreen mode

In StateAdapt, you can define state management patterns for each type/interface, and combine adapters to manage the combined type/interface. Like this:

  adapter = joinAdapters<TodoState>()({
    filter: createAdapter<TodoFilter>()({ selectors: {} }),
    todos: this.todosAdapter,
     * Derived state
    filteredTodos: (s) =>
      s.todos.filter(({ done }) => {
        if (s.filter === 'all') return true;
        if (s.filter === 'active') return !done;
        if (s.filter === 'completed') return done;
Enter fullscreen mode Exit fullscreen mode

This takes the state changes and selectors for each child adapter and makes it available on the new parent adapter, by using TypeScript magic to prepend the names by its property name (filter or todos).

This can create some naming awkwardness for selectors. The todosAdapter's selector completed becomes available on the new adapter as adapter.todosCompleted, whereas natural English would have called it adapter.completedTodos. However, since this drills down from general to specific, there is something nice about it too. In any case, it's not a problem.

More naming awkwardness can come from state change names. For now. Because I am working on a potential solution for this. But the issue is with plural vs singular. Since state change names are verbs, I put the first word first, then the namespace the state change came from, then the rest of the state change name. So set on a child adapter becomes setFilter, for example. setToTrue would become setCheckedToTrue if the namespace was checked. But in this example, we have create, as in, create a single todo item. This becomes adapter.createTodos, even though it only creates one. But in the future I plan on creating a list adapter, so this would become addOne, similar to NgRx/Entity (which was a primary inspiration for StateAdapt in the first place).

Anyway, despite this awkwardness, it allows you to easily define compact, reusable state logic.

Here is the same code in RxAngular:

interface Commands {
  create: Pick<Todo, 'text'>;
  remove: Pick<Todo, 'id'>;
  update: Pick<Todo, 'id' | 'text' | 'done'>;
  toggleAll: Pick<Todo, 'done'>;
  clearCompleted: Pick<Todo, 'done'>;
  setFilter: TodoFilter;

// ...
   * UI actions
  private readonly commands = this.factory.create();

   * State
  private readonly _filter$ ='filter');
  private readonly _allTodos$ ='todos');

   * Derived state
  private readonly _filteredTodos$ =
    selectSlice(['filter', 'todos'])
    map(({ todos, filter }) =>
      todos.filter(({ done }) => {
        if (filter === 'all') return true;
        if (filter === 'active') return !done;
        if (filter === 'completed') return done;
  private readonly _completedTodos$ = this._allTodos$.pipe(
    map((todos) => todos.filter((todo) => todo.done))
  private readonly _activeTodos$ = this._allTodos$.pipe(
    map((todos) => todos.filter((todo) => !todo.done))

// ...
     * State handlers
    this.connect('filter', this.commands.setFilter$);
    this.connect('todos', this.commands.create$, ({ todos }, { text }) =>
      insert(todos, {
        id: Math.round(Math.random() * 100000),
        done: false,
    this.connect('todos', this.commands.remove$, ({ todos }, { id }) =>
      remove(todos, { id }, 'id')
      ({ todos }, { id, text, done }) => update(todos, { id, text, done }, 'id')
    this.connect('todos', this.commands.toggleAll$, ({ todos }, { done }) =>
      update(todos, { done }, () => true)
      ({ todos }, { done }) => remove(todos, { done }, 'done')
// ...
Enter fullscreen mode Exit fullscreen mode

RxAngular is awesome because it puts RxJS first, unlike most other state management libraries. The ability to have in your state/store's declaration that it will react to an observable can really make state management cleaner.

RxAngular is awesome.


Another big difference is that I eliminated callback functions. RxAngular's implementation has this:

  setFilter(filter: TodoFilter): void {

  create(todo: Pick<Todo, 'text'>): void {

  remove(todo: Pick<Todo, 'id'>): void {

  update(todo: Todo): void {

  toggleAll(todo: Pick<Todo, 'done'>): void {

  clearCompleted(): void {
    this.commands.clearCompleted({ done: true });
Enter fullscreen mode Exit fullscreen mode

Here's what I changed that to in StateAdapt:

  setFilter =;
  create =;
  remove =;
  update =;
  toggleAll =;
  clearCompleted =;
Enter fullscreen mode Exit fullscreen mode

Although it could have been similar in the RxAngular implementation, I decided not to do it this way, because StateAdapt is extremely committed to completely reactive code organization. Traditionally, callback functions like these were seen as a good practice because they allow future flexibility. But they only allow for imperative flexibility. Observables are already flexible, because anything can refer to them and be updated, as long as there is a declarative API for it. Read more about this philosophy here.

This extreme commitment can make StateAdapt awkward to use when you're dealing with imperative APIs everywhere. But the solution is to create declarative wrapper for those APIs.


For a full comparison, see this PR comparison (but please don't open it).

RxAngular is still currently my top pick for state management in Angular. StateAdapt is still a work in progress. I need to apply it to many more projects before I can have the confidence to release version 1.0. If you think it has potential, I'd appreciate a star, and I'd love for you to try it out and share your thoughts.







Top comments (2)

pterpmnta profile image
Pedro Pimienta M.

Great, Is this a alternative to Redux in Angular?

mfp22 profile image
Mike Pearson

Yes. It accomplishes the same unidirecionality, but with much less boilerplate.

Want to Create an Account?
Now it's your turn!
🗒 Share a tutorial
🤔 Reflect on your coding journey
❓ Ask a question

Create an account to join hundreds of thousands of DEV members on their journey.