-
Notifications
You must be signed in to change notification settings - Fork 27.5k
controllerAs in directives doesn't play well with isolate scope #7635
Comments
Option 3: just reference them as Yes, I am aware that you have a bit of a problem when child scopes are in the picture, but there's a workaround for that anyways |
Firstly, it's inconsistent. Secondly, we lose all the cited benefits from Google's guide. |
You lose exactly 1) benefit: the ability to safely write the properties from a child scope without using $parent or similar. Anyways, lets see what other people think. @btford do you have an opinion on this? |
I'm keen on TypeScript and prefer the class style for controllers. In the methods code, it looks messy that some properties are accessed like Also this point is something I don't want to lose:
|
Here's a plunk to play with: http://plnkr.co/edit/ruwlP0eXOl3vxQbdqkSD |
Each time you click the button, with that service being used, you iterate through the scope traversal about 10 times --- Without it, you iterate through scope traversal about 3 times per click. (In the case of this particular plunker, we're actually checking watches 7 times per click with the service being used, and twice per click without it) Iterating through scope traversal/dirtychecking is not always cheap, it can be expensive when listeners (or watched expressions, for that matter) perform expensive operations Also important to note is that this plunker demo is a trivial example, whereas a real application would be hurt quite a lot more by this pattern. So don't do this to get around limitations of the compiler, it's not a very good idea As I said, we should get opinions from other folks about this to determine if it should be WONTFIX or not. I'm not opposed to adding support for this, but I feel like it's probably just going to make the compiler or directive api even crazier than it already is |
Sorry, but I just can't get how this code can significantly differ in terms of performance from what we have when using This fork of the plunk uses standard binding to the scope: http://plnkr.co/edit/7mWyOZIhjXklndFEHnkZ |
thorn0: the performance issue is hard to demonstrate with small plunkers like this, because you aren't showing a pathological case. The most pathological case would be where you are binding to non-const JSON literals with many properties doing non-trivial work processing their values, which is sometimes the case for ngClass or even ngOptions --- and you would expand on this pathological case by properly using the same comparison mechanism as the isolate scope (relying on angular.equals) --- doing this twice can potentially create model instability and end up calling the watchers multiple times, where they would be doing at least one, and possibly two angular.equals comparisons, after returning a value which would itself then be checked against the old value redundantly using angular.equals --- and it would do that not just for the isolate scope, but also for your service. So basically what this does is it just increases the workload of the dirtychecker significantly. Is the pathological case the most common? That depends on what kind of data is being used. You can obviously never have an assignable JSON literal, so model instability isn't going to happen. It's not as bad when only used for primitive values, but it's still extra work being done that really does not need to happen, increasing the dataset the dirtychecker deals with. |
Doesn't the same thing happen with the current implementation of bindings like |
@thorn0 yes, but setting up a redundant watch in a separate service compounds the issue, especially in the pathological case (which may or may not effect your application) |
This watcher might be redundant, but it's exactly what $compile does in the current implementation. My code of that service isn't really mine, it's basically a copy-paste from $compile, except that for the sake of code size I removed some logic that handles literal values. From the performance point of view, it just can't matter whether $watch is called from a custom service or from $compile. |
@thorn0 well it's not what $compile does, it's a little bit cheaper because you aren't using the special case for JSON literals, so you have some slight gains there, but lose the ability to deal with certain cases. |
Anyways, I don't want to have an argument about this. I think in most cases, it is not going to matter a whole lot to use the redundant watch, it's just that it can matter, is what I'm saying. It's not an ideal thing to have to do. If you have a lot of these, and they're doing real work, and digests are frequent, it can add up. I don't think it would necessarily be the end of the world to add builtin support for what you're asking, so that you don't have to add a redundant watch. My issue with it though, is that the solutions I'm imagining for this will either:
N'either of these seem particularly good to me, but I am not totally opposed to fixing them this way (more likely via option 2 --- but in order for that to happen, we need to decide if it's worth "fixing", so I would like to hear from other people, which may not happen this week. |
So if I bring that code back. the performance will be the same as that of the standard '=' bindings? If so, how is it possible that this performance is acceptable for the current implementation and in the same time it might be used as an objection to my proposal? That's what I'm trying to say. |
OK, I hear you. Let me cite @RichardLitt from #5076
Yes, it's an alternative way to use controllers. And if the framework supports it, this support should be consistent. That's my point. |
Okay @thorn0, I've taken a shot at putting together a "fix" for this, but I'm not totally confident it will make it into the tree. We'll see. |
Thank you! It's more complicated than I expected. Trying to wrap my head around it... As for syntax and the gross magic, what about something like this? {
controller: {
constructor: 'SomeCtrl',
as: 'someCtrl',
bind: {
"myData": "=someData",
"myString": "@someInterpolation",
"myExpr": "&someExpr"
}
},
scope: {},
...
} |
I've run into exactly this issue, and I agree - it would be nice to be able to use controllerAs consistently. |
Im not sure if this has been resolved, but my first thought was that they current scope {...} definition could just resolve on the scope eg: |
…ntrollerAs It is now possible to ask the $compiler's isolate scope property machinery to reference properties by their isolate scope. The current syntax is to prefix the scope name with a '@', like so: scope: { "myData": "=someData", "myString": "@someInterpolation", "myExpr": "&someExpr" }, controllerAs: "someCtrl", bindtoController: true The putting of properties within the context of the controller will only occur if controllerAs is used for an isolate scope with the `bindToController` property of the directive definition object set to `true`. Closes angular#7635 Closes angular#7645
…ntrollerAs It is now possible to ask the $compiler's isolate scope property machinery to reference properties by their isolate scope. The current syntax is to prefix the scope name with a '@', like so: scope: { "myData": "=someData", "myString": "@someInterpolation", "myExpr": "&someExpr" }, controllerAs: "someCtrl", bindtoController: true The putting of properties within the context of the controller will only occur if controllerAs is used for an isolate scope with the `bindToController` property of the directive definition object set to `true`. Closes angular#7635 Closes angular#7645
It is now possible to ask the $compiler's isolate scope property machinery to bind isolate scope properties to a controller rather than scope itself. This feature requires the use of controllerAs, so that the controller-bound properties may still be referenced from binding expressions in views. The current syntax is to prefix the scope name with a '@', like so: scope: { "myData": "=someData", "myString": "@someInterpolation", "myExpr": "&someExpr" }, controllerAs: "someCtrl", bindtoController: true The putting of properties within the context of the controller will only occur if controllerAs is used for an isolate scope with the `bindToController` property of the directive definition object set to `true`. Closes #7635 Closes #7645
It's not clear to me from this thread - so I just wanted to ask before moving to 1.3 rc1 to benefit from this. is the ControllerAs option supported on a directive and will it behave consistently with standard controllers? Is there a sample of the correct implementation? We have invested a lot of refactoring into following the "controller as vm" pattern, would be nice to leverage it for directives as well. |
@goleafs the only difference WRT compiler-instantiated controllers, is that you cannot return a value from the controller constructor. EG: // This will not work correctly, because the return value will be ignored...
function MyController($scope, $http) {
return new SomeOtherConstructor($scope, $http);
} The reason the return value is ignored, is because it's necessary to pre-allocate the object so that its isolate scope bindings can be populated before the constructor is invoked (sort of a convenience). Other than that, it is exactly the same as |
@thorn0 I haven't learned much about angularjs. I use controllerAs syntax this way in directive, so the services injected in the controller are there. :) angular.mudule('puppy').directive("faceDirective", [function() {
return {
scope: {},
controller: "LongLongDogNameController",
controllerAs: "mojo",
templateUrl: "a-puppy-face-template-tpl",
link: function(scope, elem, attrs) {
var ctrl = scope.mojo;
ctrl.viewService.showTeeth();
}
};
}]); |
@shrekuu - this worked for me. Thanks ! |
@shrekuu a bit late but I think link function always get the ctrl as the 4th parameter, there is no need to get it from scope. |
I'd like to use the 'controllerAs' option in my directives. Let me cite the reasoning from An AngularJS Style Guide for Closure Users at Google:
But I can see an issue with using this approach if the directive has isolate scope bindings.
In this case, the
bar
property is attached to the scope, not to the controller, which results in a confusing inconsistent situation where different properties should be looked for in different places.As for now I see two alternatives, both not appealing to me:
It might make sense to introduce another special character for the
scope
entries in directive definition objects. This character would mean that the given property is a property of the controller, not the scope. What about dot?The text was updated successfully, but these errors were encountered: