foam3

Expressions vs this.dynamic()

Expressions are reactive property computations defined at the model level that automatically recalculate when their dependencies change. this.dynamic() is a UI method that creates reactive DOM content that updates when referenced properties change.

Property Expressions

Expressions are defined in FOAM class properties and create computed values (see the first citation below).

They stop being reactive once manually set to a value, requiring reset to undefined to re-enable reactivity.

this.dynamic()

this.dynamic() creates reactive UI elements in FOAM’s U2 framework. It’s implemented using foam.lang.DynamicFunction which extends foam.lang.ExpressionSlot.

The implementation shows that DynamicFunction eagerly evaluates and always updates even when nobody is accessing the value, making it suitable for UI updates.

The function’s return value is ignored. Content must be added through this, which is the FunctionNode that add() wraps the function in, so a dynamic() only renders anything once it is passed to add().

Key Differences

  1. Scope: Expressions are model-level computed properties; this.dynamic() is for UI rendering
  2. Usage: Expressions define reactive data; this.dynamic() creates reactive DOM content
  3. Evaluation: Expressions are lazy (pull-based); this.dynamic() is eager (push-based)
  4. Context: Expressions work with the properties of the object that owns them; this.dynamic() watches properties of the object it is called on. Passing a bare function to this.add(function(...) {...}) instead watches properties of the context’s data (falling back to the element when there is no data)

Notes

Both use FOAM’s reactive slot system under the hood, but serve different purposes - expressions for computed model data and this.dynamic() for reactive UI rendering. The DynamicFunction implementation ensures UI updates happen immediately when dependencies change, while expressions only compute when accessed.

See Also

Wiki pages you might want to explore:

Citations

Expression property:

{ class: 'String', name: 'fullName', expression: function(firstName, lastName) {
    return firstName + ' ' + lastName;
}}

Overriding and resetting an expression:

{
  class: 'String',
  name: 'computedValue',
  expression: function(inputA, inputB) {
    return inputA + inputB;
  }
}

// Important: Once an expression is set to a value, it stops recalculating
obj.computedValue = 'manual value'; // Expression stops being reactive

// To re-enable reactive behavior, reset to undefined
obj.computedValue = undefined; // Expression becomes reactive again

Dynamic Views:

// Auto-binds to data properties
this.add(function(firstName, lastName) {
  this.start('div').add(`Name: ${firstName} ${lastName}`).end();
});

// For properties of the view itself, use this.dynamic() directly
this.start('div')
  .add(this.dynamic(function(myProp, otherProp) {
    this.add(`Values: ${myProp} ${otherProp}`);
  }))
.end();

Note: this.add(function(){}) auto-binds to data properties. For the object itself, use this.dynamic() directly. In both forms the return value is ignored; add content through this.

File: src/foam/lang/Slot.js

foam.CLASS({
  package: 'foam.lang',
  name: 'DynamicFunction',
  extends: 'foam.lang.ExpressionSlot',

File: src/foam/lang/Slot.js (DynamicFunction.subToArgs_)

    function subToArgs_(args) {
      // Overrides implementation in ExpressionSlot
      // Difference is an Expression only computes updated value if someone
      // has done a get() on the Slot, so is pull-based, but a DynamicFunction
      // always updates, even if nobody is get()-ing the value. This makes
      // it suitable as a kind of compound listener.
      const subs = args.map(a => a && a.sub(this.invalidate));

      if ( ! this.cleanup_ ) {
        this.cleanup_ = {
          detach: function() {
            for ( var i = 0 ; i < subs.length ; i++ ) {
              if ( subs[i] ) subs[i].detach();
            }
          }
        };
      }
    }