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.
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() 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().
this.dynamic() is for UI renderingthis.dynamic() creates reactive DOM contentthis.dynamic() is eager (push-based)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)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.
dynamic() alongside the other three reactive UI patterns (slot.dot(), slot.map(), and slot binding via prop$:), with a decision framework for choosing between themWiki pages you might want to explore:
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();
}
}
};
}
}