1

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"?

Community
  • 1
  • 1
Philip Bijker
  • 4,955
  • 2
  • 36
  • 44
  • Can you elaborate on why you want to get the new value before it's set? – Michael Best Dec 30 '16 at 01:19
  • @MichaelBest I'm trying to prevent a FOUC. When I'm able to read the new value before actually setting it, I can update some other state to prevent unwanted intermediate rendering of the view – Philip Bijker Dec 30 '16 at 08:52
  • You should be able to use a computed or subscription to update a second observable that you can use to hide/show/style stuff. E.g.: https://jsfiddle.net/dmnqoc53/ – user3297291 Jan 02 '17 at 13:01

3 Answers3

3

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

user3297291
  • 22,592
  • 4
  • 29
  • 45
  • Thanks for answering. Indeed, my issue was retrieving the new value before actually changing, not retrieving the old value after changing. What would wrapping the observable look like? How to subscribe to it?. – Philip Bijker Dec 30 '16 at 09:08
1

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.

Michael Best
  • 16,623
  • 1
  • 37
  • 70
0

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);
};
Jan Stanicek
  • 1,201
  • 1
  • 14
  • 30