Conversation
5c9e22b to
992bc71
Compare
|
Interestingly, I ran into this very issue last week as well while turning on ko.options.deferUpdates in my app. I had distilled down a reproduction sample and was going to post but then saw the stackoverflow post and this pull request. I made a custom knockout build (3.4.2 modified with this pull request) and my reproduction sample no longer shows the problem - great! But, I am still seeing the problem in my app.... Before tracking down why my app still shows the problem, I want to ask.
Initially the value of CONDITION is true and, thus, the view has been rendered with the value THEN has. My question is: With this pull request in place, if obsv1 is changed in such a way that CONDITION will become false, is it possible for THEN be re-evaluated? Added: In other words, is it possible that the computed observable that knockout uses to wrap THEN in the text binding is "scheduled" to be updated before the computed observable that knockout uses to wrap CONDITION in the if binding? |
Yes. That's possible. But I would think with this change, that it would match the behavior of not setting deferUpdates. |
|
In my case/app, I didn't encounter the problem when not setting deferUpdates. With deferUpdates (and your change/pull request), I am still running into the problem in my "full" app. I have to track it down further, but it is as if THEN is re-evaluated before CONDITION is re-evaluated.
Can you quickly/briefly elaborate how/when that could happen - maybe it will help me track my problem down. |
https://jsfiddle.net/mbest/g5gvfb7x/12/ Is there a solution? Yes, use |
|
Thanks - but your examples don't set I.e., using This is what's happening in my app currently. I am still trying to distill it down to a simpler/simple sample. Btw, the CONDITION and THEN (computed) observables are all pure in my case. |
|
I've modified the example to use the latest build and |
|
@mbest, it looks like from your commit comment that you're directly looking for the if binding to evaluate before descendant bindings, but wouldn't it be more generalized to refresh bindings from a top-down approach (instead of what I think we're seeing here is a bottom-up approach)? I can imagine a very complex view model that has a very high top-level property which a whole network of child-level properties are bound to deep in a cascading call hierarchy (consider a top level with, with then a for: followed by a nested for, which might have a nested if..etc). if the top level change in the observable causes all the bottom-level bindings to re-evaluate going bottom up, it could be that the top level binding is changed in such a way that none of the child bindings even need to evaluate anymore (which I think is the case in the simple fiddle you supplied above). And i think this behavior doesn't depend on defered updates at all, it is just how the nested bindings are resolving themselves going from the inner-to-outer bindings. So is this just a matter of the order of binding evaluations, and also canceling the binding evaluations when the parent bidning causes a removal of the dom-binding of a child (such as the case in the if: binding)? |
|
The commit message isn't entirely accurate. It makes sure that when using deferred updates, the order of binding updates will match what you'd get without deferred updates. Actually there's currently no mechanism in Knockout to ensure that bindings on descendant nodes get updated after parent nodes. |
|
Thanks for the additional fiddle.
The important difference doesn't seem to be (just) Another factor coming into play seems to be the ordering of the DOM nodes in the html document. E.g., your last fiddle, http://jsfiddle.net/mbest/g5gvfb7x/15/ , slightly modified using generates an error. But simply re-ordering the two divs then things will work: Another instance of the ordering of DOM nodes is perhaps the following. Using your first fiddle, https://jsfiddle.net/mbest/g5gvfb7x/13/ , slightly modified using works. But changing to I am still trying to distill the issue down in my app. Tracing into the knockout sources I did notice that (in my scenario) the observable(s) in THEN is re-evaluated before the observable(s) in CONDITION. When CONDITION finally gets re-evaluated it of course properly removes its child nodes and disposes of the observables in the bindings in the child nodes, etc. Just wished/hoped that CONDITION was ahead of the THEN in the task queue of dirty observables... (or that the computed observable knockout creates for the THEN binding had a dependency on CONDITION, then perhaps CONDITION would be re-evaluated first, so when it comes time for the THEN observable it is either disposed or marked disposed so no re-evaluation is necessary.) |
|
Maybe the bottom line here is that the computeds should be assumed to be re-evaluated in any order, ignoring how they are being bound to in a hierarchy of the DOM. IE: you can't depend on an observable in a THEN part of an if: binding to not evaluate based on the condition of the IF. If the observable is set up in a dependency track, then all the dependent observables will be notified and evaluated. It just means that you will need to guard against nulls like so: I guess I'd like it to be more robust, but I think maybe the fundamental issue here is that the dependency tracking of observables is separate from the arrangement of the DOM. And with this separation, we can't do things like 'ignore these observables when a parent in the DOM is re-evaluated'. I like knockout for what it does, and so if part of the beauty of it comes from this separation from DOM arrangement and dependency tracking of observables, then I guess the cost of this separation is to santiy-check all evaluations and not depend on ordering in the dom. -Chris |
|
I think it would get cumbersome and error prone having to repeat the condition in the descendant bindings. And if you have nested
Intuitively, it feels like the bindings inside an But, rather than ensuring bindings on descendant nodes get updated after parent nodes, what if there was a mechanism to allow for a binding to indicate whether a child (or descendant) binding is soon-to-be-disposed. I don't know the intricate details of the knockout internals and consequences and implications of modifications, but something like this:
This change means that bindings that do their updates in an
The (intended) net effect would be that descendant bindings of |
|
Instead of
|
|
While trying to distill down this problem in my app, I came across some additional weirdness/surprises. My samples use a similar address scenario as OP, but slightly more complex and I am using @mbest's latest, pre-3.50 build. Set Address followed by Clear Address => no error. I didn't even need to add CONDITION to THEN for things to work.
Set Address followed by Clear Address => error (!). This was quite surprising - since the only change was removing stuff from the Interestingly, in sample 1 there was no need to have the I think it is quite weird and confusing, that even with this pull request applied: |
|
@fastfasterfastest Thanks for the additional information. I'll take a look and see what's going on. |
|
Thanks. I also noticed that sample 2 does not error if
|
|
It's not necessary to actually do anything with the http://jsfiddle.net/mbest/0u1qx24p/3/ The important change to the makeWithIfBinding('if', false /* isWith */, false /* isNot */,
function(bindingContext, dataValue) {
return bindingContext.createChildContext(function() {
return dataValue() && bindingContext.$data;
});
}
); |
|
Thanks! A couple of comments and questions...
Should that instead be
Do you mean you will not be able to make the modifications we are discussing here to the next or a future release of knockout? If so, why not? |
As you've noted, there are a number of compatibility problem with the simplified approach I took. The important thing to note is that if all descendant bindings have a dependency on the |
|
OK, good. I did some further modifications to your Instead of passing |
|
I quickly ran through the fiddles we have referred to above, and none of them generate any errors using the changes to |
|
@fastfasterfastest That might be exactly what we need. I'll do some more testing and let you know. |
|
A quick, initial test of my app also has no errors anymore, I need to do some more testing though. I don't know if you can simplify |
|
By the way, if I understand things correctly, there is no need to actually use the value of the |
|
@fastfasterfastest Good points. |
|
By the way, the same problem applies to the Set Address followed by Clear Address => error Perhaps this is little trickier to fix this since the One way that appears to work is to create an intermediary context after the Another way that appears to work, and avoid an intermediary context, is for the |
|
My main concern with the above changes is that it will add a dependency to every single descendant binding within an |
Regardless, I think 1 trumps over 2. |
I think this assumption is not correct. |
|
@mbest - here is another attempt. This approach uses the notion that the binding value, not the binding context, can have an "additional" dependency specified ("additional" in addition to whatever dependencies the binding "itself" creates). The additional dependency is stored in the binding context as The additional dependencies a binding value may have is automatically established by the internals by calling The The result is/should be that the descendant bindings only get re-evaluated when the actual value of the ancestor Here is a sample http://jsfiddle.net/fastfasterfastest/0u1qx24p/13/ I added display of timestamps and Note, this sample uses a modified version of knockout 3.4.2 (3.4.2 + 2226 pull request + my modifications), not your 3.5.0-pre-20170420/knockout-latest.debug.js - I had to create a "custom knockout build" since modifications were needed to the internals; if you do a diff against 3.4.2 you'll see that the modifications are rather minor:
|
|
I've been working on a fix. I'll post it in the next few days. |
|
See #2234 |
As described at http://stackoverflow.com/q/43341484/1287183 by Simon_Weaver:
I very often use the
ifbinding in knockout to hide something, with the added bonus that I don't need to worry about null reference errors inside theif. In this example ifaddress()is null then the whole block is removed so you avoid having to deal with null checking for every property. This would not be the case had I used thevisiblebinding.This is the simplest case above - and yes I would generally use this pattern with the
<!-- ko -->comment syntax.What is actually causing me problems is when I use a more complex
computedvalue and enable theko.options.deferUpdatesoption :The simplest implementation of this
computedobservable might be something like this :This all works great until I do the following:
ko.options.deferUpdates = truebefore creating the observables.address()will start off as null and everything is fineaddress()to{ street: '123 My Street' }. Again everything works fine.address()to null. I get a null error becauseaddress().streetis null :-(Here is a fiddle to illustrate the problem : https://jsfiddle.net/g5gvfb7x/2/
It seems that unfortunately due to the order in which the micro-tasks runs it tries to recalculate the
textbinding before theifbinding and so you still get a null error that normally wouldn't occur.