I've got a model class in KnockoutJS which has multiple values that I'd like to subscribe to. Each subscription will perform the same task, like so:
function CaseAssignmentZipCode(zipCode, userId, isNew) {
var self = this;
self.zipCode = ko.observable(zipCode);
self.userId = ko.observable(userId);
self.isNew = isNew;
self.isUpdated = false;
self.zipCode.subscribe(function () { self.isUpdated = true; });
self.userId.subscribe(function () { self.isUpdated = true; });
}
Is there a way to combine these two calls to subscribe, so that I can use one subscription to 'watch' both values?
You can use a computed observable for this purpose. You just need to make sure that you access the value of each observable in the read function. Would be something like:
ko.computed(function() {
self.zipCode();
self.userId();
self.isUpdated = true;
});
So, you get dependencies on the two observables and set your flag.
Also, if you are looking for something like a "dirty" flag, then you might consider something like: http://www.knockmeout.net/2011/05/creating-smart-dirty-flag-in-knockoutjs.html. The idea is that you use a computed observable that calls ko.toJS() on an object to unwrap all of its observables.
Do you not want to duplicate handler function's body? Extract it to a variable.
function CaseAssignmentZipCode(zipCode, userId, isNew) {
var self = this;
self.zipCode = ko.observable(zipCode);
self.userId = ko.observable(userId);
self.isNew = isNew;
self.isUpdated = false;
var handler = function () { self.isUpdated = true; };
self.zipCode.subscribe(handler);
self.userId.subscribe(handler);
}
You can create some kind of an extension for this purpose. Simple example:
function subscribeMany(callback, observables) {
for (var i = 0; i < observables.length; i++) {
observables[i].subscribe(callback);
}
}
Usage:
var name = ko.observable();
var email = ko.observable();
var callback = function(value) {
console.log(value);
};
subscribeMany(callback, [name, email]);
name('test 1')
email('test 2')
Improving upon refactoring the function body into a variable, by turning the list of dependencies to track into a loop:
function CaseAssignmentZipCode(zipCode, userId, isNew) {
var self = this;
self.zipCode = ko.observable(zipCode);
self.userId = ko.observable(userId);
self.isNew = isNew;
self.isUpdated = false;
var handler = function () { self.isUpdated = true; };
ko.utils.arrayForEach([self.zipCode, self.userId], function(obs) {
obs.subscribe(handler);
});
}
Typescript version made for executing the same call back on any Observable in a list of Observables.
This solution works for the types:
KnockoutObservableArray<KnockoutObservable<T>>
KnockoutObservable<KnockoutObservable<T>[]>
KnockoutObservable<T>[]
Benefits of this approach:
- If an Observable gets added to your
KnockoutObservableArray
then the change will be detected and the subscribe function will be added to that Observable as well.
The same solution can be used for many different types and the types are handled for you.
function subscribeMany<T>(
observables: KnockoutObservableArray<KnockoutObservable<T>> | KnockoutObservable<KnockoutObservable<T>[]> | KnockoutObservable<T>[],
callback: (v: T) => void
): KnockoutObservableArray<KnockoutObservable<T>> | KnockoutObservable<KnockoutObservable<T>[]> | KnockoutObservable<T>[] {
function _subscribeMany<T>(
observables: KnockoutObservableArray<KnockoutObservable<T>> | KnockoutObservable<KnockoutObservable<T>[]> | KnockoutObservable<T>[],
callback: (v: T) => void): void {
if (_isObservableArray<T>(observables)) {
_subcribeAndRun(observables, (array) => {
array.forEach((observable) => {
observable.subscribe(callback);
});
});
}
else {
observables.forEach((observable) => {
observable.subscribe(callback);
});
}
}
function _isObservableArray<T>(observables: KnockoutObservableArray<KnockoutObservable<T>> | KnockoutObservable<KnockoutObservable<T>[]> | KnockoutObservable<T>[]): observables is KnockoutObservableArray<KnockoutObservable<T>> | KnockoutObservable<KnockoutObservable<T>[]> {
return "subscribe" in observables;
}
function _subcribeAndRun<T>(o: KnockoutObservable<T>, callback: (v: T) => void): KnockoutObservable<T> {
o.subscribe(callback);
callback(o());
return o;
}
_subscribeMany<T>(observables, callback);
return observables;
}