Skip to content

Commit

Permalink
Merge pull request #2171 from knockout/1735-spectate-event
Browse files Browse the repository at this point in the history
observables notify a "spectate" event whenever their value changes.
  • Loading branch information
mbest committed Dec 14, 2016
2 parents b98dd95 + 4e55d4b commit 87e8a59
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 12 deletions.
25 changes: 25 additions & 0 deletions spec/asyncBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,31 @@ describe('Rate-limited', function() {
expect(beforeChangeSpy.calls.length).toBe(1); // Only one beforeChange notification
});

it('Should notify "spectator" subscribers whenever the value changes', function () {
var observable = new ko.observable('A').extend({rateLimit:500}),
spectateSpy = jasmine.createSpy('notifySpy'),
notifySpy = jasmine.createSpy('notifySpy');

observable.subscribe(spectateSpy, null, "spectate");
observable.subscribe(notifySpy);

expect(spectateSpy).not.toHaveBeenCalled();
expect(notifySpy).not.toHaveBeenCalled();

observable('B');
expect(spectateSpy).toHaveBeenCalledWith('B');
observable('C');
expect(spectateSpy).toHaveBeenCalledWith('C');

expect(notifySpy).not.toHaveBeenCalled();
jasmine.Clock.tick(500);

// "spectate" was called for each new value
expect(spectateSpy.argsForCall).toEqual([ ['B'], ['C'] ]);
// whereas "change" was only called for the final value
expect(notifySpy.argsForCall).toEqual([ ['C'] ]);
});

it('Should suppress change notification when value is changed/reverted', function() {
var observable = ko.observable('original').extend({rateLimit:500});
var notifySpy = jasmine.createSpy('notifySpy');
Expand Down
13 changes: 13 additions & 0 deletions spec/dependentObservableBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,19 @@ describe('Dependent Observable', function() {
expect(notifiedValue).toEqual(3);
});

it('Should notify "spectator" subscribers about changes', function () {
var observable = new ko.observable();
var computed = ko.computed(function () { return observable(); });
var notifiedValues = [];
computed.subscribe(function (value) {
notifiedValues.push(value);
}, null, "spectate");

observable('A');
observable('B');
expect(notifiedValues).toEqual([ 'A', 'B' ]);
});

it('Should notify "beforeChange" subscribers before changes', function () {
var notifiedValue;
var observable = new ko.observable(1);
Expand Down
27 changes: 19 additions & 8 deletions spec/observableBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,19 @@ describe('Observable', function() {

instance('A');
instance('B');
expect(notifiedValues).toEqual([ 'A', 'B' ]);
});

expect(notifiedValues.length).toEqual(2);
expect(notifiedValues[0]).toEqual('A');
expect(notifiedValues[1]).toEqual('B');
it('Should notify "spectator" subscribers about each new value', function () {
var instance = new ko.observable();
var notifiedValues = [];
instance.subscribe(function (value) {
notifiedValues.push(value);
}, null, "spectate");

instance('A');
instance('B');
expect(notifiedValues).toEqual([ 'A', 'B' ]);
});

it('Should be able to tell it that its value has mutated, at which point it notifies subscribers', function () {
Expand Down Expand Up @@ -288,11 +297,13 @@ describe('Observable', function() {
};
instance(456);

expect(interceptedNotifications.length).toEqual(2);
expect(interceptedNotifications[0].eventName).toEqual("beforeChange");
expect(interceptedNotifications[1].eventName).toEqual("None");
expect(interceptedNotifications[0].value).toEqual(123);
expect(interceptedNotifications[1].value).toEqual(456);
// This represents the current set of events that are generated for an observable. This set might
// expand in the future.
expect(interceptedNotifications).toEqual([
{ eventName: 'beforeChange', value: 123 },
{ eventName: 'spectate', value: 456 },
{ eventName: 'None', value: 456 }
]);
});

it('Should inherit any properties defined on ko.subscribable.fn or ko.observable.fn', function() {
Expand Down
30 changes: 30 additions & 0 deletions spec/pureComputedBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,36 @@ describe('Pure Computed', function() {
expect(timesEvaluated).toEqual(3);
});

it('Should notify "spectator" subscribers whenever the value changes', function () {
var observable = new ko.observable('A');
var computed = ko.pureComputed(observable);
var computed2 = ko.pureComputed(computed);
var notifiedValues = [];
computed.subscribe(function (value) {
notifiedValues.push(value);
expect(computed()).toBe(value);
expect(computed2()).toBe(value);
}, null, "spectate");

expect(notifiedValues).toEqual([]);

// Reading the computed for the first time causes a notification
expect(computed()).toEqual('A');
expect(computed2()).toEqual('A');
expect(notifiedValues).toEqual(['A']);

// Reading it a second time doesn't
expect(computed()).toEqual('A');
expect(computed2()).toEqual('A');
expect(notifiedValues).toEqual(['A']);

// Changing the dependency doesn't, but reading the computed again does
observable('B');
expect(notifiedValues).toEqual(['A']);
expect(computed()).toEqual('B');
expect(notifiedValues).toEqual(['A', 'B']);
});

it('Should not subscribe to dependencies while sleeping', function() {
var data = ko.observable('A'),
computed = ko.pureComputed(data);
Expand Down
8 changes: 5 additions & 3 deletions src/subscribables/dependentObservable.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,16 @@ var computedFn = {
if (computedObservable.isDifferent(state.latestValue, newValue)) {
if (!state.isSleeping) {
computedObservable["notifySubscribers"](state.latestValue, "beforeChange");
} else {
computedObservable.updateVersion();
}

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

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

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

Expand Down
5 changes: 4 additions & 1 deletion src/subscribables/observable.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ ko.observable = function (initialValue) {
var observableFn = {
'equalityComparer': valuesArePrimitiveAndEqual,
peek: function() { return this[observableLatestValue]; },
valueHasMutated: function () { this['notifySubscribers'](this[observableLatestValue]); },
valueHasMutated: function () {
this['notifySubscribers'](this[observableLatestValue], 'spectate');
this['notifySubscribers'](this[observableLatestValue]);
},
valueWillMutate: function () { this['notifySubscribers'](this[observableLatestValue], 'beforeChange'); }
};

Expand Down

0 comments on commit 87e8a59

Please sign in to comment.