87

Is it possible in knockout to get the current value of an observable within a subscription to that observable, before it receives the new value?

Example:

this.myObservable = ko.observable();
this.myObservable.subscribe(function(newValue){
    //I'd like to get the previous value of 'myObservable' here before it's set to newValue
});
KodeKreachor
  • 8,852
  • 10
  • 47
  • 64

5 Answers5

154
ko.subscribable.fn.subscribeChanged = function (callback) {
    var oldValue;
    this.subscribe(function (_oldValue) {
        oldValue = _oldValue;
    }, this, 'beforeChange');

    this.subscribe(function (newValue) {
        callback(newValue, oldValue);
    });
};

Use the above like this:

MyViewModel.MyObservableProperty.subscribeChanged(function (newValue, oldValue) {

});
JBeagle
  • 2,630
  • 2
  • 18
  • 20
  • 2
    fairly new to knockout, but I'm wishing this is the way the default subscribe was setup. Or.. this fn will at least scratch my first itch as I use 'subscribe' for the first time. – bkwdesign Nov 08 '13 at 20:54
  • 1
    There has been some movement on this on https://github.com/knockout/knockout/issues/914. Looks like it's slated for the 3.4 release. – AlignedDev Sep 25 '15 at 15:09
  • 2
    In case the subscribed observable value type is an Array, you have to slice it, otherwise the oldValue will always be the same as the newValue. Check a working example, here: https://jsfiddle.net/david_freire/xmk6u9yn/4/ – David Freire Sep 30 '15 at 02:27
  • 1
    Cool. Added a return value which is a subscription object with a `dispose()` function https://gist.github.com/30ff1f5c1adf215179b0046515f86e45 – Michael Jul 20 '16 at 06:33
  • Oh just saw the git conversation. – Michael Jul 20 '16 at 06:36
  • @DavidFreire if the array contains objects it's still broken. I tried to work around it, it's quite messy. In the end I added an additional subscription that handles the value as JSON completely. – br4nnigan Apr 14 '20 at 09:39
92

There is a way to do a subscription to the before value like this:

this.myObservable = ko.observable();
this.myObservable.subscribe(function(previousValue){
    //I'd like to get the previous value of 'myObservable' here before it's set to newValue
}, this, "beforeChange");
RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • what does `this` stands for here? – Thanasis Ioannidis Jun 19 '20 at 11:00
  • @ThanasisIoannidis It ensures that you can access the same value of "this" inside the callback function. Otherwise you would need to save it to another name beforehand, e.g. var self = this; (which is a perfectly valid strategy too). – marcus Jul 14 '21 at 18:16
23

Little change to Beagle90 answer. Always return the subscription itself to be able to access the dispose() for instance.

ko.subscribable.fn.subscribeChanged = function (callback) {
    var oldValue;
    this.subscribe(function (_oldValue) {
        oldValue = _oldValue;
    }, this, 'beforeChange');

    var subscription = this.subscribe(function (newValue) {
        callback(newValue, oldValue);
    });

    // always return subscription
    return subscription;
};
Andries
  • 1,547
  • 10
  • 29
  • 2
    This is a real step up, but calling `.dispose` on the return value from this will only dispose of the second subscription, not the `'beforeChange'` subscription – TRManderson Jan 31 '19 at 03:57
18

The pull request to add this feature has some different code that winds up being better than relying on using the beforeChange event.

All credit for the solution to Michael Best

ko.subscribable.fn.subscribeChanged = function (callback) {
    var savedValue = this.peek();
    return this.subscribe(function (latestValue) {
        var oldValue = savedValue;
        savedValue = latestValue;
        callback(latestValue, oldValue);
    });
};

To quote Michael:

I originally suggested using beforeChange to solve this problem but have since realized that it's not always reliable (for example, if you call valueHasMutated() on the observable).

James Johnson
  • 512
  • 3
  • 11
4

I have found that I can call peek() from a writable computed observable to get the before value.

Something like this (see http://jsfiddle.net/4MUWp):

var enclosedObservable = ko.observable();
this.myObservable = ko.computed({
    read: enclosedObservable,
    write: function (newValue) {
        var oldValue = enclosedObservable.peek();
        alert(oldValue);
        enclosedObservable(newValue);
    }
});
rjmunro
  • 27,203
  • 20
  • 110
  • 132
  • 1
    That doesn't work, unfortunately, since by the time the subscribe callback is called, the value already changed and so `peek()` will give you the new value. – Michael Teper Jan 17 '14 at 23:55
  • @MichaelTeper I know I posted my answer a year ago, but after I got some downvotes, I've just tested it, and it does work. See: http://jsfiddle.net/4MUWp/ – rjmunro Jul 29 '14 at 21:47
  • Ok I see what you did there... The question was about retrieving the value in a `subscribe` callback which cannot be done with peek(). Your example proves nothing and could confuse a newcomer. You basically are wrapping a private variable here, and display its value before setting it - so of course it won't have changed. – Simon_Weaver Feb 05 '15 at 21:24