Skip to content

Grandgular/rx

Repository files navigation

Grandgular Logo

@grandgular/rx

License: MIT

@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:
Telegram

🔹 Author:
Connect with me on LinkedIn:
LinkedIn


Quick Navigation

tapFirst
log
distinctUntilChangedWithMemory
redirectOnHttpError


Installation

npm install @grandgular/rx

Operators

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
// 3
2. 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 subscriber

log()

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: Completed

distinctUntilChangedWithMemory()

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, 5
2. 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 memory
3. 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

About

Enhanced RxJS operators for Angular — tapFirst, distinctUntilChangedWithMemory, redirectOnHttpError, and more.

Topics

Resources

Stars

Watchers

Forks

Contributors