Get new value of an observable in "beforeChange" subscription

7.3k views Asked by At

I know there are solutions to get the new value of an observable in a subscribe event (after the actual change), but I was wondering if it's possible to access the new value of an observable in a "beforeChange" subscription.

Below is a snippet of the current version of Knockout (3.4.1) raising the beforeChange and afterChange subscription handlers with the old value and the new value respectively:

if (computedObservable.isDifferent(state.latestValue, newValue)) {
    if (!state.isSleeping) {
        computedObservable["notifySubscribers"](state.latestValue, "beforeChange");
    }

    state.latestValue = newValue;
    if (DEBUG) computedObservable._latestValue = newValue;

    if (state.isSleeping) {
        computedObservable.updateVersion();
    } else if (notifyChange) {
        computedObservable["notifySubscribers"](state.latestValue);
    }

    changed = true;
}

Apparently the newValue is available at "beforeChange" so I guess forking would be an option, but I'd prefer not to.

Is there any other solution to get this new value "beforeChange"?

3

There are 3 answers

0
Michael Best On BEST ANSWER

Note: This answer is based on your comment to the question and not the original question.

When an observable is changed, it calls a method on the observable called notifySubscribers, which does the actual notification and thus updates any computed observables, bindings, etc. that depend on that observable. If you want to do something before any of these notifications, you have a few options.

  1. As @user3297291 noted, use Ryan Niemeyer's protectedObservable.

  2. Subscribe to the observable right away, before any anything else subscribes to it. Subscribers are always called in order, so your code will always run before anything else.

  3. Override notifySubscribers to hook into the notification process (remember to call the previous method).

  4. Possibly for Knockout 3.5.0, there will be a spectate event that occurs before the change event.

1
user3297291 On

Edit: I just reread your question and figured you probably meant you want to check the new value before it is set? If that's the case, you might want to do something like this.


I usually create two subscriptions and an extra variable to keep track of the old (which you've probably already tried):

var myObs = ko.observable(1);

// Keep track of old
var myObsOld = myObs();
myObs.subscribe(function(oldVal) {
  myObsOld = oldVal;
}, null, "beforeChange");

// Subscribe to new
myObs.subscribe(function(newVal) {
  console.log("myObs changed from", myObsOld, "to", newVal);
});

myObs(2);
myObs(3);
.as-console-wrapper { min-height: 100%; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

Now, if you don't want to litter your viewmodel with extra variables and subscriptions, you could encapsulate this logic in an extender:

ko.extenders.latestValue = function(target, option) {
  target.latestValue = target();
  
  target.subscribe(function(oldVal) {
    target.latestValue = oldVal;  
  }, null, "beforeChange");
  
  return target;
};

var myObs2 = ko.observable("a").extend({ latestValue: true });
myObs2.subscribe(function(newVal) {
  console.log("myObs2 changed from", myObs2.latestValue, "to", newVal);
});

myObs2("b");
myObs2("c");
.as-console-wrapper { min-height: 100%; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

Not a very sexy approach, but I think it's better than forking the repo :)

0
Jan Stanicek On

This is not exactly response to you request, but might be useful for slightly different usecases.

In case you don't insist on calling subscription before the change is propagated to the observable and be triggered in group with beforeChange subscriptions there is possibility to to create own subscription which pushes old and new value together to the callback.

This subscription is triggered AFTER the change happens, it just provide previous and current values together.

/** 
 * extended version of knockout subscription provides to callback function new and previous value at once
 * function Callback(newValue, originalValue) {...}
 * 
 * @param {Function} callback function called on observable change
 * @param {Object} context of the callback
 * @return {Object} instance of changed subscription
 */
ko.subscribable.fn.subscribeChanged = function (callback, context) {
    var savedValue = this.peek();
    return this.subscribe(function (latestValue) {
        var oldValue = savedValue;
        savedValue = latestValue;
        callback(latestValue, oldValue);
    }, context);
};