Reactively computed field for Meteor
Reactively computed field for Meteor provides an easy way to define a reactive variable
which gets value from another reactive function. This allows you to minimize propagation of a reactive change
because if the reactive function returns the value equal to the previous value, reactively computed field
will not invalidate reactive contexts where it is used.
var field = new ComputedField(function () {
var sum = 0;
collection.find({}, {fields: {value: 1}}).forEach(function (doc) {
sum += doc.value;
});
return sum;
});
console.log(field());
You get current value by calling the field as a function. A reactive dependency is then registered.
This is useful when you are assigning them to objects because they behave like object methods. You can also access
them in Blaze Components template by simply doing{{field}}
when they are assigned to the component.
When computed field is created inside a Blaze template (in onCreated
or onRendered
) it will automatically detect this
and allow use of Template.instance()
and Template.currentData()
inside a function.
Optionally, you can pass a custom equality function:
new ComputedField(reactiveFunction, function (a, b) {return a === b});
Adding this package to your Meteor application adds the ComputedField
constructor into
the global scope.
Both client and server side.
meteor add peerlibrary:computed-field
ComputedField
constructor accepts the following arguments:
reactiveFunction
– a reactive function which should return a computed field’s valueequalityFunction
– a function to compare the return value to see if it has changed and if the computed fieldReactiveVar._isEqual
, which means that only primitive values are compareddontStop
– pass true
to prevent computed field from automatically stopping internal autorun once the field is not usedYou can pass dontStop
as a second argument as well, skipping the equalityFunction
.
The computed field is a function, but it has also two extra methods which you probably do not really need, because
computed field should do the right thing automatically.
field.stop()
Internally, computed field creates an autorun which should not run once it is not needed anymore.
If you create a computed field inside another autorun, then you do not have to worry and computed field’s autorun
will be stopped and cleaned automatically every time outside computation gets invalidated.
If you create a computed field outside an autorun, autorun will be automatically stopped when there will
be no reactive dependencies anymore on the computed field. So the previous example could be written as well as:
var result = new ComputedField(frequentlyInvalidatedButCheap);
Tracker.autorun(function () {
expensiveComputation(result());
});
Moreover, if you create a computed field inside a Blaze template (in onCreated
or onRendered
) it will automatically
detect this and it will use template’s autorun which means that autorun will be stopped automatically when
template instance gets destroyed.
Despite all this, the stop()
method is provided for you if you want to explicitly stop and clean the field. Remember,
getting a value again afterwards will start internal autorun again.
field.flush()
Sometimes you do not want to wait for global flush to happen to recompute the value. You can call flush()
on the
field to force immediate recomputation. But the same happens when you access the field value. If the value is
invalidated, it will be automatically first recomputed and then returned. flush()
is in this case called for you
before returning you the field value. In both cases, calling flush()
directly or accessing the field value,
recomputation happens only if it is needed.
Computed field is useful if you want to minimize propagation of reactivity. For example:
Tracker.autorun(function () {
var result = new ComputedField(frequentlyInvalidatedButCheap);
expensiveComputation(result());
});
In this example frequentlyInvalidatedButCheap
is a function which depends on reactive variables which frequently
change, but computing with them is cheap, and resulting value rarely changes. On the other hand,expensiveComputation
is a function which is expensive and should be called only when result
value changes.
Example frequentlyInvalidatedButCheap
could for example be determining if current mouse position is inside a
rectangle on canvas or outside. Every time mouse is moved, it should be recomputed, but result changes only when
mouse moves over the rectangle’s border. On the other hand, expensiveComputation
could be an expensive drawing
operation which draws a rectangle differently if the mouse position is inside or outside of the rectangle. You do
not want to redraw on every mouse position change.
Even if you create a computed field outside an autorun, autorun will be automatically stopped when there will
be no reactive dependencies anymore on the computed field. So the previous example could be written as well as:
var result = new ComputedField(frequentlyInvalidatedButCheap);
Tracker.autorun(function () {
expensiveComputation(result());
});
You can use computed field to attach a field to a Blaze Component:
class ExampleComponent extends BlazeComponent {
onCreated() {
super.onCreated();
this.sum = new ComputedField(() => {
let sum = 0;
collection.find({}, {fields: {value: 1}}).forEach((doc) => {
sum += doc.value;
});
return sum;
});
}
}
ExampleComponent.register('ExampleComponent');
And now you can access this field inside a component without knowing that it will change DOM only when the sum
itself changes, and not at every change of any document:
<template name="ExampleComponent">
<p>{{sum}}</p>
</template>
instanceof ComputedField
to determine if a field is a computed field;