62% less code: NGXS vs StateAdapt 1. Angular Ionic Movies
Tags: angular, stateadapt, ngxs
Published: 2022-11-08 08:22:08

---
title: 62% less code: NGXS vs StateAdapt 1. Angular Ionic Movies
published: true
description: I implemented the Angular Ionic Movies app with StateAdapt and the state management code decreased by 62% from what it was using NGXS. 
tags: angular,stateadapt,ngxs
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c9oq2j0pnjxw1d5cip1l.png
# Use a ratio of 100:42 for best results.
series: NGXS vs StateAdapt
---

[YouTube video for this article]()

I implemented the Angular Ionic Movies app with [StateAdapt](https://github.com/state-adapt/state-adapt) and the state management code decreased by 62% from what it was using [NGXS](https://www.ngxs.io/). 

![Angular Ionic Movies](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ecdbs6r9oxq0m8nc41s.png)

## Actions

The first way StateAdapt is more minimal is in how it expresses event sources. This is an action in NGXS:

```tsx
export class AddMovie {
  static readonly type = '[Movies] Add movie';

  constructor(public payload: Movie) {}
}
```

This is the same event source in StateAdapt:

```tsx
addMovie$ = new Source<Movie>('addMovie$');
```

## State changes

This particular NGXS project was using an NGXS Labs plugin that allows you to connect actions with action handlers defined outside the state file. That looks like this: 

```tsx
attachAction(MovieState, AddMovie, addMovie(moviesService));
```

That is pretty similar to StateAdapt's syntax:

```tsx
addMovies: this.addMovieRequest.success$,
```

That extra observable came from a stream that transformed `addMovie$` into some standard HTTP observables using StateAdapt's HTTP utilities:

```tsx
  addMovieRequest = getHttpSources(
    '[Add Movie]',
    this.addMovie$.pipe(
      switchMap(({ payload }) => {
        payload.poster = // COPIED—Don't mutate!
          payload.poster === ''
            ? 'https://in.bmscdn.com/iedb/movies/images/website/poster/large/ela-cheppanu-et00016781-24-03-2017-18-31-40.jpg'
            : payload.poster;

        return this.moviesService.addMovie(payload);
      })
    ),
    (res) => [!!res, res, 'Error']
  );
```

`addMovies` is a pure function inside a state adapter, and it looks like this:

```tsx
add: (state, movie: Movie) => [...state, movie],
```

Note: `add` gets turned into `addMovies` for a reason I'll explain in the next section.

StateAdapt keeps pure functions separate from side-effects and async code as much as possible, and you'll see how that enables some awesome reusability soon.

On the other hand, the function NGXS's action maps to includes multiple concerns and side-effects:

```tsx
export const addMovie =
  (moviesService: MoviesService) =>
  ({ setState }: StateContext<MoviesStateModel>, { payload }) => {
    payload.poster =
      payload.poster === ''
        ? 'https://in.bmscdn.com/iedb/movies/images/website/poster/large/ela-cheppanu-et00016781-24-03-2017-18-31-40.jpg'
        : payload.poster;
    return moviesService.addMovie(payload).pipe(
      catchError((x, caught) => {
        return throwError(() => new Error(x));
      }),
      tap({
        next: (result) => {
          setState(
            patch({
              movies: append([result])
            })
          );
        }
      })
    );
  };
```

This could have been written more succinctly, but the fact that it _can_ be nested so much (as some devs prefer) comes from how many layers of functions need to be called. 

## State adapters

`addMovies` is an awkward name, and I did not choose it. StateAdapt generated it by joining the `moviesAdapter` onto a higher state shape, `CatalogStateModel`:

```tsx
export interface CatalogStateModel {
  movies: Movie[];
  movieForm: MovieForm;
  filter: Filter;
  favorites: Movie[];
}

export const catalogAdapter = joinAdapters<CatalogStateModel>()({
  movies: moviesAdapter,
  movieForm: movieFormAdapter,
  filter: createAdapter<Filter[]>()({ selectors: {} }),
  favorites: moviesAdapter
})();
```

Since `add` is an available state change on `moviesAdapter`, `catalogAdapter` gets it as `addMovies`. Every state change name is split up as `<first word><Property name><Rest of words>`. 

However, I am working on a `createListAdapter` function that will generate state change names like `addOne` and `addMany`, and so on, which are not quite as awkward when converted into `addMoviesOne` and `addMoviesMany`. Still a little awkward, but it's worth it.

Why?

You may have noticed that I have two references to `moviesAdapter` in the last code snippet. This is because while I was busy converting NGXS action handlers into RxJS operators and adapter methods I started typing something familiar inside the `favoritesAdapter`: 

```tsx
add: (state, movie: Movie) => [...state, movie],
```

I realized I had already implemented that in the `moviesAdapter`... In fact, both `favorites` and `movies` had type `Movie[]`. Why shouldn't they have similar patterns of state changes and derived state?

State adapters should really be thought of as companions to data types. This is sort of like object-oriented programming, except with immutability, and messages are defined as independent, self-describing (declarative) entities instead of imperative, forward/downstream-looking commands. This makes a massive difference. This allows proper separation of concerns, whereas traditional OOP _does not_, for anything asynchronous.

More on this in a future article. But I'm really excited about the possibilities. 

Anyway, so that's why 2 event sources and a single, simple state change in StateAdapt came from this in NGXS:

```tsx
export const addMovie =
  (moviesService: MoviesService) =>
  ({ setState }: StateContext<MoviesStateModel>, { payload }) => {
    payload.poster =
      payload.poster === ''
        ? 'https://in.bmscdn.com/iedb/movies/images/website/poster/large/ela-cheppanu-et00016781-24-03-2017-18-31-40.jpg'
        : payload.poster;
    return moviesService.addMovie(payload).pipe(
      catchError((x, caught) => {
        return throwError(() => new Error(x));
      }),
      tap({
        next: (result) => {
          setState(
            patch({
              movies: append([result])
            })
          );
        }
      })
    );
  };
```

```tsx
export const favoriteMovie = (
  { setState }: StateContext<MoviesStateModel>,
  { payload }
) => {
  setState(
    patch({
      favorites: append([payload])
    })
  );
};
```

(Favorites were stored in `localStorage`, not the server.)

Could this have been reused? Yes. One thing I've noticed is that boilerplate, being repetitive itself, obscures repetitive business logic.

## Other notes

### Forms plugin

NGXS had a forms plugin it was using. StateAdapt had to code this from scratch, which added a few lines.

### Local storage plugin

StateAdapt doesn't have a local storage plugin yet, so I had to define a custom state sanitizer like this:

```tsx
import { actionSanitizer, stateSanitizer } from '@state-adapt/core';
import { provideStore } from '@state-adapt/angular';

const enableReduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION__?.({
  actionSanitizer,
  stateSanitizer: (state: any) => {
    const newState = stateSanitizer(state);
    localStorage.setItem('@@STATE', JSON.stringify(newState));
    return newState;
  }
});

export const storeProvider = provideStore(enableReduxDevTools);
```

Normally this would just be

```tsx
import { defaultStoreProvider } from '@state-adapt/angular';
```

### Imperative components

There was so much refactoring I wanted to do in the components... the Angular ecosystem _really_ sucks at providing declarative tools. For example, declarative dialogs are almost completely lacking:

| Framework      | Library 1 | Library 2 | Library 3 |
| ----------- | ----------- | ----------- | ----------- |
| Vue      | ✅ Declarative       | ✅ Declarative       | ✅ Declarative       |
| React   | ✅ Declarative        | ✅ Declarative        | ✅ Declarative        |
| Svelte   | ✅ Declarative        | ✅ Declarative        | ✅ Declarative        |
| Preact   | ✅ Declarative        | ✅ Declarative        | ✅ Declarative        |
| Ember   | ✅ Declarative        | ✅ Declarative        | ✅ Declarative        |
| Lit   | ✅ Declarative        | ✅ Declarative        | ✅ Declarative        |
| SolidJS   | ✅ Declarative        | ✅ Declarative        | ---        |
| Alpine   | ✅ Declarative        | ---       | ---        |
| Angular   | ❌ Imperative        | ❌ Imperative        | ❌ Imperative        |

(I made [a wrapper for Angular Material's dialog component](https://gist.github.com/mfp22/479a6860510286e69d9cb7ea3a448d5e) that you should go copy and paste into your project right now.)

The result was a lot of ugly imperative code in the component: 

```tsx
    this.actions$.pipe(ofActionSuccessful(AddMovie)).subscribe({
      next: () => {
        this.modalCtrl.dismiss();
        this.iziToast.success('Add movie', 'Movie added successfully.');
      },
      error: (err) =>
        console.log(
          'HomePage::ngOnInit ofActionSuccessful(AddMovie) | method called -> received error' +
            err
        )
    });
```

The way this would typically be done in a framework that encourages sanity, is something like this:

- Event fires
- State changes
- DOM reflects state

There were similar atrocities made convenient by NGXS's `dispatch()` returning an observable to allow easy non-unidirectional logic:

```tsx
    this.store
      .dispatch(new FetchMovies({ start: start, end: end }))
      .pipe(withLatestFrom(this.movies$))
      .subscribe({
        next: ([movies]) => {
          setTimeout(() => {
            this.showSkeleton = false;
          }, 2000);
        },
        error: (err) =>
          console.log(
            'HomePage::fetchMovies() | method called -> received error' + err
          )
      });
```

`showSkeleton` sounds a lot like a loading state... Apparently this entire component is an action handler. This is how your code would look without a state management library.

There isn't any reliable unidirectionality in this app. The amount of freedom taken in this codebase (and in other NGXS projects I've seen) results in basically no guarantees of where you could find the cause of any given issue. 

This is why I love declarative programming. It's hard to get used to at first, but once you are used to it, being able to directly inspect the thing that's wrong _itself_ and see what's wrong with _it_—instead of some callback function or action handler that could literally be anywhere—really accelerates debugging. 

## Conclusion

For a full comparison, see [the commit here](https://github.com/mfp22/angular-ionic-state-adapt-movies/commit/15578d49cd418d58ac492d6dd36029774f4869e3).

[NGXS](https://www.ngxs.io/) is a very friendly library. The maintainers are extremely nice and helpful, and there’s a lot of material out there if you get stuck as well as a solid community of developers who can help you. And although the boilerplate is in the same ballpark as NgRx/Store, the philosophy of “progressive state management” means NGXS accommodates all of the imperative coding patterns you are familiar with, while also enabling higher levels of reactivity with RxJS as your skills and reactive habits develop. 

[StateAdapt](https://github.com/state-adapt/state-adapt) is pretty much the complete opposite of NGXS. In fact, it was an NGXS project that first inspired it, and the original prototype was written in an NGXS state file. With a philosophy of [“progressive reactivity”](https://dev.to/this-is-angular/progressive-reactivity-in-angular-1d40), the idea is that your code should always be as close to 100% declarative as possible, even if you have to bend your mind a little at first. But as you work at it, thinking reactively gets more and more as easy as thinking imperatively, and the benefits of reducing spaghetti code will pay massive dividends in the long run.

But 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.

Thanks!

---

[Repo](https://github.com/mfp22/angular-ionic-state-adapt-movies)

[StateAdapt](https://github.com/state-adapt/state-adapt)

[NGXS](https://www.ngxs.io/)

[Twitter](https://twitter.com/mfpears)