KnockoutJS subscribe to multiple observables with

2019-01-19 11:15发布

问题:

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?

回答1:

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.



回答2:

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);
}


回答3:

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')


回答4:

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);
  });
 } 


回答5:

Typescript version made for executing the same call back on any Observable in a list of Observables.

This solution works for the types:

  1. KnockoutObservableArray<KnockoutObservable<T>>
  2. KnockoutObservable<KnockoutObservable<T>[]>
  3. KnockoutObservable<T>[]

Benefits of this approach:

  1. 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.
  2. 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;
    }