Update: Since Angular 19 the original answer is no longer recommended:
Use resources instead.
Use resource (for Promises) or rxResource (for Observables) instead.
Original answer for historical purposes:
Update: added generalization of the concept
Update2: Added cancellation to observable variant
This is a very important question. As @kemsky already mentioned you can use an effect to create such signals.
The nicest solution is to encapsulate the creation and management of such signal in a function, like this:
function myBlahSignal(otherSignal: Signal<Something>) {
const someService = inject(SomeService);
const resultSignal = signal<SomeResult>(null);
effect(
() => {
if (!otherSignal()) return;
someService
.someMethod(otherSignal())
.subscribe((result) => resultSignal.set(result));
},
{ allowSignalWrites: true }
);
return resultSignal.asReadonly();
}
You can then use such function directly in any component that has the required source signals:
export class MyComponent {
otherSignal = someOtherSignal();
myBlah = myBlahSignal(this.otherSignal1);
}
If we look at this pattern fundamentally, we see that we basically implemented a async computed signal, based on observables.
We can abstract this concept in a reusable way:
function myBlahSignal(otherSignal: Signal<Something>) {
const someService = inject(SomeService);
return asyncObservableComputed(() => {
if (!otherSignal()) return null;
return someService.someMethod$(otherSignal());
});
}
function asyncObservableComputed<T>(
computation: () => Observable<T> | undefined | null,
): Signal<T> {
const resultSignal = signal<T>(null);
effect(
(onCleanup) => {
const result$ = computation();
if (!result$) return;
const subscription = result$.subscribe((result) => resultSignal.set(result));
onCleanup(() => subscription.unsubscribe());
},
{ allowSignalWrites: true },
);
return resultSignal.asReadonly();
}
And a promise based variant:
function asyncPromiseComputed<T>(
computation: () => Promise<T> | undefined | null,
): Signal<T> {
const resultSignal = signal<T>(null);
effect(
async () => {
const result = await computation();
resultSignal.set(result);
},
{ allowSignalWrites: true },
);
return resultSignal.asReadonly();
}
We can go one step further and make a fully generic asyncComputed that supports Observable, Promise and plain results at the same time:
export function asyncComputed<T>(
computation: () => Observable<T> | Promise<T> | T | undefined | null,
): Signal<T> {
const resultSignal = signal<T>(null);
effect(
async () => {
const result = computation();
const unwrappedResult = await (isObservable(result)
? firstValueFrom(result as Observable<T>, { defaultValue: null })
: result);
resultSignal.set(unwrappedResult);
},
{ allowSignalWrites: true },
);
return resultSignal.asReadonly();
}