Knockout is amazing. It is fast and intuitive. I use the subscribe function a lot, but I found myself lacking a general subscribe that allows me to track the changes of an entire ViewModel, so I created one myself that even supports unsubscribe and throttling.

The basic idea

So what are we going to do? Given a ViewModel, we’ll:

  • Find all observable properties
  • Subscribe to those properties
  • Inspect the value of¬†each observable¬†property and do the same: find, subscribe and inspect.

We’ll extend on the¬†ko¬†variable, so we’ll get the following new features:

  • To subscribe a model: ko.subscribe(vm, fn, 100);
  • To unsubscribe a model: ko.unsubscribe(vm, fn);

So let’s dive in!

First things first: LoDash

Lo-Dash is a low-level utility library that implements common operations. It was created as a fork of the Underscore project. Unlike most libraries, Lo-Dash eschews almost all native iteration methods in favor of simplified loops, resulting in tight, lean code (read more). It makes code faster and more readable. I use it for array loops and to implement the throttling feature.

Tha code

A while back I wrote a blog on Automatic Knockout model persistence with Amplify. I’ll be using the same method for looping through the properties of a model.

(function () {

	//stores the subscriptions
	var subscriptions = [];

	//maintains a unique identifier 
	var id = 0;

	ko.subscribe = function (vm, fnOnChange, throttle) {

		var myId = ++id;

		//store subscription - add throttle
		subscriptions.push({
			id: myId,
			change: _.throttle(function () {
				fnOnChange(vm);
			}, throttle)
		});

		//subscripe model with id.
		_subscribe(vm, myId)
	};

	ko.unsubscribe = function (vm, fnOnChange) {
		_.remove(subscriptions, function (item) {
			return item.vm == vm && item.change == fnOnChange;
		});
	};

	function _subscribe(vm, id) {

		if (_.isArray(vm)) {

			//loop through array values and subscribe to each item
			for (var i = 0; i < vm.length; i++) {
				_subscribe(vm[i], id);
			}
		}
		else {

			//prevent double subscriptions by checking
			//a 'magic' property:
			var subscriberId = '_ko_subscr_' + id;

			if (_.isUndefined(vm[subscriberId])) {

				vm[subscriberId] = true;

				//subscribe to each observable
				for (var n in vm) {

					var observable = vm[n];

					if (ko.isObservable(observable) && !ko.isComputed(observable)) {

						observable.subscribe(function (newValue) {

							//subscribe the new observable value
							_subscribe(newValue, id);

							//fire event, because something just changed
							fire(id);
						});

						//subscribe to current value stored by observable
						var currentValue = observable();
						_subscribe(currentValue, id);
					}
				}
			}
		}
	}

	function fire(id) {
		_.forEach(subscriptions, function (item) {
			if (item.id == id) {
				item.change();
			}
		});
	}

})();

ko.isComputed = function (instance) {
	if ((instance === null) || (instance === undefined) || (instance.__ko_proto__ === undefined)) {
		return false;
	}

	if (instance.__ko_proto__ === ko.dependentObservable) {
		return true;
	}

	// Walk the prototype chain
	return ko.isComputed(instance.__ko_proto__);
};

So basically that’s it.

Demo

I’ve created a Random Greeting demo in JsFiddle,¬†so you can see this code in action.