Prevent computed from collecting dependencies
Asked Answered
B

5

5

Is there a good way to roughly achieve the following concept:

var computed = ko.computed(function() {
    readSomeObservables(); //<-- if these change, notify computed
    ko.stopCollectingDependencies();
    readSomeMoreObservables(); //<-- if these change, do not notify computed
    ko.resumeCollectingDependencies();
});

I am aware of peek(), but in this case the computed is invoking methods that were provided from an external module, and the design calls for it to be purely incidental if those methods happen to involve observables.

I have one solution, which is to roughly do this:

window.setTimeout(function() {
    readSomeMoreObservables();
}, 0);

But for obvious reasons, that is hardly ideal and leads to undesired behavior in some situations.

Bureaucrat answered 30/1, 2013 at 12:19 Comment(7)
Why do you read them from the computed if you do not want it to update when those change?Melanous
I'll restate what was said in the question: the situation is fairly complex in that external modules can provide additional callbacks that need to be invoked at a specific point in the execution flow, but if those callbacks happen to involve reading from observables, those observables should not participate in the computed. So I could redesign the entire module (expensive) or find a workaround (cheap).Bureaucrat
So the computed does not need to publish to its subscribers when those change? Correct?Melanous
@Melanous close - the computed should not subscribe to those observables in the first place; because it must not re-evaluate its own read function, much less notify its subscribers.Bureaucrat
You cant get teh team behind the readSomeMoreObservables to extend it to either use peek or standard read?Melanous
@Melanous that would force other modules to make decisions about whether to read their own observables using peek or the normal way based on the internal implementation of this module. Definitely want to avoid that.Bureaucrat
@Melanous also, I have a great friend in Göteborg named Anders who's also a developer and when I first saw how quickly an Anders responded I thought it was him :)Bureaucrat
H
4

What about a combination. Create a temp computed for the subscribeables you need to read but do not want to subscribe to. Changing them would update the temp computed but that could be a cheap operation. Your real computed reads the tempComputed with peek accessing the currently cached value.

// this one is updated 
// if any of the subscribeables used in readSomeMoreObservables changes
// but that is hopefully cheap
var tempComputed = ko.computed(function() {
    readSomeMoreObservables();
});


var computed = ko.computed(function() {
    readSomeObservables(); //<-- if these change, notify computed

    // do not update on readSomeMoreObservables
    tempComputed.peek(); 
});
Hick answered 30/1, 2013 at 14:30 Comment(3)
Good idea, I tested that with teh fiddle from my answer, jsfiddle.net/uUXWv/3Melanous
Oops, posted a comment before I fully saw the implications of this solution. This is a very good idea - I added deferEvaluation to ensure invocation order stays the same: jsbin.com/etuxuq/1 Thanks!Bureaucrat
This is good idea, but for later visitors, @Lam has provided a more modern solution below.Wahkuna
L
6

For later visitors ...

Version 3.3 has made public ko.ignoreDependencies(callback, callbackTarget, callbackArgs). This is the method used internally by the binding handler processing to avoid creating dependancies from the init function call.

See http://www.knockmeout.net/2015/02/knockout-3-3-released.html

Lam answered 4/8, 2015 at 3:25 Comment(2)
What's the advantage over .peek() function?Derekderelict
OP discounted peek(). I think because peek() only ignores the initial observable, whereas ignoreDependencies creates a new scope surrounding the callback.Lam
H
4

What about a combination. Create a temp computed for the subscribeables you need to read but do not want to subscribe to. Changing them would update the temp computed but that could be a cheap operation. Your real computed reads the tempComputed with peek accessing the currently cached value.

// this one is updated 
// if any of the subscribeables used in readSomeMoreObservables changes
// but that is hopefully cheap
var tempComputed = ko.computed(function() {
    readSomeMoreObservables();
});


var computed = ko.computed(function() {
    readSomeObservables(); //<-- if these change, notify computed

    // do not update on readSomeMoreObservables
    tempComputed.peek(); 
});
Hick answered 30/1, 2013 at 14:30 Comment(3)
Good idea, I tested that with teh fiddle from my answer, jsfiddle.net/uUXWv/3Melanous
Oops, posted a comment before I fully saw the implications of this solution. This is a very good idea - I added deferEvaluation to ensure invocation order stays the same: jsbin.com/etuxuq/1 Thanks!Bureaucrat
This is good idea, but for later visitors, @Lam has provided a more modern solution below.Wahkuna
C
2

I'm a little late, but this is the solution I use when I am in this situation:

var computed = ko.computed(function() {
    var a = readSomeObservables(); //<-- if these change, notify computed
    var b;

    // capture any dependencies from readSomeMoreObservables
    // in a temporary computed, then immediately dispose
    // of it to release those captured dependencies
    ko.computed(function() { b = readSomeMoreObservables(); }).dispose();

    return a + b; // or whatever
});

This creates a temporary computed in which we call readSomeMoreObservables. The temporary computed sets up a new dependency capture frame and so all the observables read are captured in our temporary computed. We then immediately dispose of the temporary computed to release any dependencies it captured.

Cabotage answered 13/3, 2013 at 4:36 Comment(0)
H
1

Knockout's dependency detection has an ko.dependencyDetection.ignore function. If I understand that correctly you can use that to read the value of subscribeables without creating a dependency to them.

At least the following test runs:

it('Should not subscribe to subscribeables called by ignore', function() {


    var observableInner = ko.observable('initial'),
        observableOuter = ko.observable(),
        called = 0,
        computedInner = ko.computed(function() { return observableInner(); }),
        computedOuter = ko.computed(function() { 
            called += 1;
            // read dependend
            observableOuter();

            // read ignored
            var result = ko.dependencyDetection.ignore(computedInner, null)
            expect(result).toEqual('initial');

            return true;
        });

    expect(called).toEqual(1);

    // update the one we are depending on
    observableOuter(1);
    expect(called).toEqual(2);        

    // update the inner one which should trigger an update to computedInner but not to computedOuter
    observableInner('ignore');
    expect(called).toEqual(2);
});
Hick answered 30/1, 2013 at 13:31 Comment(2)
ko.dependencyDetection is a internal helper functionMelanous
Oh, I missed that one. You could create your "own build" of knockout exporting that property: ko.exportSymbol('dependencyDetection', ko.dependencyDetection);Hick
M
0

If you are not interested of new values to those observables, why not just move the reading of the observable outside of the computed scope?

http://jsfiddle.net/uUXWv/2

var observableTwoInitialState = this.observableTwo(); 

this.computed = ko.computed(function() {
    return this.observableOne() + observableTwoInitialState;
}, this);
Melanous answered 30/1, 2013 at 13:8 Comment(3)
My fiddle had a bug with the update of observable two try the latest oneMelanous
This does not address the question, as I do not control all of the code in the real scenario.Bureaucrat
ko does not support this out of the box, you do need to use peek and since you cant change the function readSomeMoreObservables that going to be hard.Melanous

© 2022 - 2024 — McMap. All rights reserved.