Using Angular Signals for Global State
Tags: angular
Published: 2023-07-29 21:26:50

---
title: Using Angular Signals for Global State
published: true
date: 2023-07-29 21:13:05 UTC
tags: Angular,angular
canonical_url: https://jamienordmeyer.net/2023/07/29/using-angular-signals-for-global-state/
cover_image: https://jamienordmeyer.net/wp-content/uploads/2023/07/pexels-karol-d-787667.jpg
---

With the release of Angular 16 we got access to the new [Signals API](https://angular.io/guide/signals), along with a host of other features. They are currently in developer preview meaning that they could technically change between now and the next release of Angular. However, you can use them now, and they provide a much cleaner way to have reactive code in your web application. They DO not replace RxJS Observables; Angular services like [HttpClient](https://angular.io/api/common/http/HttpClient) and [resolvers](https://angular.io/api/router/ResolveFn) still rely on RxJS. However, they do provide another tool in the box for responding to changes in your application that are often easier to understand than the RxJS alternative.

## Code With RxJS

In an application that I’m working on, before Angular 16 shipped, I was using a custom `StoreService` to hold global application state. I’ve tried libraries like [NgRx](https://ngrx.io/) and Akita to manage global state, but found them to be way too heavy-handed for what I wanted (not saying ANYTHING negative towards these libraries; not every tool is right for every job, and the authors of these libraries would probably be the first to tell you that). This custom `StoreService` was created using RxJS, and looked like this:

```typescript
import { Injectable } from '@angular/core';
import { ApplicationState } from '@shared/models';
import { UserCard } from '@shared/models';
import { BehaviorSubject, map } from 'rxjs';

const initialState: ApplicationState = {
  userCard: null,
};

@Injectable({
  providedIn: 'root',
})
export class StoreService {
  private readonly store$ = new BehaviorSubject<ApplicationState>(initialState);
  readonly userCard$ = this.store$.pipe(map((state) => state.userCard));

  setUserCard(userCard: UserCard | null) {
    this.store$.next({
      ...this.store$.value,
      userCard: userCard,
    });
  }
}
```

Here, the UserCard interface represents the basic details of the logged-in user, things like name and data points for putting together a URL to their Avatar image. When I wanted to retrieve the user card for the logged-in user, a service would be used:

```typescript
import { Inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { catchError, mergeMap, Observable, of, tap } from 'rxjs';
import { StoreService } from '@shared/services';
import { UserCard } from '@shared/models';

@Injectable({
  providedIn: 'root',
})
export class UserCardService {
  constructor(
    private http: HttpClient,
    private storeService: StoreService,
    @Inject('API_BASE_URL') private baseUrl: string
  ) {}

  getUserCard(): Observable<UserCard | null> {
    return this.storeService.userCard$.pipe(
      mergeMap((existingUserCard) =>
        existingUserCard
          ? of(existingUserCard)
          : this.http.get<UserCard>(`${this.baseUrl}v1/users/user-card`).pipe(
              tap((userCard) => this.storeService.setUserCard(userCard)),
              catchError(() => {
                this.storeService.setUserCard(null);
                return of(null);
              })
            )
      )
    );
  }
}
```

In the `getUserCard` method on this service, I first check to see if the `StoreService` has a previously loaded UserCard instance. If it does, I just return that. However, if the `StoreService` does not, it will make an HTPP call to retrieve the user card from the server, store that in the `StoreService`, and then return the user card.

For the component that displays the user card data, I would expose properties like this as public properties on the component:

```typescript
  get avatarUrl$(): Observable<string | null> {
    return this.storeService.userCard$.pipe(
      map((uc) =>
        uc?.avatarVersionKey && uc.avatarVersionKey !== 0
          ? `${this.apiBaseUrl}v1/users/${uc?.uniqueKey}/avatar?size=64&v=${uc?.avatarVersionKey}`
          : `/assets/images/avatar_small.png`
      )
    );
  }

  get userFullName$(): Observable<string | null> {
    return this.storeService.userCard$.pipe(
      map((siu) => (siu ? `${siu.givenName} ${siu.surName}` : ''))
    );
  }
```

Then in the HTML I need to use the `async` pipe to get the values out:

```html
<span class="name">{{ userFullName$ | async }}</span>
```

## Coding With Signals

This of course works, and it’s what we’ve been doing for years in Angular. However, now that Signals have arrived, this code can be greatly simplified. Let’s start with the Signals-based implementation of the `StoreService` class.

```typescript
import { Injectable, computed, signal } from '@angular/core';
import { ApplicationState } from '@shared/models';
import { UserCard } from '@shared/models';

const initialState: ApplicationState = {
  userCard: null,
};

@Injectable({
  providedIn: 'root',
})
export class StoreService {
  private readonly _store = signal(initialState);
  readonly userCard = computed(() => this._store().userCard);

  setUserCard(userCard: UserCard | null) {
    this._store.update((s) => ({ ...s, userCard: userCard }));
  }
}

```

So far, it’s not a ton smaller or simplified from the RxJS version. Instead of a `BehaviorSubject` instance, I’m using a signal. The initial state is still being passed in to initialize the signal. I then define the `userCard` value to be a `computed` value. This creates a new signal, based on the `_store` signal in this case, that will automatically notify all of its listeners whenever the `_store` signal is updated, and return just the `UserCard` instance.

When calling `setUserCard`, I don’t need to call a `next$` method. Calling `next$` is an RxJS thing, and at least for me, it’s counter-intuitive. You have to remember that Observable is a stream of events and then `next$` makes sense. However, with signals, I’m calling `update`, which feels more natural. The `update` method passes the current value of the signal to an arrow function, which I’m then spreading into a new object, and replacing the userCard value with the new `UserCard`. Currently, my state ONLY has the `UserCard` field, but as it starts to expand to hold other global state, then this becomes more useful.

The real “Oh…” moment for the cleanliness of the code for me comes in the new implementation of the `UserCardService` class:

```typescript
import { Inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { catchError, Observable, of, tap } from 'rxjs';
import { StoreService } from '@shared/services';
import { UserCard } from '@shared/models';

@Injectable({
  providedIn: 'root',
})
export class UserCardService {
  constructor(
    private http: HttpClient,
    private storeService: StoreService,
    @Inject('API_BASE_URL') private baseUrl: string
  ) {}

  getUserCard(): Observable<UserCard | null> {
    const uc = this.storeService.userCard();
    if (uc) return of(uc);

    return this.http.get<UserCard>(`${this.baseUrl}v1/users/user-card`).pipe(
      tap((userCard) => this.storeService.setUserCard(userCard)),
      catchError(() => {
        this.storeService.setUserCard(null);
        return of(null);
      })
    );
  }
}

```

The `getUserCard` method still returns an Observable, as the resolver consuming it wants an observable. However, the implementation is much easier to understand in my opinion. I can execute the `userCard` method off the `StoreService` to get the current user card if there is one, and because it’s just a method, not an RxJS Observable, I can just return it straight away if I already have it; no need for `mergeMap`. I then call the HTTP endpoint like before, and add the user card to the `StoreService`.

The component fields are also greatly simplified (it’s really nice not having to use the RxJS `pipe` function):

```typescript
  avatarUrl = computed(() => {
    const uc = this.storeService.userCard();
    return uc?.avatarVersionKey && uc.avatarVersionKey !== 0
      ? `${this.apiBaseUrl}v1/users/${uc?.uniqueKey}/avatar?size=64&v=${uc?.avatarVersionKey}`
      : `/assets/images/avatar_small.png`;
  });
  userFullName = computed(() => {
    const uc = this.storeService.userCard();
    return uc ? `${uc.givenName} ${uc.surName}` : '';
  });
```

And now, in the HTML, we no longer need the `async` pipe. Instead, since each of these properties are signals, we access them like methods in the HTML:

```html
<span class="name">{{ userFullName() }}</span>
```

## Final Thoughts

I really like the new Signals API, and am looking forward to seeing where else the Angular team takes it. Will the `HttpClient` class eventually use Signals (maybe via a new class called `HttpSignalsClient` to maintain backward compatibility)? Will there be new constructs for our HTML templates that are Signals aware? Only time will tell. But so far, I really like what I’m seeing.