@grandgular/rx
v1.3.1
Published
Enhanced RxJS operators for Angular applications
Maintainers
Readme
@grandgular/rx
@grandgular/rx extends RxJS with Angular-optimized operators, providing enhanced reactive programming capabilities for Angular applications.
Features
- 🧠 Stateful Operators – Advanced operators with configurable memory and state management
- ⚡ Angular-First – Designed to work seamlessly with Angular's change detection and lifecycle
- 🏎️ Performance-Optimized – Minimal overhead, maximum efficiency for high-frequency streams
- 🛠️ Type-Safe – Full TypeScript support with strict interfaces
- 🔄 Reactive Patterns – Simplify common RxJS challenges in Angular apps
- 📦 Lightweight – Tiny bundle size with zero unnecessary dependencies
Community & Support
🔹 Angular Community:
Join our Telegram channel for Angular tips and discussions:
🔹 Author:
Connect with me on LinkedIn:
Quick Navigation
tapFirst
log
distinctUntilChangedWithMemory
redirectOnHttpError
Installation
npm install @grandgular/rxOperators
tapFirst()
An enhanced version of RxJS's tap() that executes side-effects only for the first emitted value, with optional state isolation for individual subscribers.
Key Features:
- Selective side-effects - Execute callbacks only for the first value in the stream
- Dual mode operation - Choose between shared or isolated state per subscriber
- Memory efficient - No additional subscriptions or complex state management
- Type-safe - Full TypeScript support with proper generics
- RxJS native - Seamlessly integrates with existing RxJS pipelines
Usage Examples:
1. Basic Usage (Shared State)
import { of } from "rxjs";
import { tapFirst } from "./tap-first";
const shared$ = of(1, 2, 3);
shared$.pipe(tapFirst((value) => console.log("First value detected:", value))).subscribe(console.log);
// Output:
// First value detected: 1
// 1
// 2
// 32. Isolated State for Multiple Subscribers
const numbers$ = of(10, 20, 30);
// Isolated state
const isolated$ = numbers$.pipe(tapFirst((val) => console.log("Isolated first:", val), true));
isolated$.subscribe(); // Logs: "Isolated first: 10"
isolated$.subscribe(); // Logs: "Isolated first: 10" - fresh state for each subscriberlog()
A custom RxJS operator for logging stream values, errors, and completion events.
Useful for inspecting observable streams without modifying their data flow.
Usage Example:
import { of } from "rxjs";
import { log } from "@grandgular/rx";
// Logs values and completion as-is
of(1, 2, 3).pipe(log()).subscribe();
// output: 1
// output: 2
// output: 3
// output: Completed
// Logs with a prefix for easier identification
of(4, Error("not 5"), 6).pipe(log("MyStream")).subscribe();
// output: [MyStream] 4
// output: [MyStream] Error: not 5
// output: [MyStream] 6
// output: [MyStream] Completed
// Logs using a custom formatter function
of(7, 8, 9)
.pipe(log((v) => v * 1000))
.subscribe();
// output: 7000
// output: 8000
// output: 9000
// output: CompleteddistinctUntilChangedWithMemory()
An enhanced version of RxJS's distinctUntilChanged() that maintains a memory of previous values, allowing for more sophisticated duplicate detection.
Key Features:
- Configurable memory size - Remember last N values (default: Infinity)
- Custom comparison - Define your own duplicate detection logic
- Angular-optimized - Works seamlessly with Angular's change detection
- Type-safe - Full TypeScript support
Usage Examples:
1. Infinite Memory
const nums$ = of(1, 2, 3, 4, 1, 2, 5);
nums$
.pipe(
distinctUntilChangedWithMemory(), // Defaults to Infinity
)
.subscribe(console.log);
// Output: 1, 2, 3, 4, 52. Basic Deduplication (Remember last 3 values)
const source$ = of(1, 2, 3, 1, 2, 3, 4, 3, 5);
source$
.pipe(
distinctUntilChangedWithMemory(3), // Remember last 3 values
)
.subscribe(console.log);
// Output: 1, 2, 3, 4, 5
// Explanation: The second '1' and '2' are filtered because they're in the memory3. Case-Insensitive String Comparison
const words$ = of("Apple", "Banana", "apple", "orange", "Apple");
words$
.pipe(
distinctUntilChangedWithMemory({
memorySize: 4,
comparator: (prev, curr) => prev.some((word) => word.toLowerCase() === curr.toLowerCase()),
}),
)
.subscribe(console.log);
// Output: 'Apple', 'Banana', 'orange'redirectOnHttpError()
An RxJS operator that handles HTTP errors by redirecting to specified routes based on error status codes, while maintaining the original error for downstream processing.
Key Features:
- Status-based routing - Map specific HTTP status codes to different routes
- Flexible configuration - Accepts either a route map or a single fallback route
- History control - Option to skip location change in browser history
- Error logging - Optional callback for error logging
- Type-safe - Full TypeScript support with HttpStatusCode enum
Usage Examples:
1. Basic Usage with Fallback Route
import { redirectOnHttpError } from "./path/to/operator";
this.http
.get("/api")
.pipe(
redirectOnHttpError(this.router, {
redirectConfig: "/error", // Single fallback route
}),
)
.subscribe({
next: (data) => handleData(data),
error: (err) => console.error("Original error preserved:", err),
});2. Status-Specific Routing
this.http
.get("/api")
.pipe(
redirectOnHttpError(this.router, {
redirectConfig: {
[HttpStatusCode.NotFound]: "/not-found",
[HttpStatusCode.Forbidden]: "/access-denied",
default: "/server-error",
},
skipLocationChange: true, // Won't add to browser history
}),
)
.subscribe();3. With Error Logging
this.http
.post("/api", payload)
.pipe(
redirectOnHttpError(this.router, {
redirectConfig: {
[HttpStatusCode.BadRequest]: "/invalid-request",
default: "/error",
},
logError: (error) => this.errorService.log(error), // Custom logging
}),
)
.subscribe();Configuration Options:
| Option | Type | Description |
| -------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------ |
| redirectConfig | string OR ErrorRoutesTypes | Either a single fallback route or a map of status codes to routes (must include 'default') |
| skipLocationChange | boolean (optional) | If true, navigation won't push a new state into browser history (default: false) |
| logError | (error: unknown) => void (optional) | Callback for custom error logging |
License
MIT © Grandgular
