DoneJS StealJS jQuery++ FuncUnit DocumentJS
4.3.0
5.0.0 3.13.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-bind
      • can-compute
      • can-debug
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-define
      • can-observation
      • can-observation-recorder
      • can-observe
        • Properties
          • defineProperty
          • Array
          • Object
        • Decorators
          • async
          • resolver
        • Object Behaviors
          • @can.isBound
          • @can.offKeyValue
          • @can.offPatches
          • @can.onKeyValue
          • @can.onPatches
        • Array Behaviors
          • pop
          • push
          • reverse
          • shift
          • sort
          • splice
          • unshift
        • Function Behaviors
          • @can.computedPropertyDefinitions
          • @can.defineInstanceKey
          • @can.offInstanceBoundChange
          • @can.offInstancePatches
          • @can.onInstanceBoundChange
          • @can.onInstancePatches
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Data Modeling
      • can-connect
      • can-connect-feathers
      • can-fixture
      • can-fixture-socket
      • can-ndjson-stream
      • can-set
    • Views
      • can-component
      • can-stache
      • can-stache-bindings
      • can-stache-converters
      • can-stache-route-helpers
      • can-view-autorender
      • can-view-callbacks
      • can-view-import
      • can-view-live
      • can-view-model
      • can-view-nodelist
      • can-view-parser
      • can-view-scope
      • can-view-target
      • react-view-model
      • react-view-model/component
      • steal-stache
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-hash
      • can-route-mock
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-diff
      • can-globals
      • can-join-uris
      • can-key
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-queues
      • can-string
      • can-string-to-any
      • can-util
      • can-zone
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-child-nodes
      • can-control
      • can-dom-data
      • can-dom-events
      • can-dom-mutate
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-fragment
    • Data Validation
      • can-define-validate-validatejs
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-data-types
      • can-namespace
      • can-reflect
      • can-reflect-dependencies
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

can-observe

  • npm package badge
  • Star
  • Edit on GitHub

Create observable objects, arrays, and functions that work like plain JavaScript objects, arrays, and functions.

observe(target)

Create an observable object that acts as a proxy for a target object.

import observe from "can-observe";
import canReflect from "can-refect";

const dog = observe( {} );

// non-plain JS object behavior exposed through
// symbols used by can-reflect
canReflect.onKeyValue( dog, "name", function( newVal ) {
    newVal; //-> 'Wilbur'
} );

dog.name = "Wilbur";

Parameters

  1. target {Object|Array|function}:

    The object from which an observable instance is created. Depending on what type is passed, the proxy will behave slightly differently:

    • Object - An observable proxy to the target will be returned. All properties not on the prototype will be observable. Any non-primitive and non-built-in property value will be converted to an observable. The Object Behaviors listed in the sidebar are available to can-reflect.

    • Array - Behaves like Object, but supports providing list-like Patches to can.onPatches. The Array Behaviors listed in the sidebar are overwritten to generate @can.onPatches events. The Object Behaviors listed in the sidebar are available to can-reflect.

    • Function - Behaves like Object, but when called with new, makes the instance observable. Also, makes the return value observable if it is already not observable. The Object Behaviors and Function Behaviors listed in the sidebar are available to can-reflect.

Returns

{Proxy}:

A proxy for the target object.

Use Cases

can-observe can be used to make data observable for use with CanJS. CanJS uses observables to communicate state changes in the application. The following creates a dog observable object and uses it to render a can-stache template. When dog's name is set, the page will be updated.

import observe from "can-observe";
import stache from "can-stache";

const dog = observe( {} );

const frag = stache( "<p>dog's name is {{name}}</p>" )( dog );
document.body.appendChild( frag );

dog.name = "Wilbur";

document.body; //-> <p>dog's name is Wilbur</p>

can-observe's exported observe function can also be used to make observable types useful as Models and ViewModels. However, its observe.Object and observe.Array properties are designed specifically for this purpose. observe.Object and observe.Array support "computed" getters. For example, once the following fullName property is bound, it only updates itself when one of its computed dependencies change:

import observe from "can-observe";
class Person extends observe.Object {
    fullName() {
        return this.first + " " + this.last;
    }
}

can-observe allows you to create observable objects where any property added is immediately observable, including nested objects. This makes can-observe ideal for use-cases where the data may be dynamic, or where the more rigid approach of can-define is not needed.

Make data observable

can-observe exports a function that takes an object, array or function, and returns an observable Proxy to that object, array or function.

The following example uses can-observe to create an observable superWoman:

import observe from "can-observe";

const superWoman = observe( {
    name: {
        first: "Luma",
        last: "Lynai"
    },
    hobbies: [ "justice", "soaking up rays (orange sun-only)" ],
    age: 33
} );

You can now add, delete, and set superWoman's properties like you would a normal JavaScript object:

superWoman.name.last = "Lang";
superWoman.power = "overpowered";
delete superWoman.age;

And you can mutate arrays and call all of their methods available to the browser:

superWoman.hobbies.push( "Protecting Staryl" );
superWoman.hobbies.includes( "Justice" ); //-> true

All of these changes publish events observable by the rest of CanJS. For example, can-observation is able create a computed value for superWoman's fullName like:

import Observation from "can-observation";

const fullName = new Observation( function() {
    return superWoman.name.first + " " + superWoman.name.last;
} );

fullName.on( function( newVal ) {
    console.log( newVal ); // -> "Lana Lang"
} );

superWoman.name.first = "Lana";

If you wish to observe changes in an observable made with observe for yourself, either:

  • Use can-reflect:

import canReflect from "can-reflect";

canReflect.onKeyValue( superWoman, "age", function( newVal ) { console.log( newVal ); //-> 34 } );

superWoman.age = 34;

 - Use Object or Array that include methods for
   binding directly on the object:
   ```js
const superWoman = new observe.Object( {
    name: { first: "Luma", last: "Lynai" },
    age: 33
} );
superWoman.on( "age", function( newVal ) {
    console.log( newVal ); //-> 34
} );

superWoman.age = 34;

Using observe directly isn't extremely common in larger CanJS apps that use Object or Array to create special types. However, it can be useful for simple apps, where a well-defined type is not needed.

For example, the following creates a simple counter application:

import stache from "can-stache";
import observe from "can-observe";

const counter = observe( {
    count: 0,
    add: function() {
        this.count++;
    }
} );

const view = stache( "<button on:click='add()'>+1</button>  Count: {{count}}" );

document.body.appendChild( view( counter ) );

Nested Objects

Any Object property in a can-observe will be replaced with a can-observe observed Proxy on read or write. This allows deep path traversal in objects, with observable changes all along the way.

import observe from "can-observe";

const name = { first: "Justin", last: "Meyer" };
const person = {
    name: name
};

const observed = observe( person );
observed;       // -> observed is a Proxy;
observed.name;  // -> also a Proxy
person.name;    // -> this is a plain object instead

observed.address = { city: "Chicago" };  // this gets proxified on set, so...
person.address; // -> this is a Proxy

Defining Observable Types

There are several ways to use observe to define observable types. If you wish to have observable methods like .on and .off on your types, use observe.Object or observe.Array to create special types.

However, can-observe can be used directly to create constructor functions that produce observables in two ways:

  • Calling observe(Type) on the constructor function or class.
  • Having the constructor function or class return an observe(instance) wrapped instance.

Using observe on constructor functions and classes.

If observe is called with a constructor function as follows:

const Animal = observe( function Animal( name ) {
    this.name = name;
    this.calories = 100;
} );
Animal.prototype.eat = function() {
    this.calories++;
};

All instances of Animal will be observable:

const sponge = new Animal( "Bob" );
canReflect.onKeyValue( sponge, "calories", function( newVal ) {
    console.log( newVal ); //-> 101
} );
sponge.eat();

Similarly, if observe is called on a Class function as follows:

Animal = observe( class Animal {
    constructor( name ) {
        this.name = name;
        this.calories = 100;
    }
    eat() {
        this.calories++;
    }
} );

All instances of Animal will be observable:

const sponge = new Animal( "Bob" );
canReflect.onKeyValue( sponge, "calories", function( newVal ) {
    console.log( newVal ); //-> 101
} );
sponge.eat();

NOTE: observe does not change the function passed into it. If instances of the function passed to observe are created, they will not be observable.

class Animal { constructor( name ) { this.name = name; this.calories = 100; } eat() { this.calories++; } } const ObservableAnimal = observe( Animal );

const sponge1 = new Animal( "Bob" ); // NOT OBSERVABLE const sponge2 = new ObservableAnimal( "Bob" ); // OBSERVABLE



### Returning an `observe(instance)` wrapped instance.

To make instances of an existing type observable, you can
return the `observe`-wrapped proxy from the `constructor()` function
as follows:

```js
import observe from "can-observe";

class WidgetViewModel {
    constructor( obj ) {

        // view model instances receive properties as an object on instantiation
        Object.assign( this, obj );
        return observe( this );
    }
    fixedMessage() {
        return "Hello";
    }

    // ...more static and prototype functions.
}

Extending can-observe with rich property behaviors

Like async getters, type coercion, streams, etc from can-define, can-observe supports a number of rich behaviors. However, rather than baking these behaviors into the library directly, can-observe provides mechanism to extend proxy-wrapped objects with custom rich behaviors.

To that end, can-observe recognizes a can.computedPropertyDefinitions property: an object whose values are functions which return a single-value observable; getting or setting a key on the proxy-wrapped object that matches a key in the can.computedPropertyDefinitions object will use those observations. The first time one of these properties is accessed, the function is run, and the observation is cached, to be used for all future use on that instance.

See defineProperty for details about defining your own behaviors.

Browser support

can-observe uses the Proxy feature of JavaScript to observe arbitrary properties. Proxies are available in all modern browsers.

A polyfill is available that brings Proxies back to IE9, with the caveat that only existing properties on the target object can be observed. This means this code:

const person = observe( { first: "", last: "" } );

The first and last properties are observable in older browsers, but any other property added would not be. To ensure maximum compatibility make sure to give all properties a default value.

Use with other observables

can-observe can be combined with any other CanJS observable type, like can-define or can-compute. In this example we create a compute that changes when a can-observe proxy changes. Note that with computes we use canReflect.onValue to set up the event listener and handler.

import compute from "can-compute";
import observe from "can-observe";
import canReflect from "can-reflect";

const person = observe( {
    name: new DefineMap( { first: "Justin", last: "Meyer" } ),
    age: 35
} );

const fullName = compute( function() {
    return person.name.first + " " + person.name.last;
} );

fullName.on( "change", function( ev, newVal ) {
    console.log( newVal ); // -> Chasen Le Hara
} );


person.name.first = "Chasen";
person.name.last = "Le Hara";

can-observe will not convert nested property values it recognizes as:

  • Primitives
  • Built-ins (like Date)
  • Other CanJS observables (like can-define).

How it works

can-observe works by:

  1. Creating base functions that make objects, arrays, and functions observable using proxies in: -make-object.js, -make-array.js, and -make-function.js.
    • These proxies call can-observation-recorder when observables are read. They also support can-reflect's observable symbols.
  2. A place that stores the observable proxies created for non-observable objects and a set that contains a list of proxies: -observable-store.js
    • This prevents duplicating observables for the same non-observable object and a means for identifying something that is already a proxied observable.
  3. A makeObserve function that checks the type and calls the right observable function: -observable-store.js
    • This will be passed to all the base functions so they are able to create the right observable with nested data.
  4. Finally, can-observe.js points the makeObserve function at all the right base functions.

can-observe.Object and can-observe.Array mostly use their underlying base function to setup their behavior. The primary exception is that they support "computed" getters. This behavior works by:

  1. We make sure a can.computedPropertyDefinitions symbol is added to the prototype (see above for details on can.computedPropertyDefinitions).
  2. We create definitions, which return observations derived from the getter function: -computed-helpers.js

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 4.3.0.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news