WinJS observables (Part I)
Introduction
First, lets make sure we understand what are observables.
The object is observable when it's able to notify all listeners when its property has changed.
In addition, this capabilities are applied only to the set of the properties and all that notifications are done automatically.
Implementation
Automatic property change notifications are crucial part in MVVM architecture (MVVM stands for Model View ViewModel) . WinJS team (author of WinJS library) prepared a built-in support for it. I think, they should be called notifications (in .NET it's based on interface INotifyPropertyChange) rather than observables (as WinJS proposes) because the story is about notifying the listeners that the property has changed. But let’s stay with the original name "observables".
WinJS observable related methods are defined on WinJS.Binding namespace. There are two levels of defining observables:
- object level: it's done by calling the method WinJS.Binding.as(object_to_be_made_observable). It takes any object and returns new instance with same set of the properties as the original object but making all properties auto-observable.
- prototype level: it's done by calling a method WinJS.Binding.define(class_to_be_observable). It takes a class definition and turns its properties into the observable properties
Pitfalls
WinJS.Binding was implemented ONLY for plain data JS objects and classes. It has the following limitations/disadvantages when it's used in the traditional JS Prototype Oriented Programming:
- only value properties are supported (no get/set properties)
- observable notifications are applied over ALL class members including methods! which is performance bottleneck and useless. This unfortunately causes another issue: calling the private field (its name starts with '_') from the observable method throws exception that the private field is not found. Only private fields defined via WinJS.Class.define() are omitted from making observable.
- there is no way how to specify that only an explicit set of members (fields, properties) should be observable
The developers has to be very careful with using observable objects.
Recapitulation
- WinJS.Binding.as() works on object level and only for plain JavaScript Data Transfer Object (DTO)
- WinJS.Binding.define() works on class level but works only for DTO classes
Observables in the action
There are 3 samples showing observables.
Let's see the video showing their capabilities and later more details.
[View:https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-59-20/3603.oservables.wmv]
The demonstrated project is attached to this article.
Samples
Sample #1 - direct usage of plain DTO objects
When pure DTO object is bound directly to the view and DTO object changes its state, there are no updates propagated to the view. So there is no observation at all.
Sample #2 - using observable DTO objects
Pure DTO objects transformed by calling WinJS.Binding.as(dto) are called observable DTO objects. When a property changes its value, all listeners (objects subscribed to this 'event') are notified. It's basic publish/subscribe scenario.
Sample #3 - using observable DTO objects
Same as previous sample but defined on the prototype level. That's the best option of all three samples in this article. The difference is that a reader of the code immediately can see that the class exposes observable functionality and all references to the created objects are observable.
Usage
The most common usage of observable DTO objects is binding them to View. When observable DTO object is bound to the view and the observable DTO object changes its state (properties), all its property changes are propagated to the view. This enables great separation of concerns and fits well in MVVM architectural style. But more on this in other posts.
How does this observable magic work in detail
Binding the object to the view or calling WinJS.Binding.as(dto) extends DTO with the following observable capabilities:
- _getObservable() - returns the observable proxy instance, one instance per DTO object.
- _backingData - refers the original DTO object
- _listeners - comes from dynamic observable mixin , maps property names to the listeners
- notify(property, newValue, oldValue) - comes from dynamic observable mixin , calls all listeners when the property has changed
The trick behind the scene is that binding related WinJS functioanlity calls _getObservable which returns new instance of ObservableProxy. This proxy object copies all properties (including methods) from original DTO object on proxy object and defines them with setter and getters. The setter method contains a check of difference between old and new values. If the new value differs from the old value, all listeners are notified. I explicitly write, all listeners are notified as it doesn’t use events at all.
When a property changes its value, the logic in the notify method iterates over all listeners subscribed to that property and calls them with 2 arguments: new and old value. The notifications are done asynchronously, via setImmediate() call!
Unfortunately, that's not enough for real M-V-VM scenarios.
Limitations:
- Only value properties are supported, unfortunately, no support for get/set properties is available
- It's not possible to define only subset of the properties to be observable
- PropertyChange notifications are raised via "setImmediate", not directly synchronously and sometimes it's helpful
All these limitations can be solved but more on this in the next part.