-
Notifications
You must be signed in to change notification settings - Fork 27.4k
feat($compile): add partialDigest property in directive definition #8055
Conversation
Thanks for the PR! Please check the items below to help us merge this faster. See the contributing docs for more information.
If you need to make changes to your pull request, you can update the commit with Thanks again for your help! |
@vojtajina @kseamon @lgalfaso @caitp please take a look. it's not perfect but I think it's a decent try that is useful in some scenarios. |
We are still missing a way to trigger global digest from a partial digest if needed. |
I think it's cool, but I feel like usually the author of a template would want to have control over this, instead of having to fight with design choices of (likely 3rd party) directives. I will look at the tests and implementation closer in the morning |
A directive asking for a child or isolated scope can now ask for that scope to have partialDigest, making any digest triggered from inside the directive only do dirty-checking for that scope and its descendants.
The goal of this is to localize digests triggered from a component to only that component. So e.g. ng-model and all event directives would then digest locally. (there is an issue with debounced ng-model because it uses $timeout but we can deal with that later) The author of the template where this partially digested component lives should not make this decision - the decision should be localized to the component because it's the component that knows about how it changes the state. |
The component really has no idea whether the same data is being used outside of the hierarchy. Only the author calling the directive would know that (under the assumption that they're different people). What this means is, under certain cases you end up fighting with a 3rd party package to make sure this setting doesn''t prevent bindings from being updated in your app, or you end up fighting a legacy package to make sure that it can and does work for that. An author of a component really has no clue how transcluded ng-models (or whatever) need to behave. |
An alternative solution which put this in the hands of application template authors (rightfully, I believe), would be a new attribute which could be checked for when deciding to create, or not to create, a new scope. Suppose, if The implementation ought to be simpler, and fewer tests should be broken with a strategy like that. It's up to you guys, but in my opinion, that would work a lot better. |
Only the component knows what it is modifying. So if a component is modifying local state then it can use this option. If it's modifying state that came from outside of the component then it either has to trigger global digest or not opt into the local digest. The component user has no idea about how the component will use the input (if any) and whether it has internal state changes which would benefit from local digest. |
That assumption doesn't hold for transcluded content |
Aha! Now that's different. We do need to take that into account. On Wed, Jul 2, 2014, 8:28 PM Caitlin Potter notifications@github.com
|
@@ -2167,6 +2167,71 @@ describe('$compile', function() { | |||
}); | |||
}); | |||
}); | |||
|
|||
ddescribe('partialDigest', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be describe
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is very much work in progress and intended primarily to explore the
possibilities.
On Jul 2, 2014 9:12 PM, "Jeff Hubbard" notifications@github.com wrote:
In test/ng/compileSpec.js:
@@ -2167,6 +2167,71 @@ describe('$compile', function() {
});
});
});
+
- ddescribe('partialDigest', function () {
Shouldn't this be describe?
—
Reply to this email directly or view it on GitHub
https://github.com/angular/angular.js/pull/8055/files#r14496326.
There are some weirdnesses in trying to block a |
@lgalfaso if a component is propagating changes outwards it should not opt into the partial digest. |
I was thinking more about the transclusion issue and I came to conclusion that if instead of a simple flag, we'd actually point to the nearest scope on which a partial digest should be applied then transclusion would not be a problem and triggering digest from within a transcluded view would result in a digest targeting the digest target scope of the scope from which the transclusion inherits. So given this scope tree:
where
So if a digest is triggered from the transclusion, a full digest would happen, but if the digest is triggered from |
You are right, but it is possible to write a case where this is happens indirectly. Say there are the following directives
<div ng-init="foo = {bar: 'baz'}">
<directive-with-partial-digest-no-isolated-scope>
<directive-with-two-way-binding man="foo.bar">
<button click-that-does-partial-digest="man='go'" />
</directive-with-two-way-binding>
</directive-with-partial-digest-no-isolated-scope>
{{foo.bar}}
</div> |
What about the situation where you map an object (rather than a string or number) to the isolated scope?
In this case if you change a property on the object, from within the isolated scope it will change in the parent scope, by the fact that both the isolated and parent scopes reference the same object.
|
Regarding transclusion: the DOM hierarchy does not map 1-1 to the scope hierarchy. DOM:
SCOPE:
The So we do have a situation where the DOM and the Scope hierarchy don't directly match but we do know that for some scope and DOM association (S, E), the scopes (S', S'') of all ancestor DOM elements (E', E'', ...), can never be descendent scopes of the original scope (S). |
btw @petebacondarwin is right about the tree structure. |
@@ -170,7 +171,7 @@ function $RootScopeProvider(){ | |||
* @returns {Object} The newly created child scope. | |||
* | |||
*/ | |||
$new: function(isolate) { | |||
$new: function(isolate, partialDigest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer one argument with named flags (e.g. scope.$new({isolate: true, partialDigest: false})
), as calling scope.$new(true, false)
is hard to read...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, if we are going to make a (fairly major) breaking change
I am thinking about the following use case: One solution that came to my mind: Make the For the use case above we would have:
A controller like this:
And a template with:
|
$rootScope.$digest(); | ||
var scope = this; | ||
while(!scope.$$digestTarget) scope = scope.$parent; | ||
scope.$digest(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my proposal: This is the place where we should evaluate the $$digestTarget
expression:
```if (scope.$$digestTarget && !scope.$eval(scope.$$digestTarget)) scope = scope.$parent; ...`
Here is a plunker for the example above (a little bit nicer): http://plnkr.co/edit/5wlwHtkyxjRvsiz8VyXK?p=preview Just talked to @IgorMinar and concluded that we should also add a check that shows a warning when an isolate directive uses This check should only be executed when the app is run in batarang or protractor. Allowing protractor to check this as well is actually better than just checking in bataran, as this check really needs to run the app to detect the error (@btford I think this is also a good idea for the |
After sleeping over it, the expression in
So I would vote for a |
When a scope has partialDigest, $apply-ing the scope only triggers the dirty checking in that scope and its descendants BREAKING CHANGE: Scope.$apply now requires its `this` to be a given scope. Replace any calls similar to ```js expect($scope.$apply).toThrow('...') ``` with ```js expect(function () { $scope.$apply(); }).toThrow('...'); ``` Also, with the introduction of partialDigest, `Scope.$new` now takes an object for its options instead of a boolean. Before: ```js $scope.$new(true); ``` After: ```js $scope.$new({isolate: true}); ``` Passing a parameter is still optional
A directive asking for a child or isolated scope can now ask for that scope to have partialDigest, making any digest triggered from inside the directive only do dirty-checking for that scope and its descendants.
we should also add a test for triggering full digest from local digest - I think we'll uncover that that doesn't work because of digest in progress error. this is something that we must address and tobias's comments above tried to tackle that. |
This PR doesn't look quite right to me though. Has @btford tried to use this on material components? I don't think that he did. |
btw while working on the slides for this feature I realized that "local digest" might be a better name. what do you think? |
Local apply probably - $digest is already local to the scope in question. On Jul 31, 2014, at 2:17 AM, Igor Minar notifications@github.com wrote:
|
+1 Karl |
…be called This is useful when combined with partialDigest. Before this, $timeout was only able to call $apply on the $rootScope.
… be called This is useful when combined with partialDigest. Before this, $interval was only able to call $apply on the $rootScope.
So the remaining unanswered questions are:
** I'm not sure how true this still is, considering partialDigest can only be turned on for directives with isolated scopes now. |
I think |
app.js
index.html
|
We are going to close this issue.
To summarize, this would be doable and some use cases would benefit, but its not such a huge win as we hoped it could be. |
No description provided.