Subscribing to the change event

3,130 views
Skip to first unread message

Harminder

unread,
Feb 28, 2011, 1:48:48 PM2/28/11
to KnockoutJS
Is there a way to subscribe to the change event. For example: I want
to know when an observable object changes [either by the UI or
something else]. Basically, I want to send data to the server
whenever some change happens. Now I have to bind to the UI for such
notification.

Thanks,

rpn

unread,
Feb 28, 2011, 1:54:48 PM2/28/11
to knock...@googlegroups.com
Sure, there is a subscribe function available on observables.

You could use it like this:

var viewModel {
  name: ko.observable()
}

viewModel.name.subscribe(function(newvalue) {
  alert("name changed to " + newvalue);
});


Harminder

unread,
Feb 28, 2011, 2:41:17 PM2/28/11
to KnockoutJS
this worked, thanks a lot.

Harminder

unread,
Mar 4, 2011, 10:35:40 AM3/4/11
to KnockoutJS
Another related question:
I think subscribing to an observableArray needs to behave a bit
differently than subscribing to an observable. The callback method
returns the new value, and its perfect for an observable. But for an
array, more context is needed. An app may need to know, for example,
at which index an items was inserted/removed. It currently passes the
new array back and I can’t tell what happened. As soon as the array
is updated, I need to send the new item up to the server [instead of
the whole array].

Any ideas if this is possible with existing API.
Thanks

kgvi...@gmail.com

unread,
Mar 4, 2011, 3:21:29 PM3/4/11
to knock...@googlegroups.com
Not sure if this might answer the question about subscribing to ObservableArray.

I worked on something similar recently, so this might help.

In this sample we keeps track of changed items ready to be sent to the server.


Would like to know if there are better ways to do this, but it works.

rpn

unread,
Mar 4, 2011, 8:35:34 PM3/4/11
to knock...@googlegroups.com
I don't see a straightforward way to do exactly what you are asking with the existing API.  An observableArray is really an observable with some extra features.  It notifies subscribers that its value (the array) has changed.

My suggestion would be to tap into the places that you are doing a push/remove on your observableArray.  Keep track at that point or notifiy the server.  Not sure if that works for your project.

Hope this helps.

Harminder

unread,
Mar 4, 2011, 9:13:04 PM3/4/11
to KnockoutJS
Thanks for your responses. I wanted to expose a unified API [ko API]
to update both the server and the view model. I’ll investigate a bit
more and get back if I find any solution. Thanks again .

Steven Sanderson

unread,
Mar 5, 2011, 7:35:33 AM3/5/11
to knock...@googlegroups.com
There is a way to do what you want, though I appreciate it isn't obvious.

The thing is that when an observable array changes, you could have
assigned any new array to it, so it isn't always the case that you've
just added or removed things using the 'push', 'splice', etc, methods.
That's why all you know by default is the new value.

However, to support things like the 'foreach' binding that n

Steven Sanderson

unread,
Mar 5, 2011, 7:44:44 AM3/5/11
to knock...@googlegroups.com
Whoops, sorry, accidentally hit "send"! Stupid iPhone... :)

To support things like the 'foreach' binding that need to know what changed, KO contains an internal utility function called ko.utils.compareArrays. Pass two array values to it, and it will return a data structure describing what was added/deleted/retained. For example,

var previousArrayValue = [];
viewModel.someObservableArray.subscribe(function(newArray) {
    var diff = ko.utils.compareArrays(previousArrayValue, newArray);
    previousArrayValue = newArray;
});

If you use something like Firebug to inspect the value of "diff", you'll see it's a structure containing values from both arrays in their original order, plus each item is marked "added", "deleted", or "retained", so if you wanted you could just filter out the "added"/"deleted" ones to send to the server.

Steve

kgvi...@gmail.com

unread,
Mar 5, 2011, 9:56:56 AM3/5/11
to knock...@googlegroups.com
Thanks Steve, I've been trying to find out more about compareArrays and how it's used, and this really helps.


rpn

unread,
Mar 5, 2011, 10:22:20 AM3/5/11
to knock...@googlegroups.com
I need to play with compareArrays a bit more.  I was trying it the other day and it seemed to work as expected for arrays of strings/numbers, but not for arrays of objects.

I was working around it by comparing arrays with the toJSON values of my view model objects vs.their originals.  Is there a better way to use this for comparing objects?


kgvi...@gmail.com

unread,
Mar 5, 2011, 2:20:43 PM3/5/11
to knock...@googlegroups.com
But never got subscribe to work directly on observableArrays like viewModel.myObsArray.subscribe never worked for me. May be I am missing something?

Steven Sanderson

unread,
Mar 16, 2011, 5:57:59 AM3/16/11
to knock...@googlegroups.com
Sorry, Rpn, I just realised I never actually answered your query about compareArrays! 

ko.utils.compareArrays compares elements using the strict comparison (===) operator, so as you point out, if you have two different objects deserialized from JSON they will be treated as different (because they are different objects in memory), even if logically they represent the same entity as far as you are concerned. 

It would certainly be possible to add a futher "comparer" callback function by which you could supply custom comparison logic and then just compare something like a primary key property. But would this actually be useful to you? If you had changed some other property (not the primary key), then you'd still see the two objects as being equal even though one has been edited. It sounds like you're really trying to difference two entire object graphs, including all the properties, which is vastly more complex and your existing solution of comparing their JSON representations is really the only practical approach I can think of. 

Please let me know if you can think of a particular use case for adding a "comparer" callback to ko.utils.compareArrays.

kgvi...@gmail.com: It really does work in exactly the same way as a regular observable. For example,
var viewModel = { myObsArray : ko.observableArray([]) };
viewModel.myObsArray.subscribe(function(value) { alert("Got updated array: " + JSON.stringify(value)) });
viewModel.myObsArray.push("ABC");
viewModel.myObsArray.push("DEF");

Regards
Steve

Ω Alisson

unread,
Mar 16, 2011, 7:01:39 AM3/16/11
to knock...@googlegroups.com
We could implement deep object comparison like Underscore.js

rpn

unread,
Mar 16, 2011, 10:39:18 AM3/16/11
to knock...@googlegroups.com
For my purposes, I was thinking of something like:
  • pass a "key" function to identify that two objects should be considered the same.  If available, use this function instead of === to compare the array values.  This would give you the added, deleted, and retained arrays.
  • additionally, allow you to pass an optional comparison function that would let you evaluate whether an item has changed.  When the key function indicates that you have a match, then use the comparison function, if it was passed in, to determine if the item gets pushed to a "changed" array.
  • for the comparison, it might be useful to have a utility function that allows you to compare all or a list of properties between two objects.  Maybe even more useful to be able to pass a list of properties to exclude from comparison (the view model version might have additional observables like "showDetails" that wouldn't be in the original).
The use case would be that you retrieve your original array of objects via AJAX or serialized into your markup.  Then, you use the mapping plugin or manually map the objects to turn properties into observables, add new observables, or even replace IDs with the actual object that they reference (replace categoryID with the corresponding category object).  At this point, my original method of comparing JSON representations of the original and edited doesn't work so easily, as the view model version of the objects do not have the same properties.  I would use the comparison function to make sure we are comparing apples to apples.

Here is a quick sample implementation of something like this:  http://jsfiddle.net/rniemeyer/pnV5N/

Thanks.



  
Reply all
Reply to author
Forward
0 new messages