The Resource API includes resource and rxResource
functions whose default value is undefined. When developers only provide a loader, the return type of the functions is T | undefined. In Angular 19.2.0, the Resource API will add a defaultValue option that overrides the undefined, and the functions return type T. This default value is used when the resource is undefined, loads data, or throws an error.
In this blog post, I will show you how to use the defaultValue
option to return a dummy Person object in the rxResource
function.
Add defaultValue option to the Resource API
Use rxResource to retrieve Star Wars details
export function getPerson() {
assertInInjectionContext(getPerson);
const http = inject(HttpClient);
return (id: number) => {
return http.get<Person>(`https://swapi.dev/api/people/${id}`).pipe(
delay(500),
map((p) => ({...p, id } as Person)),
catchError((err) => {
console.error(err);
return throwError(() => err);
}));
}
}
The getPerson
function calls the endpoint to retrieve a Star Wars character. If the HTTP request throws an error, the function catches it, logs the error message, and rethrows it. The HTTP request adds 500 milliseconds delay to simulate long resource loading.
const DEFAULT: Person = {
id: -1,
name: 'NA',
height: 'NA',
mass: 'NA',
hair_color: 'NA',
skin_color: 'NA',
eye_color: 'NA',
gender: 'NA',
films: [],
}
export function getStarWarsCharacter(id: Signal<number>, injector: Injector) {
return runInInjectionContext(injector, () => {
const getPersonFn = getPerson();
const starWarsResource = rxResource({
request: id,
loader: ({ request: searchId }) => getPersonFn(searchId),
defaultValue: DEFAULT
}).asReadonly();
return computed(() => ({
value: starWarsResource.value(),
status: starWarsResource.status(),
}));
});
}
The getStarWarCharacter
function creates a resource that queries a Star Wars character by an id. The resource has a new defaultValue
property that returns a dummy Person object when the resource is undefined, loading, or has an error. The function returns a computed signal consisting of the resource status and resource value.
Refactor the CharacterComponent
export class CharacterComponent {
searchId2 = signal(initialId);
injector = inject(Injector);
person = getStarWarsCharacter(this.searchId2, this.injector);
}
Using rxResource
, I successfully query the Star Wars character from the backend server in the CharacterComponent
component.
<h3>Display one of the 83 Star War Characters</h3>
<div class="border">
<p>Resource Status: {{ person().status }}</p>
<app-character-info [info]="person().value" />
</div>
<app-character-picker (newSearchId)="searchId2.set($event)" />
The CharacterComponent
component displays the resource status and passes the Star Wars character in the CharacterInfoComponent
component. The CharacterPickerComponent
component changes the ID and emits it to the parent component.
Refactor the CharacterInfoComponent
@Component({
selector: 'app-character-info',
standalone: true,
template: `
@let person = info();
<p>Id: {{ person.id }} </p>
<p>Name: {{ person.name }}</p>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CharacterInfoComponent {
info = input.required<Person>();
}
The CharacterInfoComponent
is a presentation component that accepts the Star Wars character's ID and details. I moved the template logic to a new component such that the ChartacterComponent looked cleaner.
The rxResource
function uses the defaultValue
option to return a dummy Person object; therefore, the info
signal input can never be undefined. The type of the signal input is Person
and the template does not need to check info
for undefined.
Refactor the CharactePickerComponent
<div class="container">
<button (click)="delta.set({ value: -1 })">-1</button>
<button (click)="delta.set({ value: 1 })">+1</button>
</div>
export class CharacterPickerComponent {
newSearchId = output<number>();
delta = signal<{ value: number }>({ value: 0 });
characterId = linkedSignal<{ value: number }, number>({
source: this.delta,
computation: ({ value }, previous) => {
const previousValue = !previous ? initialId : previous.value;
return previousValue + value;
}
});
}
When the delta
signal receives a new value, the characterId
signal increments or decrements the current ID to derive the new ID.
In the constructor, the effect's callback function emits the value of the characterId
LinkedSignal to the parent component. The Angular team said that the computation function of the LinkedSignal
should be pure; therefore, it is not a good place to emit the value.
constructor() {
effect(() => this.newSearchId.emit(this.characterId()));
}
When users click the buttons to increment or decrement the ID, the default value is displayed when the rxResource
function loads the data from the backend server. The user interface displays the resource status 2, the enum value of "Loading". After the data is retrieved, it is displayed, and the resource status changes to 4, the enum value of "Resolved". When users change the ID to 17, the HTTP request throws an exception. The resource status changes to 1, the enum value of "Error", and the default value is displayed.
The defaultValue
option allows developers to provide the Resource API with a default value other than undefined. It is helpful when the resource must return T, not T | undefined. Extra codes are unnecessary to destructure properties and assign default values when the object is possibly null.
Top comments (0)