Skip to content
View lessons Login

Launch sale! Get 30% OFF expires in 126:46:22

3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Resource API Refactor

Resource API Refactor

As I mentioned in the introductory modules, the resource API offers a lot of power, and a great developer experience, but it also sort of muddies the boundary between the role of RxJS and signals.

Since these APIs are still marked as experimental in Angular I did not want to rely on using them as the default approach, but it likely will become the default at some point.

In this lesson, we are going to look at how to refactor the application to utilise these APIs.

Refactoring the ChecklistService

We are going to modify ChecklistService to utilise resource and linkedSignal. We are going to use resource (via the implementation we already have in the StorageService) to handle loading our checklist data.

We will then use linkedSignal to take that data and create a WritableSignal that we can then update with our action sources (e.g. add$, edit$).

It is a big ask, but if you’re up to the challenge see if you make some progress toward getting this implemented by yourself before continuing. Even just thinking about some of the things that might be changed will be helpful.

import {
Injectable,
effect,
inject,
linkedSignal,
} from '@angular/core';
// sources
loadedChecklists = this.storageService.loadedChecklists;
add$ = new Subject<AddChecklist>();
edit$ = new Subject<EditChecklist>();
remove$ = this.checklistItemService.checklistRemoved$;
// state
checklists = linkedSignal({
source: this.loadedChecklists.value,
computation: (checklists) => checklists ?? [],
});
constructor() {
this.add$
.pipe(takeUntilDestroyed())
.subscribe((checklist) =>
this.checklists.update((checklists) => [
...checklists,
this.addIdToChecklist(checklist),
]),
);
this.remove$
.pipe(takeUntilDestroyed())
.subscribe((id) =>
this.checklists.update((checklists) =>
checklists.filter((checklist) => checklist.id !== id),
),
);
this.edit$
.pipe(takeUntilDestroyed())
.subscribe((update) =>
this.checklists.update((checklists) =>
checklists.map((checklist) =>
checklist.id === update.id
? { ...checklist, title: update.data.title }
: checklist,
),
),
);
// effects
effect(() => {
const checklists = this.checklists();
if (this.loadedChecklists.status() === 'resolved') {
this.storageService.saveChecklists(checklists);
}
});
}

There are a few differences here, so let’s talk through them.

The biggest difference is that we have replaced our checklistsLoaded$ source with loadedChecklists which contains the result from the resource API. This means that we no longer have the subscribe to checklistsLoaded$ in the constructor and we don’t need to worry about manually handling the loaded and error state so we are able to remove those as well.

This is a pretty big win, this is all of the boilerplate we are able to just instantly get rid of:

export interface ChecklistsState {
checklists: Checklist[];
loaded: boolean;
error: string | null;
}
private state = signal<ChecklistsState>({
checklists: [],
loaded: false,
error: null,
});
checklists = computed(() => this.state().checklists);
loaded = computed(() => this.state().loaded);
error = computed(() => this.state().error);
this.checklistsLoaded$.pipe(takeUntilDestroyed()).subscribe({
next: (checklists) =>
this.state.update((state) => ({
...state,
checklists,
loaded: true,
})),
error: (err) => this.state.update((state) => ({ ...state, error: err })),
});

Instead of all of that we just have:

loadedChecklists = this.storageService.loadedChecklists();

Pretty cool, right? But we’ve also added this:

checklists = linkedSignal({
source: this.loadedChecklists.value,
computation: (checklists) => checklists ?? [],
});
STANDARD STANDARD

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