Core Concepts

Functions

Call methods and functions directly in expressions — arguments are tracked, this is bound to the immediate owner, and return values participate in the reactive graph.

What it means

You can call any method or function in an expression string: multiply(a), b.multiply(a), cart.byId(id).qty. rs-x treats function call nodes like any other expression node — dependencies on arguments and on the function owner are tracked, and results update automatically when those dependencies change.

Practical value

No manual wiring is needed to include a function return value in a reactive graph. Pass a reactive field as an argument and the call re-evaluates when that field changes. Replace the owner object and the call re-evaluates against the new owner. The expression string is the complete specification.

Key points

Basic method call example

Call a method on the root model object. The expression re-evaluates automatically when the argument field changes.

import { emptyFunction, InjectionContainer, WaitForEvent } from '@rs-x/core';
import { rsx, RsXExpressionParserModule } from '@rs-x/expression-parser';

await InjectionContainer.load(RsXExpressionParserModule);

const model = {
  a: 10,
  multiplyWithTwo(x: number) {
    return 2 * x;
  },
};

// Expression calls the method and tracks the argument 'a'
const expr = rsx<number>('multiplyWithTwo(a)')(model);
await new WaitForEvent(expr, 'changed').wait(emptyFunction);

console.log(expr.value); // 20

// Changing 'a' causes the call to re-evaluate
await new WaitForEvent(expr, 'changed', { ignoreInitialValue: true })
  .wait(() => { model.a = 5; });

Nested method call with this binding example

Call a method on a nested object where this refers to that nested object. Re-evaluates when the owner object is replaced.

import { emptyFunction, InjectionContainer, WaitForEvent } from '@rs-x/core';
import { rsx, RsXExpressionParserModule } from '@rs-x/expression-parser';

await InjectionContainer.load(RsXExpressionParserModule);

const model = {
  a: 10,
  b: {
    x: 3,
    multiply(factor: number) {
      return this.x * factor; // 'this' is bound to 'b'
    },
  },
};

const expr = rsx<number>('b.multiply(a)')(model);
await new WaitForEvent(expr, 'changed').wait(emptyFunction);

console.log(expr.value); // 30

// Replacing the owner 'b' re-evaluates the call against the new object
await new WaitForEvent(expr, 'changed', { ignoreInitialValue: true })

Dynamic dispatch example

Select the method at runtime via a computed member name: b[b.methodName](a). Re-evaluates when the method name field changes.

import { emptyFunction, InjectionContainer, WaitForEvent } from '@rs-x/core';
import { rsx, RsXExpressionParserModule } from '@rs-x/expression-parser';

await InjectionContainer.load(RsXExpressionParserModule);

const model = {
  a: 10,
  b: {
    methodName: 'multiply' as 'multiply' | 'add',
    multiply(x: number) { return 3 * x; },
    add(x: number) { return 3 + x; },
  },
};

// The method name itself comes from another reactive field
const expr = rsx<number>('b[b.methodName](a)')(model);
await new WaitForEvent(expr, 'changed').wait(emptyFunction);

console.log(expr.value); // 30 (multiply: 3 * 10)

// Changing 'methodName' selects a different method
await new WaitForEvent(expr, 'changed', { ignoreInitialValue: true })

Chained call example

The return value of a function call becomes the owner of the next member access: cart.first().qty.

import { emptyFunction, InjectionContainer, WaitForEvent } from '@rs-x/core';
import { rsx, RsXExpressionParserModule } from '@rs-x/expression-parser';

await InjectionContainer.load(RsXExpressionParserModule);

const model = {
  cart: {
    items: [{ qty: 1 }, { qty: 2 }],
    first() { return this.items[0]; },
  },
};

// Return value of first() becomes the owner of .qty
const expr = rsx<number>('cart.first().qty')(model);
await new WaitForEvent(expr, 'changed').wait(emptyFunction);

console.log(expr.value); // 1

await new WaitForEvent(expr, 'changed', { ignoreInitialValue: true })
  .wait(() => { model.cart.items[0].qty = 4; });

console.log(expr.value); // 4