I have two controllers, the second one prototypically inherits from the first.
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
</div>
</div>
The ParentCtrl has $scope.$broadcast while the ChildCtrl has a $scope.$on that receives from the broadcast.
I have then decided to make a directive, whose controller definition is the ChildCtrl.
<div ng-controller="ParentCtrl">
<div my-directive>
</div>
</div>
So far, so good, $broadcast/$on event still working. (Correct me if I am wrong, the directive does not have a scope of its own, right? Unless you set scope: true as directive property, which in this case, the directive prototypically inherits from the ParentCtrl?)
I decided then to isolate the scope of my directive. However, I am surprised to see that $on (in the directive controller) still receives the event by $broadcast (in ParentCtrl).
As I understand, this should not work anymore because $broadcast propagates events downward to descendants and in this scenario the parent-child scope relationship no longer applies because of the isolate scope. Can someone explain this? Thanks.
you where right until the part you assume the event won't work
the events are propagated to all the children, no matter the nesting level, so as long as you have a scope listening for a specific event and this event is $broadcasted that listener will be triggered if you are in need of triggering the same event and have a selective react then you might need to consider using event parameters to react differently
Isolating the scope is done for a different reason than what you mention. It's not to cut the child scope free from the parent scope.
It's to keep the child scope separate from other child scopes on the same level. The following example is where isolating the scope would really shine:
<div ng-controller="ParentCtrl">
<div my-directive></div>
<div my-directive></div>
<div my-directive></div>
</div>
In order to keep the my-directives from freely using the same scope variable between each other, you need to isolate their scope.
Basically, the child scope and the parent scope are still linked. As a further example to demonstrate that they are still connected, you should try executing a function in an isolated child scope, and defining that function in the parent scope. It should still run in theory.
Related
I am trying to make a transitive transclusion, or call it “directive inception”.
I made this example to illustrate what I am trying to do:
http://plnkr.co/edit/0hFFHknDps2krtK1D9ud?p=preview
The directive “first” wraps the directive “second” in its template and the two of them use transclusion.
What I want to do is to bind a value from a controller to the html that is a child of the “first” directive.
So I wanted my example to display:
<h1>Chained transclusions test</h1>
<div>
<h2>First directive</h2>
<div>
<h2>Second directive</h2>
<div>Controller hello</div>
</div>
</div>
Obviously that is not what I got.
I tried to analyze the scope with the developer tool and I was surprised by the result scope tree:
the result trees
I thought angularJS would create a new scope when using the transclude feature in a directive. And that this scope would be a non isolate sibling of my directive isolate scope. But I cannot see any sibling of my first directive scope (although it uses transclude). Plus, every children of my “first” directive has a scope isolated from the controller scope since the “first” directive scope is an isolated one.
I don’t understand the behavior here.
Is the transclude inclusion completely forbidden in angularJS ?
Is it possible to create a directive with transclusion, that wraps another directive that uses transclusion ?
It seems to me that this is the whole power behind web components, the fact that transclusion or any other special caracteristics should be seen as “implementation detail”, and the component should be able to use other directives that hide their own implementation details.
Without getting into the details of scope creation when using isolate scope with transclusion... it is possible to nest transclusions, but in your example, you need to make scope.controllerMsg available to the first directive's isolate scope:
JS:
app.directive('first', function(){
return {
...
scope: { controllerMsg: '=text'},
...
}
});
HTML:
<first text="controllerMsg">
{{controllerMsg}}
</first>
Demo
I have read in different places that it is important that you always use objects in your scope but I haven't found a definitive answer as to why that is. Can someone please help me?
I like Ryan Q's answer but after doing some more research I wanted to add an answer with a little more emphasis on javascript's prototypal inheritance.
The issue here is an object vs. primitive issue (pass by reference and pass by value) and how Javascript’s prototypal inheritance works.
When a javascript class inherits from a parent class, it copies the values over into the child class. There are two types of values that could be copied over, objects or primitives.
When an object is copied in through inheritance, it is passed by reference. This means that any updates I make in my child object will be seen in the parent object as well.
When a primitive is copied in through inheritance, it is passed by value. This means that any updates will NOT be seen in the parent class.
What does this have to do with Angular scopes? When we create a directive, a scope will be created for this directive and we can declare it to be an isolated scope or an inherited scope. If it is an inherited scope, it will inherit its parent’s scope items. Now, if I have primitive values in my parent scope, I will be inheriting them into my child scope as pass by value. Which means when I make a change in my parent scope, it won’t be seen in my child scope, and vice versa. Now I could have the same inherited variable in my child and parent scope with different values. This is going to lead to confusion…. And probably anger…lol.
So if you just use an object, then this problem will not happen. And that is why you should always objects in scopes.
Take a look at this:
https://github.com/angular/angular.js/wiki/Understanding-Scopes
and
Does my ng-model really need to have a dot to avoid child $scope problems?
Lets say you have 3 controllers, a main control, and two others which inherit from the main control.
Your html might look like this.
<div ng-controller="MainCtrl">
<div ng-show="helloWorld">Hello World</div>
<div ng-controller="Sub1Ctrl">
<button type="button" ng-click="helloWorld = false">Hide Hello World</button>
</div>
<div ng-controller="Sub2Ctrl">
<button type="button" ng-click="helloWorld = false">Hide Hello World</button>
</div>
</div>
Your Main Controller
angular.module('MyModule').controller('MainCtrl', function( $scope ){
$scope.helloWorld = true;
});
All is fine and dandy and your hello world element shows as expected. Now try clicking one of those buttons which sets helloWorld to false. Your Sub1Ctrl will now look like this:
angular.module('MyModule').controller('Sub1Ctrl', function( $scope ){
$scope.helloWorld = false;
});
But your MainCtrl will still be
angular.module('MyModule').controller('MainCtrl', function( $scope ){
$scope.helloWorld = true;
});
Why is that? Well because when Angular evaluates "helloWorld = false" it sets $scope["helloWorld"] = false internally. The issue is because your setting it on the lower Sub1Ctrl, the higher controller is never set.
If you change your Html and MainCtrl to this:
<div ng-controller="MainCtrl">
<div ng-show="myModel.helloWorld">Hello World</div>
<div ng-controller="Sub1Ctrl">
<button type="button" ng-click="myModel.helloWorld = false">Hide Hello World</button>
</div>
<div ng-controller="Sub2Ctrl">
<button type="button" ng-click="myModel.helloWorld = false">Hide Hello World</button>
</div>
</div>
angular.module('MyModule').controller('MainCtrl', function( $scope ){
$scope.myModel = {
helloWorld: true
}
});
Then Angular will look up and see if the object exists in the scope's prototype (which is how controllers inherit through the Scope Hierarchy) before setting the value.
So now Angular evaluates "myModel.helloWorld" on the Sub1Ctrl which ends up finding "myModel" on the parent MainCtrl and so properly sets the helloWorld property to false.
Because then you keep the data in one place. A child scope (which is not an isolated scope) can shadow a property "message" for example. So the parent scope and the child scope have a "message" property. But if the parent scope has "data.message" property, the child scope cannot shadow this, unless first a property "data" is created in the child scope, and then "data.message", but this is not what AngularJS does. But AngularJS indeed creates first "data" and then "data.message" in the parent scope.
I'm wondering if its possible to prevent a child div within a nested div inheriting its parent controller.
<div id="parentDiv" ng-controller="parentCtrl">
<div id="childDiv" ng-controller="childCtrl">
</div>
</div>
Imagine I have a scope variable called name, how could I ensure that name is not accessible in side childDiv.
See plunk.
It's not problematic to have a child div accessing the variables of its parent controller but it just doesnt feel right. What about naming collisions on so on?
I do not believe there is a way to hide variables of a child scope from the parent scope with respect to angular controllers. The scope of the parent is always available to the scope of the children. I do have two points that may help.
1) Naming collision may not be a problem as you would expect. If you set $scope.name in the childCtrl to "testChild", the name variable's value parentCtrl is still "testParent". The child $scope has access to the parent but does not overwrite/share variables, only objects.
2) If you are worried about naming collision or confusion, I would recommend placing an object on scope and adding properties to that object. Example: $scope.parentData = { name = "testParent"} and $scope.childData = { name = "testChild"}
I have a directive element. Within it I have a form that I want to set some of the directive's scope properties. Like so:
<trans-dir>
<form role='form' class='form-inline'>
<div class='form-group'>
Select Value:
<label>
<input type='radio' ng-model='data' value='Val1'>Val1
</label>
<label>
<input type='radio' ng-model='data' value='Val2'>Val2
</label>
</div>
</form>
</trans-dir>
I'm baffled by how the scopes work (even though I've read a bunch about it):
Parent controller scope: 03
Directive controller: 04
Directive postLink: 04
Directive transcludeFn: 08
FORM scope: 05 <== Huh?
So, I want the form to set the directive controller's scope. If it were the same scope, that would be fine. If it were the transcluded scope (08) I could then access it. No problem. But it's yet another scope - 05. I can traverse to it using trasnscludedScope.$$prevSibling, but I don't really know what's going on and I don't know if that is legit and deterministic.
More detailed code at: http://plnkr.co/edit/z70R6W?p=preview
So my questions:
How do I access the FORM scope?
What's going on with these scopes? Why is my form scope different from my transcluded scope?
Bonus: Where are scopes 6 & 7?? (In my real project I have tons of scopes I don't know the source of, and batarang always says they have no model, which can't always be true.)
If you use ng-transclude, you don't need to call transcludeFn manually. When you call it manually, you're creating the #8 scope. It is called automatically by ng-transclude to create #5. So, the form scope is the transcluded scope, just not the one that you're creating manually (that's why it doesn't have the $watch set on it). I'm not sure if there's a way to access the auto-generated transclude scope created by ng-transclude. You could, however, not use ng-transclude, and instead make your manual call to transcludeFn (like you are already doing) and manually append the cloned element to your DOM.
Your directives will inherit their parent scope if one isn't declared, so removing that will simplify this.
Also, one of those scope gotchas about angular:
If you set a "plain variable" in a child that inherits scope from a parent, it WON'T propagate back up — it will have its own variable instantiated and updated.
$scope.plainString = "derp";
// child will inherit this, but if it gets set in child it will be
// instantiated there as its own variable and won't affect the parent
However, if there is a query as part of that, the variable will be changed in the parent.
$scope.objToQuery = {
value: "derp"
};
// child will inherit this, and when it gets changed there
// e.g. ng-model="objToQuery.value"
// it WILL be changed in the parent
This object that you "query" must exist in the parent scope.
Check out this modified plunkr: http://plnkr.co/edit/SftstP7L2OJnfLka8OLd?p=preview
Does this help?
In the video AngularJS MTV Meetup: Best Practices (2012/12/11), Miško explains "..if you use ng-model there has to be a dot somewhere. If you don't have a dot, you're doing it wrong.."
However, the very first example (The Basics) in the Angular.JS website seems to contradict it. What gives? Has Angular.JS changed since the MTV meetup that it's now more forgiving with ng-model?
That little dot is very important when dealing with the complexities of scope inheritance.
The egghead.io video "The Dot" has a really good overview, as does this very popular stack overflow question: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
I'll try to summarize it here:
Angular.js uses scope inheriting to allow a child scope (such as a child controller) to see the properties of the parent scope. So, let's say you had a setup like:
<div ng-controller="ParentCtrl">
<input type="text" ng-model="foo"/>
<div ng-controller="ChildCtrl">
<input type="text" ng-model="foo"/>
</div>
</div>
(Play along on a JSFiddle)
At first, if you started the app, and typed into the parent input, the child would update to reflect it.
However, if you edit the child scope, the connection to the parent is now broken, and the two no longer sync up. On the other hand, if you use ng-model="baz.bar", then the link will remain.
The reason this happens is because the child scope uses prototypical inheritance to look up the value, so as long as it never gets set on the child, then it will defer to the parent scope. But, once it's set, it no longer looks up the parent.
When you use an object (baz) instead, nothing ever gets set on the child scope, and the inheritance remains.
For more in-depth details, check out the StackOverflow answer
Dot will be required when you prototypically inherit one scope from another for example in case of ng-repeat a new scope is created for every line item which prototypically inherits from parent scope. In the basic example there is no prototype inheritance since there is only one scope but if you have a number of child scopes then you will start facing the problem. The link below will make everything clear.
https://github.com/angular/angular.js/wiki/Understanding-Scopes#ng-repeat
So to solve this, make sure in the JS you declare the parent first:
e.g.
$scope.parent
followed by:
$scope.parent.child = "Imma child";
doing just the child without the parent will break Angular.
According to #OverZealous's answer, I thought up a dirty but comfortably simple and quick workaround for this:
$scope.$s = $scope
$scope.foo = 'hello'
Then use $s in template can safely modify model:
<input ng-model="$s.foo"/>
I wrote a service for such dirty works in my project.