Signals: what this means for NgRx
Tags: angular, ngrx, javascript, typescript
Published: 2023-04-17 10:33:01

_Original cover photo by [Carlos Alberto Gómez Iñiguez](https://unsplash.com/@iniguez) on Unsplash._

On April 4th, 2023, the Angular team officially launched an [RFC for the new reactive primitive](https://github.com/angular/angular/discussions/49685), signals, that will arrive as a developer preview in Angular v16. This could mean a beginning of a new era of Angular development, less dependence on RxJS, more granular change detection, and maybe even zoneless applications.

But what this means for us, NgRx enthusiasts? Let's find out.

> Note: we assume that the reader is already familiar with the concept of Signals. If not, I suggest reading the aforementioned RFC, or at least the [Sub-RFC 2](https://github.com/angular/angular/discussions/49683) (which talks about the signals themselves) and [Sub-RFC 4](https://github.com/angular/angular/discussions/49681) (which talks about the RxJS-signals interoperability). 

## How NgRx might work with signals

First of all, we need to keep in mind that Angular v16 is going to introduce a new package, `rxjs-interop`, which will contain functions that will allow to conversion of RxJS observables to signals and vice versa. As NgRx is built on top of `Observable`-s, NgRx itself doesn't really need an innate support of signals. Essentially, instead of this:

```typescript
@Component({
  selector: 'app-data',
  template: `
    <p>{{ data$ | async }}</p>
  `,
})
export class DataComponent {
  store = inject(Store);  
  data$ = this.store.select(selectData);
}
```

We can do this:

```typescript
@Component({
  selector: 'app-data',
  template: `
    <p>{{ data() }}</p>
  `,
})
export class DataComponent {
  store = inject(Store);  
  data = toSignal(this.store.select(selectData));
}
```

And that would be it.

However, there are two concerns

1. The code looks a bit verbose, and it will be tedious to call `toSignal` all the time, raising a question - why are we even using `Observable`-s at all?
2. What if I want to build an entire store on signals?

Let's see how the NgRx team proposes addressing these issues. We will go chronologically, so will start with the second point. 

## NgRx Signal Store

On March 6, just 2 days after the Signals RFC, the NgRx core team published their own Signals RFC, titled [NgRx SignalStore](https://github.com/ngrx/platform/discussions/3796). Give it a read if you want more details, but here we will cover it in a shorter way.

1. You will be able to define a store via a special function, `createSignalStore`, and add properties and features to it with other helper functions, like `withState`, `withEffects`, etc. Here is a small snippet:

```typescript
export const counterStore = createSignalStore(
    withState<CounterState>({ count: 0 }),
    withComputed((state) => ({
        doubleCount: state.count * 2,
    })),
);
```

So if we inject this into our component, we would have access to the `count` and `doubleCount` signals, which we can use in our template:

```typescript
@Component({
  selector: 'app-counter',
  template: `
    <p>{{ counterStore.count() }}</p>
    <p>{{ counterStore.doubleCount() }}</p>
  `,
})
export class CounterComponent {
  counterStore = inject(CounterStore);
}
```

And we could update the state with the `update` function:

```typescript
counterStore.update((state) => ({ count: state.count + 1 }));
```

Or we could choose to add custom updater methods:

```typescript
export const counterStore = createSignalStore(
    withState<CounterState>({ count: 0 }),
    withComputed((state) => ({
        doubleCount: state.count * 2,
    })),
    withUpdaters(() => ({
        increment: (state) => ({ count: state.count + 1 }),
    })),
);
```

And use these updater methods in components and so on. It has way more functionality to it, so feel free to explore the RFC, or ask questions either there or here in the comments.

## Existing NgRx stores

Let's now address the first bullet point. If we already have an existing application that uses the conventional, RxJS-based NgRx Store, but we really want to seamlessly use signals instead of `Observable`-s an async pipe without too much code, will NgRx help? Turns out they will

On March 12, the NgRx team published another RFC, that sheds light on how it will support signals for the conventional NgRx Store. The RFC is titled [Integration with Angular Signals and NgRx packages](https://github.com/ngrx/platform/discussions/3843), so again, give it a read if you want more details. But in two words, essentially the `Store` "service" will get another method apart from `select`, named `selectSignal`, which will work in **exactly** the same way as `select`, but will return a `Signal` instead of an `Observable`. The same method will also be added to `ComponentStore`, working in the same fashion. 

So continuing the previous example, now we can just do the following:

```typescript
@Component({
  selector: 'app-data',
  template: `
    <p>{{ data() }}</p>
  `,
})
export class DataComponent {
  store = inject(Store);  
  data = this.store.selectSignal(selectData);
}
```

And that's it. The rest of the store will remain exactly as before, no hassle, no heavy migrations.

## What all of this means?

I would like to address two points:

### 1. Store values would now be directly accessible everywhere.

If we visit the NgRx Store source code and take a look at the [store itself](https://github.com/ngrx/platform/blob/master/modules/store/src/store.ts#L13), we might see that the `Store` injectable extends RxJS `Observable`, and not `BehaviorSubject`, meaning it does not really "hold on to", or at least, give access to the current state in the Store. So something like this would be impossible:

```typescript
@Component({
  selector: 'app-data',
  template: `
    <p>{{ data$ | async }}</p>
  `,
})
export class DataComponent {
  store = inject(Store);  
  someService = inject(SomeService);
  data$ = this.store.select(selectData);

  useData() {
    // do something with this.data
    this.someService.doSomething(this.data$);
  }
}
```

Now this **will not** work, because `this.data$` is an `Observable`, so an event-driven wrapper of the value, and not the value itself. So if we want to use the value in a synchronous manner, we would have to subscribe to it, and then unsubscribe, which is not ideal. Or we can do something like this:

```typescript
@Component({
  selector: 'app-data',
  template: `
    <p>{{ data$ | async }}</p>
    <button *ngIf="data$ | async as data"
            (click)="useData(data)">
       Use data
    </button>
  `,
})
export class DataComponent {
  store = inject(Store);  
  someService = inject(SomeService);
  data$ = this.store.select(selectData);

  useData(data: Data) {
    // do something with data
    this.someService.doSomething(data);
  }
}
```

This will work, but not every scenario is addressed with this solution, and things can be more complex. However, with Signals, we can **always** access the current value:

```typescript
@Component({
  selector: 'app-data',
  template: `
    <p>{{ data() }}</p>
    <button (click)="useData()">Use data</button>
  `,
})
export class DataComponent {
  store = inject(Store);  
  someService = inject(SomeService);
  data = this.store.selectSignal(selectData);

  useData(data: Data) {
    // we can now read from the signal
    this.someService.doSomething(data()); 
  }
}
```

And this is something that can (and probably will) mean some overhaul in how we use the store data in the component code itself.

### 2. Some best practices might need to be rethinked

NgRx has its [own eslint plugin](https://ngrx.io/guide/eslint-plugin), which introduces some (rather strict) rules. One rule is that we should [not use RxJS operators to map and modify the state](https://ngrx.io/guide/eslint-plugin/rules/avoid-mapping-selectors), and instead rely on custom selectors. So the following is considered a bad practice:

```typescript
export class Component {
  name$ = this.store
    .select(selectLoggedInUser)
    .pipe(map((user) => ({ name: user.name })));
}
```

And instead, we should do something like this:

```typescript
// in selectors.ts:
export selectLoggedInUserName = createSelector(
  selectLoggedInUser,
  (user) => user.name
)

// in component:
export class Component {
  name$ = this.store.select(selectLoggedInUserName)
}
```

So the plugin will successfully prevent us from doing this with RxJS-based store data, but with signals, we can kinda work around it using the `computed` function:

```typescript
export class Component {
  user = this.store.selectSignal(selectLoggedInUser);

  name = computed(() => ({ name: this.user().name }));
}
```

Whether this is a bad practice or not still remains to be seen, and is up for debate. In my opinion, we should stick with the previous practice of keeping store calculations on the store side of things, and use the component only for presentation. But this is just my opinion, and I would love to hear yours in the comments `computed` only for combining store signals with local signals. 

## Conclusion

First of all, I would like to extend my appreciation to the [NgRx Core Team](https://ngrx.io/about?group=Core) and [all other contributors](https://ngrx.io/about?group=Community) for once again staying on top of the things and delivering top-notch features quickly and efficiently. I am really excited to see how this will play out, and I am sure that the community will benefit from this greatly. 

Signals are super exciting, and we are in an era of lots of discussion, decision-making, and experimentation. I am sure that we will see more and more of this in the future, and I am really looking forward to it. In this spirit, you are welcome to comment on this article, NgRx RFC-s, and Angular Signal RFC-s to discuss, understand, and learn

Stay tuned for other developments!