signal-operators
v0.1.3
Published
RxJS-style pipe() operator chaining for Angular Signals
Downloads
60
Maintainers
Readme
signal-operators
RxJS-style pipe() operator chaining for Angular Signals.
signal-operators brings the declarative power and composability of RxJS operators to Angular's native Signals, allowing you to transform, filter, and compute reactive state with an elegant, chainable API.
❓ What does it do?
Angular Signals provide a great primitive for reactive state, but computing derived state often requires nesting computed() calls or writing imperative logic inside them.
signal-operators introduces a signalPipe utility that wraps any Angular Signal and provides a .pipe() method. This allows you to compose derived signals using standard functional operators (like map, filter, scan, debounce, etc.) exactly like you would with RxJS Observables, but entirely synchronously and within the Angular reactivity graph.
🤔 Why should you use it?
- Declarative Derivations: Express complex derived state declaratively instead of imperatively within
computed(). - Familiar RxJS Syntax: If you know RxJS, you already know how to use
signal-operators. It uses the exact samepipe(op1, op2)mental model. - Fully Native: Under the hood, operators use Angular's
computed()andeffect(). It stays 100% within the Angular Signals ecosystem. - Type-Safe: Written in strict TypeScript with full type inference through the
.pipe()chain. - Lightweight: Minimal overhead. It's just a thin wrapper around native Signals.
📦 Installation
npm install signal-operatorsEnsure you have Angular ^17.0.0 or higher installed, as it relies on the native Signals API.
🛠️ Usage & Examples
🏁 Basic Setup
Wrap any signal (or computed) with signalPipe() to enable chaining:
import { signal } from '@angular/core';
import { signalPipe, map, filter } from 'signal-operators';
const count = signal(0);
const processed = signalPipe(count).pipe(
filter(x => x >= 0),
map(x => x * 2)
);
console.log(processed()); // 0🧩 Using it in Angular Templates
To use the piped signal seamlessly in your Angular templates, you can extract the underlying Angular Signal using ``:
import { Component, signal } from '@angular/core';
import { signalPipe, map } from 'signal-operators';
@Component({
selector: 'app-counter',
template: `
<p>Raw Count: {{ count() }}</p>
<p>Doubled: {{ doubledCount() }}</p>
<button (click)="increment()">Increment</button>
`
})
export class CounterComponent {
count = signal(0);
// Expose as a standard Angular Signal
doubledCount = signalPipe(this.count).pipe(
map(x => x * 2)
);
increment() {
this.count.update(c => c + 1);
}
}🚀 Advanced Operations
You can use a variety of operators like scan for accumulating state or debounce for delaying updates:
import { signal } from '@angular/core';
import { signalPipe, scan, debounce, tap } from 'signal-operators';
const actions = signal<string>('init');
const state = signalPipe(actions).pipe(
tap(action => console.log('Action dispatched:', action)),
scan((acc, curr) => [...acc, curr], [] as string[]),
debounce(300) // waits 300ms before emitting the new state
);🧰 Available Operators
The library currently exports the following operators out-of-the-box:
mapfiltertapscandistinctUntilChangedskiptakedebounceswitchMapmergeMap
📄 License
MIT
