Skip to content

Latest commit

 

History

History

rx-computed

@jscutlery/rx-computed

Installation

yarn add @jscutlery/rx-computed

# or

npm install @jscutlery/rx-computed

rxComputed

Usage

import { rxComputed } from '@jscutlery/rx-computed';

@Component({
  ...
  template: `
    <mc-keywords-input (keywordsChange)="keywords.set($event)"/>
    <mc-sort-input (sortChange)="sort.set($event)"/>
    <mc-recipes-list [recipes]="recipes()"/>
  `
})
class MyCmp {
  keywords = signal<string | undefined>(undefined);
  sort = signal<'asc' | 'desc'>('desc');

  recipes = rxComputed(() => repo.getRecipes(keywords(), sort()), {initialValue: []});
}

Custom Injector

rxComputed utilizes effect() underneath so it is required to be invoked within an Injection Context. However, a custom Injector can be passed in and rxComputed will invoke within that Injector context.

class MyCmp {
  injector = inject(Injector);

  ngOnInit() {
    const value = rxComputed(() => of(16), { injector: this.injector });
  }
}

Motivation

Synchronously computed signals in Angular are relatively straightforward. However, when dealing with an asynchronous source of data like an Observable, there is no primitive to derive a signal from it.

There are two common ways of dealing with this:

  1. Using @angular/core/rxjs-interop which requires us to explicitly define the dependencies of the computed signal:
// Signals
const keywords = signal(...);
const sort = signal(...);

// Signals => RxJS
const recipes$ = combineLatest({
  keywords: toObservable(keywords),
  sort: toObservable(sort)
}).pipe(
  switchMap(({ keywords, sort }) => repo.getRecipes(keywords, sort))
);

// RxJS => Signals
const recipes = toSignal(recipes$, []);
  1. Using effect explicitly (which is not the recommended way of using it, cf. https://angular.io/guide/signals#when-not-to-use-effects):
const keywords = signal(...);
const sort = signal(...);

const recipes = signal(...);

effect((onCleanup) => {
  const sub = repo.getRecipes(keywords(), sort())
    .subscribe(_recipes => recipes.set(_recipes));

  onCleanup(() => sub.unsubscribe());
});

FAQ

What about errors?

Cf. Managing RxJS Traffic with Signals and Suspensify

Cf. @jscutlery/operators#suspensify