Pushing Angular's Reactive Limits: Router and Component Store
Tags: angular, ngrx, stateadapt
Published: 2022-11-14 19:26:16

---
title: Pushing Angular's Reactive Limits: Router and Component Store
published: true
description: I converted a component from an imperative approach using NgRx/Component-Store to a completely reactive approach. I decide to create a wrapper component for reactive router navigations. I also decide to define some reactive utilities for NgRx/Component-Store to handle state updates reactively. 
tags: angular,ngrx,stateadapt
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eid3xlwfdc7fe1mwiphm.png
# Use a ratio of 100:42 for best results.
# published_at: 2022-11-10 08:13 +0000
series: Pushing Angular's Reactive Limits
---

[YouTube](https://youtu.be/EULYt4sHD1k)

In an imperative app, events trigger event handlers, which are containers of imperative code. This seems to be the most common approach in Angular apps, unfortunately. A typical app might have a function like this:

```tsx
  navigateBack() {
    this.store.deleteMovies();
    this.store.switchFlag(false);
    this.router.navigate(['/']);
  }
```

That event callback is controlling 3 pieces of state: `store.movies`, `store.flag` and the app's URL.

![Imperative Callback](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/peing2mfi5lorkpdxiam.png)

This isn't great separation of concerns, because logic that controls what `store.flag` and `store.movies` are is scattered across many callbacks such as `navigateBack`, so to understand why anything is the way it is at a given time, you have to refer to many locations across the codebase. Basically, you need "Find All References", which can get really annoying.

So getting rid of event handlers/callbacks seems like a good potential place to start refactoring to more reactivity.

And what would the reactive version look like?

A simple rule for reactivity is this: **Every user event in the template pushes the most minimal change to a single place in our TypeScript, then everything else reacts to that.** Here's a diagram of that:

![Reactive Source Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxdq2gqrfvj6hwdpefxm.png)

As you can see, the logic that controls each piece of state has been moved next to the state it controls. This makes it easier to avoid bugs, since we can easily refer to similar logic when writing new logic to control state.

# Reactive Implementation

Now the question is how to implement this in code. 

First, let's say we create a subject to represent the clicks on the back button: ```tsx
backClick$ = new Subject<void>();
```

Now that we have an observable, what do we do with all these methods from the imperative click handler?

```tsx
    this.store.deleteMovies();
    this.store.switchFlag(false);
    this.router.navigate(['/']);
```

[The particular app I'm working on](https://github.com/mfp22/ngrx-cs-stateadapt-sylomovies) is using [NgRx/Component-Store](https://ngrx.io/guide/component-store), which, unfortunately, does not give us a way to update state reactively; there's nowhere to plug in our click source observable, so we have to call methods on a class. 

We have 3 options that I know of: [Create a custom wrapper around NgRx/Component-Store](https://dev.to/this-is-angular/choosing-a-state-management-library-for-progressive-reactivity-in-angular-21j6) that lets us plug in observables; migrate to [RxAngular/State](https://www.rx-angular.io/docs/state); or migrate to [StateAdapt](https://state-adapt.github.io/), which isn't even on version 1.0 yet. I think the obvious choice is going to be StateAdapt, since I made it and I love it 😀 (btw: 1.0 is coming within a month 🤞)

Edit: NgRx/Component-Store actually can take in observables to update state, but I like the override I created for `select` because it passes subscriptions through to the update observables, just like StateAdapt. 

# Router State

But wait. What do we do about the router? There's no reactive alternative to this method:

```tsx
    this.router.navigate(['/']);
```

In my series on progressive reactivity in Angular, I wrote an article called [Wrapping Imperative APIs in Angular](https://dev.to/this-is-angular/wrapping-imperative-apis-in-angular-550b). I complained about the Angular ecosystem lacking a lot of declarative APIs. For example, Angular is the only front-end framework out of the 9 most popular where every component library I've seen uses imperative APIs for opening and closing dialogs:

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

We shouldn't just accept this because this is how things have always been in Angular. Instead, we should create wrapper components that can be used declaratively in templates and abstract away the imperative `this.dialog.open()` method calls.

But what about router state?

I'm not aware of any front-end framework that has a way to have the URL react to application state. That doesn't itself mean it's a terrible idea, but maybe it is.

I've thought quite a bit about this and can't decide what to do. So let's think through this more now.

First of all, it's not a good idea to have a central place where all sources of navigation events from the entire app are directly imported and combined into a stream and passed into a declarative API. The reason is because many features need to be lazy-loaded, and importing the entire app in one place would undermine that. 

Instead, something like the way declarative dialogs work might be a good idea: Yes, there is something central being controlled (the global modal element & backdrop), but it is easy to provide a wrapper component that can be used in any component, and _pretend_ the modal is being opened right in the component triggering it:

```html
<h1>Some Component Template</h1>
<dialog [open]="dialogIsOpen$ | async">
  <app-dialog-content-component></app-dialog-content-component>
</dialog>
```

Could we do something like that for navigation? How's this:

```html
<h1>Some Component Template</h1>
<button (click)="backClick$.next()">Back</button>
<app-navigate url="/" when="backClick$"></app-navigate>
```

You know what... That reminds me of something:

```html
<a routerLink="/">Back</a>
```

So it turns out it _is_ okay to navigate declaratively in the template! It's just that whenever we do it typically, there's a tight, closed loop from the click to the navigation. Is it okay to loosen that loop a little bit with that `app-navigate` component?

Let's imagine we load a route, and then for some reason the app navigates away from it. Would it be easier to figure out why if the `navigate` method was being called in the component class rather than by a component in the template? 

Well, any component in the template might be calling the `navigate` method internally anyway, since the router can be injected anywhere... And the fact that the wrapper component's selector would be `app-navigate` makes it pretty obvious what's happening, doesn't it?

So, I am going to create this wrapper component and see how it goes. Here's the source code for it:

```tsx
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { Router, RouterModule } from '@angular/router';
import {
  BehaviorSubject,
  filter,
  Observable,
  of,
  switchAll,
  tap,
  withLatestFrom,
} from 'rxjs';

@Component({
  standalone: true,
  selector: 'app-navigate',
  template: '<ng-container *ngIf="navigate$ | async"></ng-container>',
  imports: [RouterModule, CommonModule],
})
export class NavigateComponent {
  @Input() set to(val: string) {
    this.toInput$.next(val);
  }
  toInput$ = new BehaviorSubject<string>('');

  @Input() set when(val: Observable<any>) {
    this.whenInput$.next(val);
  }
  whenInput$ = new BehaviorSubject<Observable<any>>(of(null));

  when$ = this.whenInput$.pipe(switchAll());
  navigate$ = this.when$.pipe(
    withLatestFrom(this.toInput$),
    filter(([, url]) => url !== ''),
    tap(([, url]) => this.router.navigate([url]))
  );

  constructor(private router: Router) {}
}
```

However, for this specific case, I don't see any reason I have to use this component. Instead of this data flow:

![Reactive Source Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxdq2gqrfvj6hwdpefxm.png)

Why don't I just turn that back button into a link and have the data flow go like this?

![Reactive Source 2 Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mxalceo7gtf56rgdl5ym.png)

Instead of reacting to `backClick$`, we can have the other two methods react the the URL changing back to `home`.

So how do we get those store methods to react?

# NgRx/Component-Store

Our 2 store methods need to react to an observable, so we have 2 choices: Either we can convert the whole store to StateAdapt right now, or we could try a more incremental approach by implementing the reactive utilities I explained in [this article](https://dev.to/this-is-angular/choosing-a-state-management-library-for-progressive-reactivity-in-angular-21j6) first. 

Let's do this incrementally. 

You can see the source code for the reactive wrapper class I made [here](https://stackblitz.com/edit/angular-ivy-2xdu2h?file=src%2Fapp%2Freactive-store.ts), but what matters is that it allows us to do this:


```tsx
    this.react<AppStore>(this, {
      deleteMovies: urlFromMovieDetailToHome$.pipe(map(() => undefined)),
      switchFlag: urlFromMovieDetailToHome$.pipe(map(() => false)),
    });
```

This is made possible by directly importing `ReactiveStore` and extending that class instead of `ComponentStore`. Now, whenever `urlFromMovieDetailToHome$` emits, it will trigger the methods on the left-hand side, and pass the emitted value into them. 

In order to define `urlFromMovieDetailToHome$`, I injected the router into the Component Store class and listened to the events for a specific transition:

```tsx
    const urlFromMovieDetailToHome$ = this.router.events.pipe(
      filter((event): event is NavigationEnd => event instanceof NavigationEnd),
      pairwise(),
      filter(
        ([before, after]) =>
          before.url === '/movie' && ['/home', '/'].includes(after.url)
      ),
    );
```

It's a bit complicated, but this will simplify a bit when we swap in StateAdapt.

But we now have update logic right in the store next to the state it's updating! Now, if we're ever wondering why movies were deleted, we can just look in the class and see how `urlFromMovieDetailToHome$` is defined! Declarative code is nice. Granted, these update methods are technically imperative, but I think of the class as being one, self-contained thing, so this is close enough to declarative for me.

# Takeaways from Event #1

Even though we only refactored one event to reactivity, it feels like we accomplished a lot already. So far, we have decided to

1. navigate reactively by using a wrapper component for `router.navigate`
2. incrementally move NgRx/Component-Store to more reactive syntax

# Finishing up the component

The remainder of the component is just a bunch of downstream RxJS stuff. As it's written, we have subscriptions inside subscriptions, and a lot of imperative statements:

```tsx
  cast!: any[];
  movie!: MovieModel;

  // ...

  ngOnInit(): void {
    this.store.state$.subscribe((res) => {
      let movie = res.movieSelected;
      if (movie == null) {
        this.router.navigate(['/']);
      } else {
        this.singleMovie.getMovieDetails(movie.id).subscribe((data: any) => {
          this.movie = data;
          this.movie.poster_path = `${environment.imageUrl}${this.movie.poster_path}`;
        });
        this.singleMovie.getCast(movie.id).subscribe((data: any) => {
          this.cast = Array.from(data.cast);
          this.cast = this.cast.filter((c) => c.profile_path != null);
          this.cast.forEach((c) => {
            c.profile_path = `${environment.imageUrl}${c.profile_path}`;
          });
        });
      }
    });
  }
```

Now that we have a declarative way to navigate, this doesn't look hard at all!

I found an observable on the store called `movieSelected$`, so, I'll define two observables chaining off of that:

```tsx
  movieSelectedNull$ = this.store.movieSelected$.pipe(
    filter((movie) => movie == null)
  );
  movieNotNull$ = this.store.movieSelected$.pipe(
    filter((movie) => movie != null)
  ) as Observable<MovieModel>;
```

We'll feed `movieSelectedNull$` into the template to trigger the navigation to `home`: 

```html
<app-navigate to="/" [when]="movieSelectedNull$"></app-navigate>
```

Now we can use the non-null movie observable to define `movie$`, the reactive replacement for the `movie` property from the imperative implementation:

```tsx
  movie$ = this.movieNotNull$.pipe(
    switchMap((movie) => this.singleMovie.getMovieDetails(movie.id)),
    map((data: any) => ({
      ...data,
      poster_path: `${environment.imageUrl}${data.poster_path}`,
    }))
  );
```

Finally, we can define `cast$`, the reactive replacement for the `cast` property from the imperative implementation:

```tsx
  cast$ = this.movie$.pipe(
    switchMap((movie) =>
      this.singleMovie.getCast(movie.id).pipe(
        map((data: any) =>
          (Array.from(data.cast) as any[])
            .filter((c) => c.profile_path != null)
            .map((c) => ({
              ...c,
              profile_path: `${environment.imageUrl}${c.profile_path}`,
            }))
        )
      )
    )
  );
```

And then we update the template, and we're done!

[Here's the full commit.](https://github.com/mfp22/ngrx-cs-stateadapt-sylomovies/commit/ad6f4730785653822164882ad0374741e22d24a5)

# Conclusion

So far this process of starting with events and working downstream is going pretty smoothly. 

Previously, there were 13 imperative statements, and lots of mixed concerns, as you can see from this color-coded screenshot:

![info-movie.component-before](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f456rkny4zb07ukxqlqr.png)

A little bit of this was generic cleanup, but for the most part, reactivity simplified it a lot with better separation of concerns, and 0 imperative statements:

![info-movie.component-after](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50k75ywxhbnx97dxjjfz.png)

This looks like a lot less code, but remember that we moved a bunch over to the store, and added a little to the template. In total, the reactive implementation was actually 5 lines of code more. `git stat` said the commit had 63 insertions and 58 deletions.

But the better separation of concerns is definitely worth it! And by the end of refactoring this app to reactivity, I suspect that the reactive implementation will actually be less code.

We'll see!

---

In the meantime, check out [StateAdapt](https://state-adapt.github.io/)!