can-compute
Create an observable value.
compute(getterSetter[, context])
Create a compute that derives its value from other observables. Uses can-observation to call the getterSetter
and track observables.
const age = compute( 32 );
const nameAndAge = compute( function() {
return "Matthew - " + age();
} );
console.log( nameAndAge() ); // -> Matthew - 32
age( 33 );
console.log( nameAndAge() ); // -> Matthew - 33
Parameters
- getterSetter
{function(newVal, oldVal)}
:A function that gets, and optionally sets, the value of the compute. When called with no arguments, getterSetter should return the current value of the compute. When called with a single argument, getterSetter should arrange things so that the next read of the compute produces that value. This compute will automatically update its value when any observables values are read.
- context
{Object}
:The
this
to use when calling thegetterSetter
function.
compute(initialValue [, settings])
Creates a compute from a value and optionally specifies how to read, update, and listen to changes in dependent values. This form of compute can be used to create a compute that derives its value from any source.
Parameters
- initialValue
{*}
:The initial value of the compute. If
settings
is not provided, the compute simply updates its value to whatever the first argument to the compute is.var age = compute(30); age() //-> 30 age(31) //-> fires a "change" event
- settings
{computeSettings}
:Configures all behaviors of the compute. The following cross binds an input element to a compute:
const input = document.getElementById( "age" ); const value = compute( "", { get: function() { return input.value; }, set: function( newVal ) { input.value = newVal; }, on: function( updated ) { input.addEventListener( "change", updated, false ); }, off: function( updated ) { input.removeEventListener( "change", updated, false ); } } );
compute(initialValue, setter(newVal,oldVal))
Create a compute that has a setter that can adjust incoming new values.
const age = compute( 6, function( newVal, oldVal ) {
if ( !isNaN( +newVal ) ) {
return +newVal;
} else {
return oldVal;
}
} );
Parameters
- initialValue
{*}
:The initial value of the compute.
- setter
{function(newVal, oldVal)}
:A function that is called when a compute is called with an argument. The function is passed the first argumented passed to compute and the current value. If
set
returns a value, it is used to compare to the current value of the compute. Otherwise,get
is called to get the current value of the compute and that value is used to determine if the compute has changed values.
compute(object, propertyName [, eventName])
Create a compute from an object's property value. This short-cut signature lets you create a compute on objects that have events that can be listened to with on.
const input = document.getElementById( "age" );
const age = compute( input, "value", "change" );
const me = new DefineMap( { name: "Justin" } );
const name = compute( me, "name" );
Parameters
- object
{Object}
:An object that has an
addEventListener
method and events dispatched on it. - propertyName
{String}
:The property value to read on
object
. The property will be read viaobject.attr(propertyName)
orobject[propertyName]
. - eventName
{String}
:Specifies the event name to listen to on
object
forpropertyName
updates.
Use
can-compute
exports a function that lets you make an observable value. The following
makes an observable age
compute whose value changes from 33
to 34
:
import compute from "can-compute";
const age = compute( 33 );
age(); // 33
age.on( "change", function( ev, newVal, oldVal ) {
newVal; //-> 34
oldVal; //-> 33
} );
age( 34 );
age(); // 33
Computes are similar to observable maps like can-define/map/map, but they represent a single value rather than a collection of values.
Computes can derive their value from other computes, maps and lists.
When the derived values change, the compute's value will be automatically updated. This
is can-compute
's best feature. For example, the following combines the age
compute in the previous example, and a name
compute into an info
compute:
const age = compute( 33 ),
name = compute( "Justin" ),
info = compute( function() {
return name() + " is " + age() + ".";
} );
info(); //-> "Justin is 33."
If we listen to change on info
, if either age
or name
changes, info
will be updated automatically:
info.on( "change", function( ev, newVal, oldVal ) {
newVal; //-> "Justin is 34."
} );
age( 34 );
Computes are similar to event streams like Bacon.js
or RXJS
. However, computes
are easier to compose values because:
- you can just read other observables and computes and return a value.
- you don't have to manage subscribing and merging streams yourself.
Also, computes can also have [can-event/batch/batch batched updates] to prevent unnecessary
updates. For example, if both age
and name
were changed at the same time, we
could prevent info
from updating twice with:
import canBatch from "can-event/batch/batch";
canBatch.start();
age( 35 );
name( "Justin Meyer" );
canBatch.stop();
There are a wide variety of ways to create computes. Read on to understand the basics.
Observing a value
The simplest way to use a compute is to have it store a single value, and to set it when that value needs to change:
var tally = compute(12);
tally(); // 12
tally.on("change",function(ev, newVal, oldVal){
console.log(newVal,oldVal)
})
tally(13);
tally(); // 13
Any value can be observed. The following creates a compute that holds an object and then changes it to an array.
const data = compute( { name: "Justin" } );
data( [ { description: "Learn Computes" } ] );
Derived computes
If you use a compute that derives its value from properties of an observable map or other can-computes, the compute will listen for changes in those properties and automatically recalculate itself, emitting a change event if its value changes.
The following example shows creating a fullName
compute
that derives its value from two properties on the person
observe:
const person = new Person( {
firstName: "Alice",
lastName: "Liddell"
} );
const fullName = compute( function() {
return person.firstName + " " + person.lastName;
} );
fullName.on( "change", function( ev, newVal, oldVal ) {
console.log( "This person's full name is now " + newVal + "." );
} );
person.firstName = "Allison"; // The log reads:
//-> "This person's full name is now Allison Liddell."
Because Person is an observable can-define/map/map can-compute knows to listen for changes because the map's firstName and lastName properties are read.
Translator computes - computes that update their derived values
Sometimes you need a compute to be able to translate one value to another. For example, consider a widget that displays and allows you to update the progress in percent of a task. It accepts a compute with values between 0 and 100. But, our task observe has progress values between 0 and 1 like:
const task = new DefineMap( {
progress: 0.75
} );
Use compute( getterSetter )
to create a compute that updates itself
when task's progress
changes, but can also update progress when
the compute function is called with a value. For example:
const progressPercent = compute( function( percent ) {
if ( arguments.length ) {
task.progress = percent / 100;
} else {
return task.progress * 100;
}
} );
progressPercent(); // -> 75
progressPercent( 100 );
task.progress; // -> 1
The following is a similar example that shows converting feet into meters and back:
const wall = new DefineMap( {
material: "brick",
length: 10 // in feet
} );
const wallLengthInMeters = compute( function( lengthInM ) {
if ( arguments.length ) {
wall.length = lengthInM * 3.28084;
} else {
return wall.length / 3.28084;
}
} );
wallLengthInMeters(); // 3.048
// When you set the compute...
wallLengthInMeters( 5 );
wallLengthInMeters(); // 5
// ...the original map changes too.
wall.length; // 16.4042
Events
When a compute's value is changed, it emits a change event. You can listen for this change
event by using on
to bind an event handler to the compute:
const tally = compute( 0 );
tally.on( "change", function( ev, newVal, oldVal ) {
console.log( "The tally is now at " + newVal + "." );
} );
tally( tally() + 5 ); // The log reads:
// 'The tally is now at 5.'
Caching values
A compute that has an event listener will cache its value and only update when one of its source observables change.
For example:
const foo = {
first: "Wonder"
};
const last = compute( "Woman" );
const hero = {
fullName: compute( function() {
return foo.first + " " + last();
} )
};
hero.fullName.on( "change", function() {} ); // bind to compute
console.log( hero.fullName() ); // console.logs "Wonder Woman"
foo.first = "Super";
console.log( hero.fullName() ); // console.logs "Wonder Woman" because the source observable (last) hasn't changed
last( "Man" );
console.log( hero.fullName() ); // console.logs "Super Man" because fullName updates its value now after hearing the change on "last"
In contrast, if we didn't bind to the compute:
const foo = {
first: "Wonder"
};
const last = compute( "Woman" );
const hero = {
fullName: compute( function() {
return foo.first + " " + last();
} )
};
console.log( hero.fullName() ); // console.logs "Wonder Woman"
foo.first = "Super";
console.log( hero.fullName() ); // console.logs "Super Woman" because fullName did not cache its previous value
last( "Man" );
console.log( hero.fullName() ); // console.logs "Super Man"