The computedAsync function is a versatile utility enabling value computation from Promises, Observables, or regular values. It provides flexibility in computational behavior through selectable flattening strategies (switch, merge, concat, exhaust) and initial value assignment. To utilize computedAsync, provide a function returning a Promise, Observable, or regular value. It then returns a Signal emitting the computed value.
When returning an Observable, it is automatically subscribed to and will be unsubscribed when the component is destroyed or when the computation is re-triggered. In the following example, if the userId changes, the previous computation will be cancelled (including any ongoing API calls), and a new computation will be initiated.
This doesn't offer any advantage over employing a standard computed signal. However, it does allow the return of regular values (those not consisting of Promises or Observables) from the callback function. The callback function executes in the microtask queue, hence it won't promptly emit the value (by default, it returns undefined). To emit the value immediately, you can utilize the requireSync option within the second argument options object.
If we possess an Observable that synchronously emits the value, we can utilize the requireSync option to ensure immediate emission of the value. This feature is also beneficial for ensuring the signal type excludes undefined by default. Without requireSync, the signal type would be Signal<User | undefined>. However, with requireSync enabled, the signal type will be Signal<User>.
In the example below, we have a Signal that represents the state of an API call. We use computedAsync and createAsyncState operator to compute the state of the API call based on the userId.
Example 7: Behaviors (switch, merge, concat, exhaust)
By default, computedAsync employs the switch behavior, which entails that if the computation is re-triggered before the prior one concludes, the former will be canceled. Should you wish to alter this behavior, you can provide the behavior option within the second argument options object.
When desiring to cancel the prior computation, the switch behavior is utilized, which is the default. If a new computation is triggered before the previous one concludes, the former will be terminated.
merge
To retain the preceding computation, the merge behavior is employed. If a new computation is initiated before the previous one is finished, the former is preserved while the latter is started.
concat
If preservation of the prior computation is desired along with waiting for its completion before initiating the new one, the concat behavior is chosen.
exhaust
In instances where disregarding the new computation while the prior one remains ongoing is necessary, the exhaust behavior is selected.
Example 8: Use with Previous Computed Value
Should there be a necessity to utilize the previously computed value in the subsequent computation, it can be accessed within the callback function as the first argument.
import {computedAsync} from'ngx-lift';
import {Component, inject, input, Signal} from'@angular/core';
import {HttpClient} from'@angular/common/http';
exportclassUserDetailComponent {
private http = inject(HttpClient);
userId = input.required<number>();
user: Signal<User | undefined> = computedAsync(
(previousValue) => {
// Use previousValue here if you needreturnthis.http.get<User>(`https://localhost/api/users/${this.userId()}`);
},
);
}
import {AsyncState, computedAsync, createAsyncState, createTrigger} from'ngx-lift';
import {Component, inject, Signal} from'@angular/core';
import {UserService} from'./user.service';
exportclassUserListComponent {
private userService = inject(UserService);
private fetchTrigger = createTrigger();
// user list will be fetched only when button clicksdeferredUsersState: Signal<AsyncState<PaginationResponse<User>> | undefined> = computedAsync(() => {
returnthis.fetchTrigger.value() ? this.userService.getUsers({results: 9}).pipe(createAsyncState()) : undefined;
});
load() {
this.fetchTrigger.next();
}
}
Example 11: Error Handling with onError
Use the onError callback to handle errors gracefully and provide fallback values. When onError returns a value, the signal will emit that value instead of throwing an error:
The onError callback receives the error and can return a fallback value. If it returns undefined, the error will be stored in the signal (when used with createAsyncState).
Example 12: Error Handling with throwOnError
Use throwOnError: true to propagate errors up instead of silently handling them. This is useful when you want errors to bubble up to a global error handler:
import {computedAsync} from'ngx-lift';
import {Component, inject, input, Signal} from'@angular/core';
import {HttpClient} from'@angular/common/http';
exportclassUserDetailComponent {
private http = inject(HttpClient);
userId = input.required<number>();
// Throw errors instead of silently handling themuser: Signal<User | undefined> = computedAsync(
() =>this.http.get<User>(`https://localhost/api/users/${this.userId()}`),
{
throwOnError: true, // Errors will propagate uponError: (error) => {
console.error('API Error:', error);
// Can still log/handle but error will still throwreturnundefined; // Won't be used since error throws
}
}
);
}
When throwOnError: true, errors will be thrown even if onError is provided. The onError callback can still be used for logging or side effects before the error throws.
API Reference
computedAsync
Computes values from Promises, Observables, or regular values, returning a Signal that emits the computed value.
computation: (previousValue?: T) => Promise<T> | Observable<T> | T
A function that returns a Promise, Observable, or regular value. The function receives the previous computed value as an optional first argument.
options?: ComputedAsyncOptions<T>
Configuration options:
initialValue?: T - The initial value for the computed signal before the computation completes.
requireSync?: boolean - If true, requires the computation to emit synchronously. This ensures the signal type excludes undefined.
behavior?: 'switch' | 'merge' | 'concat' | 'exhaust' - The flattening strategy for handling multiple computations. Defaults to 'switch'.
onError?: (error: unknown) => T | undefined - New! Error handler that receives errors from async operations. Can return a fallback value to recover from errors.
throwOnError?: boolean - New! If true, errors will be thrown and propagate up. If false (default), errors can be handled by onError.
Returns
A Signal that emits the computed value. The signal type depends on the requireSync option: Signal<T | undefined> if requireSync is false or omitted, Signal<T> if requireSync is true.
Behaviors
switch (default): Cancels the previous computation when a new one is triggered.
merge: Preserves the previous computation while starting a new one when triggered.
concat: Waits for the previous computation to complete before starting a new one.
exhaust: Ignores new computations while a previous one is still ongoing.