Computed Observable is a function which is dependent on one or more Observables and automatically updates whenever its underlying Observables (dependencies) change.
Computed Observables can be chained.
this.varName = ko.computed(function(){ ... ... // function code ... },this);
Let us look at the following example which demonstrates the 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>
In the following lines, first two are for accepting input values. Third line prints the average of these two numbers.
<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>
In the following lines, type of Observables a and b is number when they are initialized for the first time inside ViewModel. However, in KO every input accepted from UI is by default in the String format. So they need to be converted to Number so as to perform arithmetic operation on them.
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);
In the following line, the calculated average is displayed in the UI. Note that data-bind type of totalAvg is just text.
<p>Average := <span data-bind = "text: totalAvg"></span></p>
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 the text boxes and observe that the average is calculated.
Note that in the above example, the 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 the reference of this. Doing so, there is no need to track this throughout the code. Instead, self can be used.
Following ViewModel code is rewritten for the above example using self.
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; }); }
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 reevaluation and memory usage efficiently.
When a Computed Observable is returning primitive data type value (String, Boolean, Null, and Number) then its subscribers are notified if and only if the actual value change takes place. It means if an Observable has received the value same as the previous value, then its subscribers are not notified.
You can make Computed Observables always explicitly notify the observers, even though the new value is the same as the old by using the notify syntax as follows.
myViewModel.property = ko.pureComputed(function() { return ...; // code logic goes here }).extend({ notify: 'always' });
Too many expensive updates can result in performance issues. You can limit the number of notifications to be received from Observable using rateLimit attribute as follows.
// make sure there are updates no more than once per 100-millisecond period myViewModel.property.extend({ rateLimit: 100 });
In certain situations, it might be necessary to find out if a property is a Computed Observable. Following functions can be used to identify the types of Observables.
Sr.No. | Function |
---|---|
1 | ko.isComputed Returns true if the property is Computed Observable. |
2 | ko.isObservable Returns true if the 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) |
Computed Observable is derived from one or multiple other Observables, so it is read only. However, 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 work 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 the chaining syntax as follows.
myViewModel.fullName('Tom Smith').age(45)
Following example demonstrates the 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. Hence, both the dates (today date and birth date) are converted into milliseconds and then the difference between them is converted back in years and months.
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 observe that the age is calculated.