3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 13

Resource API Refactor

EXTENDED

Resource API Refactor

Now let’s take a look at how we might improve this application with resource and linkedSignal.

The interesting thing about this refactor is that we aren’t going to refactor to resource or rxResource everywhere in the application. We are going to hit scenarios that show the current limitations of resource.

This refactor is also much more straight forward than the refactor we did for Giflist. For this refactor I will help get things started, and then I will leave the rest of the refactor for you to do as an exercise. Remember that if you get stuck the source code is available.

The MessageService

Let’s start with the service we won’t be refactoring, and why. The reason we won’t be using resource or rxResource to load our messages is this:

  messages$ = this.getMessages().pipe(
    // restart stream when user reauthenticates
    retry({
      delay: () => this.authUser$.pipe(filter((user) => !!user)),
    }),
  );

Although the retry is a critical part for us as well, that’s not the specific reason why we can’t use rxResource here.

Our getMessages gives us a stream of messages coming from our backend. Whenever there is a new message, this emits.

The problem with rxResource is that, even though it allows us to use a stream to pull in data, it only actually uses the first value from that stream. We would get an initial set of messages, but they would never update.

We can see this if we inspect the internals of the rxResouce implementation:

export function rxResource<T, R>(opts: RxResourceOptions<T, R>): ResourceRef<T> {
  opts?.injector || assertInInjectionContext(rxResource);
  return resource<T, R>({
    ...opts,
    loader: (params) => {
      const cancelled = new Subject<void>();
      params.abortSignal.addEventListener('abort', () => cancelled.next());

      // Note: this is identical to `firstValueFrom` which we can't use,
      // because at the time of writing, `core` still supports rxjs 6.x.
      return new Promise<T>((resolve, reject) => {
        opts
          .loader(params)
          .pipe(take(1), takeUntil(cancelled))
          .subscribe({
            next: resolve,
            error: reject,
            complete: () => reject(new Error('Resource completed before producing a value')),
          });
      });
    },
  });
}

This is actually the entire implementation of rxResource, and you may notice that it is basically just a resource with some extra stuff set up automatically for us.

It will use the stream we pass in and basically convert it into a Promise that only returns the first value, we can see that here:

.pipe(take(1), takeUntil(cancelled))

It takes one value and unsubscribes from the stream, because it immediately resolves the promise with that first value:

EXTENDED
Key

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).