Wednesday, March 1, 2017

KnockoutJS - Computed Observables

  • Computed Observable is a function which is dependent on one or more Observables and will automatically update whenever its underlying Observables (dependencies) change.
  • Computed Observables can be chained.

Syntax

this.varName = ko.computed(function(){
...
... //  function code
...
},this);

Example

Let us see below example which demonstrates use of Computed Observables:
<!DOCTYPE html>
<head >
   <title>KnockoutJS Computed Observables</title>
   <script src="https://ajax.aspnetcdn.com/ajax/knockout/knockout-3.1.0.js"></script>
</head>
<body>

   <p>Enter first number: <input data-bind="value: a" /></p>
   <p>Enter second number: <input data-bind="value: b"/></p>
   <p>Average := <span data-bind="text: totalAvg"></span></p>

<script>
   function MyViewModel() {
      this.a = ko.observable(10);
      this.b = ko.observable(40);

      this.totalAvg = ko.computed(function(){
         if(typeof(this.a()) !== "number" || typeof(this.b()) !== "number"){
         this.a(Number(this.a()));   //convert string to Number
         this.b(Number(this.b()));   //convert string to Number
      }
      total = (this.a() + this.b())/2 ;
      return total;
      },this);
   }
   ko.applyBindings(new MyViewModel());

</script>
</body>
</html>
  • <p>Enter first number: <input data-bind="value: a" /></p>
    <p>Enter second number: <input data-bind="value: b"/></p>
    <p>Average := <span data-bind="text: totalAvg"></span></p>
    First 2 lines are for accepting input values. 3rd line prints average of these 2 numbers.
  • this.totalAvg = ko.computed(function(){
       if(typeof(this.a()) !== "number" || typeof(this.b()) !== "number"){
       this.a(Number(this.a()));   //convert string to Number
       this.b(Number(this.b()));   //convert string to Number
      }
       total = (this.a() + this.b())/2 ;
       return total;
    },this);
    Type of Observables a and b is number when they are initialized for first time inside ViewModel. But in KO every input accepted from UI is by default in String format. So they need to be converted to Number so as to perform arithmetic operation on them.
  • <p>Average := <span data-bind="text: totalAvg"></span></p>
    Calculated average is displayed in UI. Note that data-bind type of totalAvg is just text.

Output

Let's carry out the following steps to see how the above code works:
  • Save the above code in computed-observable.htm file.
  • Open this HTML file in a browser.
  • Enter any 2 numbers in text boxes and see that average is calculated.

Managing 'This'

Note in above example that second parameter is provided as this to Computed function. It is not possible to refer to Observables a() and b() without providing this.
To overcome this, self variable is used which holds reference of this. Doing so, there is no need to track this throughout code. Instead self can be used.
ViewModel code is rewritten for above example using self as below.
function MyViewModel(){
   self = this;
   self.a = ko.observable(10);
   self.b = ko.observable(40);

   this.totalAvg = ko.computed(function(){
      if(typeof(self.a()) !== "number" || typeof(self.b()) !== "number"){
      self.a(Number(self.a()));   //convert string to Number
      self.b(Number(self.b()));   //convert string to Number
     }
      total = (self.a() + self.b())/2 ;
      return total;
   });
}

Pure Computed Observables

A Computed Observable should be declared as Pure Computed Observable if that Observable is simply calculating and returning the value and not directly modifying the other objects or state. Pure Computed Observables helps knockout to manage re-evaluation and memory usage efficiently.

Notifying subscribers explicitly

When a Computed Observable is returning primitive data type value (String, Boolean, Null and Number) then its subscribers are notified if and only if actual value change takes place. Means if an Observable has received value same as previous value then its subscribers are not notified.
You can make Computed Observables to explicitly notify observers always even though the new value is same as old by using notify syntax as below
myViewModel.property = ko.pureComputed(function() {
    return ...;  // code logic goes here
}).extend({ notify: 'always' });

Limiting change notifications

Too many expensive updates can result into performance issue. You can limit the number of notifications to be received from Observable using rateLimit attribute as below
// make sure there are updates no more than once per 100-millisecond period
myViewModel.property.extend({ rateLimit: 100 });

Finding out if a property is Computed Observable

In certain situation it might be required to find out if a property is a Computed Observable. Below are few functions which can be used to identify types of Observables.
S.N.Function
1 ko.isComputed
Returns true if property is Computed Observable.
2 ko.isObservable
Returns true if property is Observable, Observable array or Computed Observable.
3 ko.isWritableObservable
Returns true if Observable, Observable array or Writable Computed Observable. (This is also called as ko.isWriteableObservable)

Writable Computed Observables

Computed Observable is derived from one or multiple other Observables, so it is read only. But it is possible that one can make Computed Observable writable. For this you need to provide callback function that works on written values.
These writable Computed Observables works just like regular Observables, in addition they require custom logic to be built for interfering read and write actions.
One can assign values to many Observables or Computed Observable properties using chaining syntax as below:
myViewModel.fullName('Tom Smith').age(45)

Example

Following example demonstrates use of Writable Computable Observable:
<!DOCTYPE html>
<head >
   <title>KnockoutJS Writable Computed Observable</title>
   <script src="https://ajax.aspnetcdn.com/ajax/knockout/knockout-3.3.0.js"></script>
</head>
<body>
   <p>Enter your birth Date: <input type="date" data-bind="value: rawDate" >  </p>
   <p> <span data-bind="text: yourAge"></span></p>

<script>
   function MyViewModel(){
      this.yourAge = ko.observable();
      today = new Date();
      rawDate = ko.observable();

      this.rawDate = ko.pureComputed({
         read: function(){
            return this.yourAge;
         },
         write: function(value){
            var b = Date.parse(value);   // convert birth date into milliseconds
            var t = Date.parse(today);   // convert todays date into milliseconds
            diff = t - b;                 // take difference
            var y = Math.floor(diff/31449600000);         // difference is converted into years. 31449600000 milliseconds form a year.
            var m = Math.floor((diff % 31449600000)/604800000/4.3);  //calculating months. 604800000 milliseconds form a week.
         this.yourAge("You are " + y + " year(s) " + m +" months old.");
         },
         owner: this
      });
   }
   ko.applyBindings(new MyViewModel());

</script>
</body>
</html>
  • In the above code, rawDate is pureComputed property accepted from UI. yourAge Observable is derived from rawDate.
  • Dates in Javascript are manipulated in milliseconds. So both the dates (today date and birth date) are converted into milliseconds and then difference between them is converted back in years and months.

Output

Let's carry out the following steps to see how the above code works:
  • Save the above code in writable_computed_observable.htm file.
  • Open this HTML file in a browser.
  • Enter any birth date and see that age is calculated.

No comments:

Post a Comment