Signal Pipeline Operators
Create composable operators for signals inspired by RxJS, enabling elegant data transformation pipelines.
Complete Solution
import { signal, computed, effect, Signal } from '@angular/core';
export type SignalOperator<T, R> = (source: Signal<T>) => Signal<R>;
// Map operator
export function signalMap<T, R>(transform: (value: T) => R): SignalOperator<T, R> {
return (source) => computed(() => transform(source()));
}
// Filter operator
export function signalFilter<T>(predicate: (value: T) => boolean): SignalOperator<T, T | undefined> {
return (source) => computed(() => {
const value = source();
return predicate(value) ? value : undefined;
});
}
// Debounce operator
export function debounce<T>(delayMs: number): SignalOperator<T, T> {
return (source) => {
const debounced = signal(source());
let timeoutId: any;
effect(() => {
const value = source();
clearTimeout(timeoutId);
timeoutId = setTimeout(() => debounced.set(value), delayMs);
});
return debounced.asReadonly();
};
}
// Throttle operator
export function throttle<T>(delayMs: number): SignalOperator<T, T> {
return (source) => {
const throttled = signal(source());
let lastTime = 0;
effect(() => {
const value = source();
const now = Date.now();
if (now - lastTime >= delayMs) {
throttled.set(value);
lastTime = now;
}
});
return throttled.asReadonly();
};
}
// Distinct operator
export function distinct<T>(compareFn?: (a: T, b: T) => boolean): SignalOperator<T, T> {
return (source) => {
const distinct = signal(source());
const compare = compareFn || ((a, b) => a === b);
effect(() => {
const value = source();
if (!compare(value, distinct())) {
distinct.set(value);
}
});
return distinct.asReadonly();
};
}
// Take operator
export function take<T>(count: number): SignalOperator<T, T | undefined> {
return (source) => {
const taken = signal<T | undefined>(source());
let emitCount = 0;
effect(() => {
if (emitCount < count) {
taken.set(source());
emitCount++;
}
});
return taken.asReadonly();
};
}
// Pipe function for composition
export function pipe<T>(source: Signal<any>, ...operators: SignalOperator<any, any>[]): Signal<T> {
return operators.reduce((acc, operator) => operator(acc), source);
}
Usage Examples
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-operator-demo',
standalone: true,
imports: [FormsModule],
template: `
<input [(ngModel)]="searchInput" placeholder="Search..." />
<p>Debounced: {{ debouncedSearch() }}</p>
<p>Filtered: {{ filteredSearch() }}</p>
`
})
export class OperatorDemoComponent {
searchInput = signal('');
// Compose operators
debouncedSearch = pipe(
this.searchInput,
debounce(500),
signalMap((value: string) => value.trim()),
signalFilter((value: string) => value.length > 2),
distinct()
);
filteredSearch = pipe(
this.searchInput,
signalMap((value: string) => value.toUpperCase())
);
}
Key Takeaways
- Composable operators for clean data transformations
- Type-safe pipelines with full TypeScript support
- Reusable logic across components
- Functional programming style
Next Steps
- Learn about Custom Equality Functions
- Explore RxJS Interop