Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Async Await in Angular computed signal

Tags:

angular

Is it possible to use async await inside of an Angular computed signal?

I have tried to play with that but so far i couldn't make it work correctrly (fetching the value, manipulate the value and returning a raw value to the signal computed result and not a promise)

like image 260
Lagistos Avatar asked Dec 17 '25 17:12

Lagistos


1 Answers

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();
}
like image 86
Mark Lagendijk Avatar answered Dec 20 '25 07:12

Mark Lagendijk



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!