Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

New properties are not set on an isolate scope? #5112

Closed
aliakhtar opened this issue Nov 25, 2013 · 28 comments
Closed

New properties are not set on an isolate scope? #5112

aliakhtar opened this issue Nov 25, 2013 · 28 comments

Comments

@aliakhtar
Copy link

If I have a directive with an isolate scope, and I wanted to set a new property on that isolate scope within the controller of that directive, those properties seem to not get set. Why is that?

I use isolate scope only because there's no other way to pass callbacks / functions to a directive. But now I'm finding that I can't set any properties on the isolate scope, other than what I've defined.

Please advise..

@caitp
Copy link
Contributor

caitp commented Nov 25, 2013

You can "pass callbacks" to directives without isolate scope. ngEventDirs (eg ng-click) provides a simple example of this: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js#L45 --- by using $parse to build the expression which gets evaluated.

So, your directives can do the same thing, if it makes sense to. There's a time and place to use an isolate scope.

To my knowledge though, a directive with an isolate scope's controller will actually get the isolate scope, and not the child scope. If it doesn't, you can get the isolate scope with $element.isolateScope() (if you inject $element), so it is technically doable


I guess it is doable without element.isolateScope() hacks, that's good


Unless I'm misunderstanding your issue... But what I think you're saying is that you're having trouble assigning a property to the isolate scope from a directive's (with an isolate scope's) controller

@aliakhtar
Copy link
Author

@caitp Thanks for the reply, I'm simply trying to set a new property on the scope, in the directive's controller. If I set: $scope.foo = "bar" from within the directive's controller, then, in the template of the directive, if I try to print {{foo}}, it doesn't produce any output. However if I set $scope.$parent.foo = "bar" in the same place, then the {{foo}} line works.

If I remove the isolate scope, and then set $scope.foo = "bar", that works as well. But putting the isolate scope back in makes it ignore any additional properties that I set. I don't see the reason why, because it makes things very inconvinient.

@caitp
Copy link
Contributor

caitp commented Nov 25, 2013

If "foo" is defined in the isolate scope, I think you're going to have an issue with that (because it will be overwritten every digest cycle as the expression it's bound to is evaluated again). I might actually be wrong about that, but definitely you can reference properties which aren't declared in the isolate scope object from your directives controller (example)


Yeah, so the reason you can't change properties defined in the isolate scope object (the scope property of the directive definition) can be seen at https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L1417, which is basically overwriting values that you change in them.

You could work around it by changing the attributes themselves, or deleting the observers/watchers from scope.

@aliakhtar
Copy link
Author

@caitp Please see this fork of your plunkr:

http://plnkr.co/edit/lTCRrnWtkESiNwpPP3Ph?p=preview

Here, I'm setting a new property on $scope called foo, but its not being displayed when I try to output it via {{foo}}

If you delete the isolate scope from the directive and refresh it, then you will see the value of foo being output.

Is this expected behavior?

@aliakhtar
Copy link
Author

Sorry, correct url: http://plnkr.co/edit/lTCRrnWtkESiNwpPP3Ph?p=preview

@caitp
Copy link
Contributor

caitp commented Nov 25, 2013

Yeah, it doesn't work that way --- the isolate scope applies only to the directive (and its template, if it has one), not to child nodes

@aliakhtar
Copy link
Author

What are you referring to as the child node here?

@caitp
Copy link
Contributor

caitp commented Nov 25, 2013

<div iso-box="true">foo: {{foo}}</div>

in 1.2, the text node will be watched using the parent scope, not the directive's isolate scope

@aliakhtar
Copy link
Author

Why is that? Clearly, it should fall under the directive's scope since its included in the directive's parent element. Using the parent's scope is very unintuitive. Also, it breaks code written with previous versions.

@caitp
Copy link
Contributor

caitp commented Nov 25, 2013

The directive's scope is "isolate", it is separate from the scope hierarchy (child nodes apart from the directive's template do not descend from/inherit/receive it). d0efd5e and 909cabd --- You can read the commit messages for the justification behind the change, it is quite reasonable.

@aliakhtar
Copy link
Author

Intuitively, if you include a directive as an attribute, e.g:

<div something>

then you would expect all the child elements of that div to be included in the something directive's isolate scope. Otherwise, there doesn't seem to be any reason to support attribute based directives.

@Narretz
Copy link
Contributor

Narretz commented Nov 25, 2013

Imo, it was a good idea to make isolate scopes truly isolate, but I didn't really understand the need to exclude child nodes that are not defined in the template from getting the isolate scope. I mean how could the devs know that "In theory, nobody should rely on this behavior, as it is very rare - in most cases the isolate directive has a template"?

@aliakhtar
Copy link
Author

@Narretz I agree with you. The very point of allowing directives as attributes on existing elements (e.g divs) means that you should be able to access the directive's scope within that element.

For example, I have an 'ajaxForm' directive, which I set on any normal <form> in order to have that form submitted via ajax. Within the <form> and </form> tags I put in the HTML code of the form fields, which are different for every form, so I can't just use a single template for the directive. However now, because of this change, I can't access the directive's scope within the child nodes of the form.

@caitp
Copy link
Contributor

caitp commented Nov 25, 2013

@Narretz: There is no reason why someone couldn't simply use a child scope, if they want to use it like that. I know isolate scopes look "convenient" (and they are, in a sense), but it's not necessary to use those conveniences, you have other options (as I've pointed out in examples).

So you aren't stuck using an isolate scope, and you should really only use it when it's appropriate.

Have you thought about using an isolate scope + transclusion to keep your child nodes intact? (they wouldn't be able to see the isolate scope, but they wouldn't have to be thrown away)

@aliakhtar
Copy link
Author

@caitp I am only using the isolated scope where appropriate in my ajaxForm directive example given above. Also, I've updated my plunkr to use transclusion here:

http://plnkr.co/edit/lTCRrnWtkESiNwpPP3Ph?p=preview

But, its still unable to access scope.foo being set from the directive.

I don't see why the scope of the directive should not be accessible to the child elements of the parent element which has the directive as an attribute. If you set ng-controller as an attribute on any element, all the children of that element do get the controller's scope. Its only consistent and intuitive to keep the same behavior for directives.

@caitp
Copy link
Contributor

caitp commented Nov 25, 2013

no, transclusion will keep your child nodes, but they will not have access to the isolate scope --- they reference the parent scope. (using ng-transclude in your template will let you reference the isolate scope without discarding child nodes that you want to keep)

you need to either create a child scope rather than an isolate scope, or put every reference to your isolate properties in a template.


Anyways, the issue tracker isn't really the place for these kinds of support questsions, you should ask on the mailing list or on #angularjs on irc.freenode.net to get answers to support questions.

Everything here is covered in the BREAKING CHANGES in the changelog, so please consult with that as well

@aliakhtar
Copy link
Author

But that's bad design, and should be changed for the reasons I gave above.

@aliakhtar
Copy link
Author

Also I can't use a single template when each form that I want to use my directive on, has different fields.

@caitp
Copy link
Contributor

caitp commented Nov 25, 2013

you can dynamically compile templates, you can simply use a child scope rather than an isolate scope --- you are not limited here, there are lots of things you can do.

@aliakhtar
Copy link
Author

But I shouldn't have to do these complicated workarounds for what should work intuitively, i.e if you set a directive as an attribute on an element, the children of that element should fall under the directive's scope, just as if you do ng-controller on an element, its children all get the controller's scope.

@caitp
Copy link
Contributor

caitp commented Nov 25, 2013

http://plnkr.co/edit/zW1RCU00fbc6wIDLNotX this works just fine (despite creating an extra child scope), it's not all hopeless

Anyways, an isolate scope must be isolate --- it should not leak into other directives, or child nodes, that's not what it is meant to do. You can tell by the name --- "isolate" is clearly meant to not leak around. It did prior to 1.2, but this was not intentional afaik.

Again, this isn't really an appropriate place for support questions --- if you aren't happy with it, it would be good to propose improvements on the google group or on the IRC channel.

just as if you do ng-controller on an element, its children all get the controller's scope.

ng-controller creates a child scope, not an isolate scope.

@petebacondarwin
Copy link
Member

Isolate scopes are special and in general it is expected that their children are going to be transcluded into a template.

As such it is slightly fiddly to get what you want working - but only very slightly! You just use the fact that template can be a function.

Here is a fiddle: http://jsfiddle.net/NTuS6/

@Narretz
Copy link
Contributor

Narretz commented Nov 26, 2013

Interesting. I guess stuff like "in general it is expected that their children are going to be transcluded into a template" should go in a doc section called "directive examples and best practices". I have the feeling that what was intended by the devs for directives is often not how people use the mechanics.

@wtfribley
Copy link

I'm a little confused about the "children are going to be transcluded into a template" bit... I've updated the fiddle from @petebacondarwin to use a template + transclusion and still the children don't have access to the isolate scope. I'm surely missing something... what it is?

http://jsfiddle.net/uUkt8/

@joshkurz
Copy link
Contributor

joshkurz commented Dec 4, 2013

@wtfribley what is going here is that you are transcluding the contents of the parent element to the new template.
This means that the original bindings from the parent scope are still "intact". So the {{isolatedProp}} variable is technically still a part of the parent scope. Some people say its the fourth scope option. You cannot write to the transcluded scope in an isolate scope unless you have activated two way data binding, because its like writing the parent. Child scopes cannot write to the transcluded parent scope either.

http://jsfiddle.net/uUkt8/3/

Here we are writing to the transcluded template, but only because we are overriding the original scope that the transcludeFn is bounded too.

http://jsfiddle.net/uUkt8/6/

@wtfribley
Copy link

@joshkurz thanks! Yeah, after looking a little harder at @caitp's example, I was able to wrap my head around the problem and override the scope in the compile function (I guess I could do it in the linking function too, as per your example).

@btford
Copy link
Contributor

btford commented Dec 19, 2013

This seems to have been resolved, so I'm closing it.

It'd be nice to clarify the points brought up this issue in the AngularJS docs. Any takers? :)

@btford btford closed this as completed Dec 19, 2013
@schellmax
Copy link

was quite confused about this one, too. thanks for all of the clarifications here, i'm using the 'template function' approach by @petebacondarwin and am happy with it.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants