Core Concepts

Angular integration

Bind rs-x expressions to Angular templates with the RsxPipe — reactive updates propagate automatically using Angular's change detection, with no manual subscriptions.

What it means

@rs-x/angular ships two exports: the RsxPipe impure pipe and the providexRsx() provider function. The pipe wraps an rs-x expression (string or pre-built IExpression) and calls ChangeDetectorRef.markForCheck() whenever the expression value changes. providexRsx() wires the rs-x DI container into Angular's dependency injection system during APP_INITIALIZER.

Practical value

You write plain model mutations anywhere in your app — a service, a WebSocket handler, or a button click — and every template that reads that data updates automatically. No BehaviorSubjects, no ngrx actions, no manually managed subscriptions. The pipe is impure by design so Angular calls transform() on each change-detection cycle, but it only allocates a new expression when the input actually changes.

Key points

Setup — standalone app example

Register rs-x providers in your ApplicationConfig.

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { providexRsx } from '@rs-x/angular';

export const appConfig: ApplicationConfig = {
  providers: [
    // ... other providers
    ...providexRsx(),
  ],
};

Setup — NgModule app example

Import RsxPipe and spread providexRsx() into your providers array.

// app.module.ts  (NgModule-based apps)
import { NgModule } from '@angular/core';
import { RsxPipe, providexRsx } from '@rs-x/angular';

@NgModule({
  declarations: [AppComponent],
  imports: [RsxPipe],
  providers: [...providexRsx()],
  bootstrap: [AppComponent],
})
export class AppModule {}

RsxPipe — string expression example

Pass a model as the pipe argument. The pipe creates, owns, and disposes the expression. The template updates whenever any model field changes.

// component.ts
import { Component } from '@angular/core';
import { RsxPipe } from '@rs-x/angular';

@Component({
  selector: 'app-greeting',
  standalone: true,
  imports: [RsxPipe],
  template: `
    <p>{{ 'firstName + " " + lastName' | rsx: model }}</p>
  `,
})
export class GreetingComponent {
  model = {
    firstName: 'Jane',
    lastName: 'Doe',
  };
}

// Mutate the model — the template updates automatically
// this.model.firstName = 'Alice';

RsxPipe — pre-built IExpression example

Build the expression once (e.g. in a class field or service) and pass it directly. The pipe subscribes but does not dispose on destroy.

// component.ts
import { Component } from '@angular/core';
import { rsx } from '@rs-x/expression-parser';
import { RsxPipe } from '@rs-x/angular';

const model = { price: 100, quantity: 3 };

@Component({
  selector: 'app-order-total',
  standalone: true,
  imports: [RsxPipe],
  template: `
    <span>Total: {{ totalExpr | rsx }}</span>
  `,
})
export class OrderTotalComponent {
  // Build the expression once — share it across any number of templates
  readonly totalExpr = rsx<number>('price * quantity')(model);
}

// Update the model anywhere — all bound templates re-render
model.quantity = 5;

RsxPipe — async values example

Async model fields (Promise, Observable) work transparently — no async pipe needed. The template updates whenever the Observable emits.

// component.ts
import { Component } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { RsxPipe } from '@rs-x/angular';

@Component({
  selector: 'app-live-price',
  standalone: true,
  imports: [RsxPipe],
  template: `
    <p>Price (inc. tax): {{ 'base * (1 + taxRate)' | rsx: model }}</p>
  `,
})
export class LivePriceComponent {
  model = {
    base: new BehaviorSubject(100),
    taxRate: 0.21,
  };

  updatePrice(newBase: number) {
    this.model.base.next(newBase); // template updates automatically
  }

RsxPipe — null safety example

Passing null or undefined is safe and renders as an empty string.

// Passing null or undefined is safe — the pipe renders nothing
// and does not create or hold an expression.

@Component({
  template: `
    <span>{{ maybeExpr | rsx }}</span>
  `,
})
export class SafeComponent {
  maybeExpr: string | null = null; // renders as empty string
}

Manual injection — factory and transaction manager example

Inject IExpressionFactoryToken and IExpressionChangeTransactionManagerToken directly to build expressions in services or use batched updates.

import { inject, Component } from '@angular/core';
import {
  IExpressionFactoryToken,
  IExpressionChangeTransactionManagerToken,
} from '@rs-x/angular';

@Component({ selector: 'app-manual', standalone: true, template: '' })
export class ManualComponent {
  private readonly factory = inject(IExpressionFactoryToken);
  private readonly txManager = inject(IExpressionChangeTransactionManagerToken);

  ngOnInit() {
    const model = { a: 1, b: 2 };
    const expr = this.factory.create<number>(model, 'a + b');

    // Batch multiple model mutations into a single change notification
    this.txManager.begin();
    model.a = 10;
    model.b = 20;
    this.txManager.commit(); // expr fires changed once, not twice
  }
}

Installation example

Install all required packages.

npm install @rs-x/core @rs-x/state-manager @rs-x/expression-parser @rs-x/angular