yarn add @jscutlery/rx-computed
# or
npm install @jscutlery/rx-computed
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: []});
}
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 });
}
}
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:
- 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$, []);
- 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());
});