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)