peerlibrary:computed-field

v0.4.0Published 8 years ago

This package has not had recent updates. Please investigate it's current state before committing to using it in your project.

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.

1var field = new ComputedField(function () {
2  var sum = 0;
3  collection.find({}, {fields: {value: 1}}).forEach(function (doc) {
4    sum += doc.value;
5  });
6  return sum;
7});
8
9console.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.

Optionally, you can pass custom equality function:

1new 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.

Installation

meteor add peerlibrary:computed-field

Extra field methods

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.

1field.stop()

Internally, computed field creates an autorun. 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. This is useful if you want to minimize propagation of reactivity. For example:

1Tracker.autorun(function () {
2  var result = new ComputedField(frequentlyInvalidatedButCheap);
3  expensiveComputation(result());
4});

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:

1var result = new ComputedField(frequentlyInvalidatedButCheap);
2Tracker.autorun(function () {
3  expensiveComputation(result());
4});

Still, 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.

For example, if you want to manually stop computed fields in Blaze Components, you could create them in the onCreated hook and clean-up them inside the onDestroyed hook. You can use the following pattern, in CoffeeScript:

onCreated: ->
  @field = new ComputedField =>
    @_computeField()

onDestroyed: ->
  for field, value of @ when value instanceof ComputedField
    value.stop()
}

But you do not have to! They will be stopped automatically anyway.

If you want to prevent automatically stopping the computed field, maybe because you are not accessing its value inside an autorun, you can pass a true value as a third argument to its constructor. But be sure to cleanup the field once you do not need it anymore.

1field.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.

minimizing reactivity propagation

field, just different implementation, but embox-value does not stop autoruns automatically by default, only when run lazily; computed field allows you to use instanceof ComputedField to determine if a field is a computed field; embox-value package has special provisioning for better integration with Blaze templates, but computed field does not need that because of the auto-stopping feature