Video version of this article

When I rebuilt the Angular Movies app shell with StateAdapt, the resulting code was 50% smaller than the original RxAngular version.

Todo MVC UI

What caused the gap?

The application shell's state was minimal — just a boolean flag controlling whether the sidenav was visible.

That simplicity worked in StateAdapt's favor. While RxAngular needs an object to manage, StateAdapt can handle any shape of state, so I used a plain boolean directly. If the requirements change down the road, switching to an object is straightforward.

There was another factor at play. The RxAngular version triggered a method call when the URL changed, rather than responding reactively. StateAdapt handled this reactively from the start, so I suspect the RxAngular implementation could trimmed a few more lines if that pattern had been avoided. With StateAdapt, I made everything as reactive as possible from the outset.

Here's the main StateAdapt code:

  urlChange$ = this.router.events.pipe(
    filter((e) => e instanceof NavigationEnd),
    map((e) => (e as NavigationEnd).urlAfterRedirects),
    distinctUntilChanged(),
    toSource('urlChange$')
  );

  sideDrawerOpen = adapt(['app-shell.sideDrawerOpen', false, booleanAdapter], {
    setFalse: this.urlChange$,
  });
Enter fullscreen mode Exit fullscreen mode

In RxAngular, that same false had to be expressed as:

    this.state.set({ sideDrawerOpen: false });
Enter fullscreen mode Exit fullscreen mode

In the StateAdapt template, I referenced the state source directly from the sideDrawerOpen store. RxAngular, however, requires reacting to an observable:

    this.state.connect('sideDrawerOpen', this.ui.sideDrawerOpenToggle$);
Enter fullscreen mode Exit fullscreen mode

The number of imperative calls is the same across both: one uses .next() in the template, the other uses set().

The most awkward part of the RxAngular version was this piece:

    this.effects.register(
      this.router.events.pipe(
        filter((e) => e instanceof NavigationEnd),
        map((e) => (e as NavigationEnd).urlAfterRedirects),
        distinctUntilChanged()
      ),
      () => this.closeSidenav()
    );
// ...
  closeSidenav = () => {
    this.ui.sideDrawerOpenToggle(false);
  };
Enter fullscreen mode Exit fullscreen mode

The closeSidenav method was invoked from precisely one location. That same side-effect could have been wired up directly as this:

this.state.connect('sideDrawerOpen', urlChange$, () => false);
// I might be wrong on the exact syntax, but I think that's right.
Enter fullscreen mode Exit fullscreen mode

Everything was nested inside an init method, called imperatively from ngOnInit, which Angular itself invokes imperatively:

init() {
// ...
// RxAngular connections, `set`s, effects
// ...
}
Enter fullscreen mode Exit fullscreen mode

Wrapping up

For the complete diff, check out the pull request — though it's best not to open it.

RxAngular remains my go-to choice for state management in Angular. StateAdapt is still in its early stages. I want to apply it across several more projects before I'm ready to tag it as version 1.0. If it looks promising to you, a star on GitHub would be appreciated — and I'd love to hear what you think if you experiment with it.

Thanks for reading!


Repository

StateAdapt

RxAngular

Twitter