How does angular keep track of which $scope is related to which element? I have a few theories:
angular element keeps scope object as property
angular saves each scope in cache with the relationship with element
angular searches each ng-scope, and magically find element
For example, I have an element with $scope, and I want to change dom hierarchy of this element (moving to outside of the outer controller). How is scope affected by this action?
If you have an angular app, and your HTML looks something like this:
<div ng-controller="OuterController">
...
<div ng-controller="InnerController">
<div id="wrapper">
<span>An Element</span>
</div>
</div>
</div>
And you move the <span> out of the #wrapper div, it's still within the context of both OuterController and InnerController. However, if you move it up to where the ... is, only the OuterController's context applies.
Each controller has its own context, and that context extends down into all child-elements recursively, even if a new controller is present, at which point, both controllers are in-scope.
This is true whether the element is moved dynamically with Javascript or otherwise. Angular keeps track of most context internally.
Related
Learning Angular JS, trying a simple function where clicking an element hides its parent element.
My code:
<div class="form-section" ng-init="visible = true" ng-show="visible">
<a class="next" ng-click="$parent.visible = false">NEXT</a>
</div>
However, nothing happens when I click my <a> tag.
I based my code on this fiddle: http://jsfiddle.net/oxda3aes/
You don't need the $parent
What you're doing is creating a variable on the scope called visible and setting it to true.
So all you need to do to change the variable is call visible
$parent isn't referencing a parent node, it's referencing a parent scope.
<div class="form-section" ng-init="visible = true" ng-show="visible">
<a class="next" ng-click="visible = false">NEXT</a>
</div>
http://jsfiddle.net/oxda3aes/25/
This might help you see a little better what's happening:
http://jsfiddle.net/oxda3aes/26/
Here I declare the visible variable in the controller. Both accomplish the same task.
And here I have a function on the scope that gets called on click that does the same thing:
http://jsfiddle.net/oxda3aes/27/
$parent in angular does not mean parent element in HTML structure, but nested scopes relation. In mentioned by you example, ng-repeat creates child scopes for each it's iteration, and $parent in this case from this child scope points to its parent scope. In your case both HTML elements belong to the same scope, so, as was already answered just do not use $parent in your case.
When dealing with parent child scopes interactions in angular, it is always to use objects for your ng-models, rather then primitives (ngModel="obj.property" instead of just ngModel="property"), see very good article about scopes in angular https://github.com/angular/angular.js/wiki/Understanding-Scopes
Given the following situation with one parent directive and two child directives:
<div pane-list>
<div calc-pane></div>
<div user-pane></div>
</div>
I can use replace: true on the child directives, and then in the top level of their template <div ng-show="showPane">. This allows me to control whether or not the pane is showing from within the directives I use inside this list of panes - without replying on the parent pane-list directive having anything to do with it or needing to share scope. With replace: true I can use these panes inside any kind of parent element with no worries.
Apparently replace is being removed, so what can I do instead? I don't see any ways that aren't considerably more complicated. If I can't replace the pane element, I don't see a simple way to be able to show/hide it from within the directive.
Anybody have any ideas?
Could you please explain me one thing. Imagine we have directive called "myDirective". Here is html:
<div my-directive>
</div>
When we remove this div from DOM, will angular destroy scope of myDirective and its watchers or I have to listen the event "$destroy" on div DOM element and inside the listener to call scope.$destroy?
Usually when you remove a div from DOM, angularJS automatically deallocates events and destroys the scope, even if you don't listen to $destroy event.
So why you should use $destroy? You should use it to manually deallocate some non angularJS events and plugins (e.g. if you create a 'Kendo Grid' inside myDirective you need to destroy it inside the $destroy function using the plugin function to destroy it).
I suggest also to set all references to objects in the scope to null (best practice).
With Angularjs, you can bind an event to a dom element like this:
<div ng-controller="SampleController">
<a ng-click="showTheHiddenDiv()">Show the hidden div</a>
<div ng-show="showHiddenDiv">
hidden content
</div>
</div>
My question: is it possible to attach an event handler function like this from outside the controller, like this?
<a ng-click="showTheHiddenDiv()">Show the hidden div</a>
<div ng-controller="SampleController">
<div ng-show="showHiddenDiv">
hidden content
</div>
</div>
This does not work, I'm wondering if there is a special way to access the showTheHiddenDiv() function. I know i could just wrap everything in another container and make that the controller scope, but I'm wondering if its possible to do it this way.
Thanks!
Think bindings. You want to introduce a variable on the scope that the view (ng-click and inner div can bind to). It would make sense to make this variable a Boolean so that when it changes, it will update the views.
Start by creating a showDiv variable on the scope, and updating it when the anchor link is clicked:
<div ng-controller="SampleController">
Show the hidden div
<div ng-show="showDiv">
hidden content
</div>
</div>
When the link is clicked, it will toggle 'showDiv' which is saved on the scope of SampleController. Since, the ng-show directive on the inner div is bound to 'showDiv' (the same showDiv that is on the SampleController's scope), it will automatically show and hide based on the value of 'showDiv'.
[EDIT]
I failed to answer the original question, so I'll try again.
It is certainly possible to define a scope variable that is outside SampleController and bind it to the view within an inner controller:
Show the hidden div
<div ng-controller="SampleController">
<div ng-show="showDiv">
hidden content
</div>
</div>
This works because of scope inheritance. When you have scope inheritance, the child scope inherits the scope variables from the parent scope. In the above example, you have the scope of SampleController (the child scope) which inherits from the scope of the outside controller (the parent scope). If there is no parent controller, then the parent scope is just the root scope. The important point is that child scope inherits from parent scope, and inherits all its scope variables.
In the example above, ng-show is bound to showDiv, but it hasn't actually been assigned to a scope variable in any scope (yet). So initially, 'showDiv' is undefined but it remains hidden because in angular, undefined is treated as false when evaluated. Once you click the link, then showDiv is assigned !showDiv. It is at this point that the scope variable 'showDiv' is created, and since the assignment happens outside of SampleController, the scope variable is created in the parent scope - in this case, the root scope.
Since showDiv is bound to the same scope variable in both the anchor link and the ng-show directive, any changes to the scope variable 'showDiv' (such as clicking the link), will propagate to all views that are bound to it (the div that contains the hidden content).
You can always use a service and a directive to share a value between different scopes.
app.service('SharedService', function(){
this.showHiddenDiv = false;
});
app.directive('showDiv', function(SharedService){
return {
restrict: 'AC',
link : function(scope, element, attr) {
scope.showHiddenDiv = SharedService.showHiddenDiv;
scope.toggleDiv = function(){
SharedService.showHiddenDiv = SharedService.showHiddenDiv ? false : true;
}
}
}
})
html:
<a ng-click="toggleDiv()" show-div>Show the hidden div</a>
<div ng-controller="SampleController">
<div ng-show="showHiddenDiv" show-div>
hidden content
</div>
</div>
As a side note, any time you're trying to manipulate the DOM, you should be doing so in a directive, that's the angular way!
I got the following code:
<div class="map" ng-controller="DealerMarkerListCtrl">
<a ng-click="showdetails=!showdetails" href="#/dealer/{{marker.id}}" class="marker" style="left:{{marker.left}}px;top:{{marker.top}}px" ng-repeat="marker in dealer"></a>
</div>
and this view:
<div ng-show="showdetails" class="alldealermodal">
<div ng-view></div>
</div>
This same "ng-show" stuff is working properly with just one link outside of the ng-repeat but in this ng-repeat it isn't working.
The link shall open an overlay. The ng-view works too.
What am I missing?
Since ngRepeat creates a new scope the showdetails being referenced outside of your ng-repeat is not the same instance as the showdetails being updated in your repeated ng-click.
You can see this post for more details but one way around the new scope(s) is to bind to an object property instead of a primitive type.
This fiddle shows a small example binding to details.show instead of showdetails with:
$scope.details = { show: true };
"What am I missing?"
As #Gloopy already mentioned, you probably didn't realize that ng-repeat creates child scopes (one for each item). In addition, an understanding about how JavaScript prototypal inheritance works is also necessary because each child scope prototypically inherits from the same parent scope, and that affects how JavaScript finds (or creates) properties on scopes.
Note that a number of Angular built-in directives create child scopes: ng-repeat, ng-include, ng-switch, ng-controller, directives (can, but may not). For (much) more information about how prototypal inheritance works, why it is a problem when trying to bind to primitives, and how it relates to Angular scopes, see here.
To extend #Gloopy's answer, there are two other alternatives you could consider:
use $parent inside the ng-repeat to bind to the parent scope property (instead of creating child scope properties):
<a ng-click="$parent.showdetails=!$parent.showdetails" ...
define a method on the parent scope and call it, thereby also changing a parent scope property (rather than a child scope properties):
<a ng-click="toggleDetails()" ...