Metro: Understanding Observables

Posted by Stephen.Walther on Stephen Walter See other posts from Stephen Walter or by Stephen.Walther
Published on Sat, 25 Feb 2012 17:21:36 +0000 Indexed on 2012/03/18 18:17 UTC
Read the original article Hit count: 539

Filed under:

The goal of this blog entry is to describe how the Observer Pattern is implemented in the WinJS library. You learn how to create observable objects which trigger notifications automatically when their properties are changed.

Observables enable you to keep your user interface and your application data in sync. For example, by taking advantage of observables, you can update your user interface automatically whenever the properties of a product change. Observables are the foundation of declarative binding in the WinJS library.

The WinJS library is not the first JavaScript library to include support for observables. For example, both the KnockoutJS library and the Microsoft Ajax Library (now part of the Ajax Control Toolkit) support observables.

Creating an Observable

Imagine that I have created a product object like this:

var product = {
    name: "Milk",
    description: "Something to drink",
    price: 12.33
};

Nothing very exciting about this product. It has three properties named name, description, and price.

Now, imagine that I want to be notified automatically whenever any of these properties are changed. In that case, I can create an observable product from my product object like this:

var observableProduct = WinJS.Binding.as(product);

This line of code creates a new JavaScript object named observableProduct from the existing JavaScript object named product. This new object also has a name, description, and price property. However, unlike the properties of the original product object, the properties of the observable product object trigger notifications when the properties are changed.

Each of the properties of the new observable product object has been changed into accessor properties which have both a getter and a setter. For example, the observable product price property looks something like this:

price: { 

   get: function () { return this.getProperty(“price”); } 

   set: function (value) { this.setProperty(“price”, value); } 

} 

When you read the price property then the getProperty() method is called and when you set the price property then the setProperty() method is called. The getProperty() and setProperty() methods are methods of the observable product object.

The observable product object supports the following methods and properties:

· addProperty(name, value) – Adds a new property to an observable and notifies any listeners.

· backingData – An object which represents the value of each property.

· bind(name, action) – Enables you to execute a function when a property changes.

· getProperty(name) – Returns the value of a property using the string name of the property.

· notify(name, newValue, oldValue) – A private method which executes each function in the _listeners array.

· removeProperty(name) – Removes a property and notifies any listeners.

· setProperty(name, value) – Updates a property and notifies any listeners.

· unbind(name, action) – Enables you to stop executing a function in response to a property change.

· updateProperty(name, value) – Updates a property and notifies any listeners.

So when you create an observable, you get a new object with the same properties as an existing object. However, when you modify the properties of an observable object, then you can notify any listeners of the observable that the value of a particular property has changed automatically.

Imagine that you change the value of the price property like this:

observableProduct.price = 2.99;

In that case, the following sequence of events is triggered:

1. The price setter calls the setProperty(“price”, 2.99) method

2. The setProperty() method updates the value of the backingData.price property and calls the notify() method

3. The notify() method executes each function in the collection of listeners associated with the price property

Creating Observable Listeners

If you want to be notified when a property of an observable object is changed, then you need to register a listener. You register a listener by using the bind() method like this:

(function () {
    "use strict";

    var app = WinJS.Application;

    app.onactivated = function (eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {

            // Simple product object
            var product = {
                name: "Milk",
                description: "Something to drink",
                price: 12.33
            };

            // Create observable product
            var observableProduct = WinJS.Binding.as(product);

            // Execute a function when price is changed
            observableProduct.bind("price", function (newValue) {
                console.log(newValue);
            });

            // Change the price
            observableProduct.price = 2.99;

        }
    };

    app.start();
})();

In the code above, the bind() method is used to associate the price property with a function. When the price property is changed, the function logs the new value of the price property to the Visual Studio JavaScript console.

The price property is associated with the function using the following line of code:

// Execute a function when price is changed
observableProduct.bind("price", function (newValue) {
   console.log(newValue);
});

Coalescing Notifications

If you make multiple changes to a property – one change immediately following another – then separate notifications won’t be sent. Instead, any listeners are notified only once. The notifications are coalesced into a single notification.

For example, in the following code, the product price property is updated three times. However, only one message is written to the JavaScript console. Only the last value assigned to the price property is written to the JavaScript Console window:

// Simple product object
var product = {
    name: "Milk",
    description: "Something to drink",
    price: 12.33
};

// Create observable product
var observableProduct = WinJS.Binding.as(product);

// Execute a function when price is changed
observableProduct.bind("price", function (newValue) {
    console.log(newValue);
});

// Change the price
observableProduct.price = 3.99;
observableProduct.price = 2.99;
observableProduct.price = 1.99;

Only the last value assigned to price, the value 1.99, appears in the console:

image

If there is a time delay between changes to a property then changes result in different notifications. For example, the following code updates the price property every second:

// Simple product object
var product = {
    name: "Milk",
    description: "Something to drink",
    price: 12.33
};

// Create observable product
var observableProduct = WinJS.Binding.as(product);

// Execute a function when price is changed
observableProduct.bind("price", function (newValue) {
    console.log(newValue);
});

// Add 1 to price every second
window.setInterval(function () {
    observableProduct.price += 1;
}, 1000);

In this case, separate notification messages are logged to the JavaScript Console window:

image

If you need to prevent multiple notifications from being coalesced into one then you can take advantage of promises. I discussed WinJS promises in a previous blog entry:

http://stephenwalther.com/blog/archive/2012/02/22/windows-web-applications-promises.aspx

Because the updateProperty() method returns a promise, you can create different notifications for each change in a property by using the following code:

// Change the price
observableProduct.updateProperty("price", 3.99)
   .then(function () {
      observableProduct.updateProperty("price", 2.99)
         .then(function () {
            observableProduct.updateProperty("price", 1.99);
         });
    });

In this case, even though the price is immediately changed from 3.99 to 2.99 to 1.99, separate notifications for each new value of the price property are sent.

image

Bypassing Notifications

Normally, if a property of an observable object has listeners and you change the property then the listeners are notified. However, there are certain situations in which you might want to bypass notification. In other words, you might need to change a property value silently without triggering any functions registered for notification.

If you want to change a property without triggering notifications then you should change the property by using the backingData property. The following code illustrates how you can change the price property silently:

// Simple product object
var product = {
    name: "Milk",
    description: "Something to drink",
    price: 12.33
};

// Create observable product
var observableProduct = WinJS.Binding.as(product);

// Execute a function when price is changed
observableProduct.bind("price", function (newValue) {
    console.log(newValue);
});

// Change the price silently
observableProduct.backingData.price = 5.99;
console.log(observableProduct.price); // Writes 5.99 

The price is changed to the value 5.99 by changing the value of backingData.price. Because the observableProduct.price property is not set directly, any listeners associated with the price property are not notified.

When you change the value of a property by using the backingData property, the change in the property happens synchronously. However, when you change the value of an observable property directly, the change is always made asynchronously.

Summary

The goal of this blog entry was to describe observables. In particular, we discussed how to create observables from existing JavaScript objects and bind functions to observable properties. You also learned how notifications are coalesced (and ways to prevent this coalescing). Finally, we discussed how you can use the backingData property to update an observable property without triggering notifications.

In the next blog entry, we’ll see how observables are used with declarative binding to display the values of properties in an HTML document.

© Stephen Walter or respective owner

Related posts about metro