Skip to content

Files

Latest commit

author
πŸ€– JSCutlery Bot
Feb 28, 2025
91932e2 Β· Feb 28, 2025

History

History

rx-computed

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
Nov 29, 2024
May 24, 2023
Feb 28, 2025
Jul 17, 2023
May 24, 2023
May 24, 2023
Feb 28, 2025
Apr 2, 2024
May 24, 2023
May 24, 2023
May 24, 2023
May 24, 2023

@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