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
Related
I'm confused from angular transclude scope. I'm trying to make let say collapsible directive. But binding inside the transclude scope will not change model of parent unless I use some object for the model eg. data.
<div>
data.prop: {{data.prop}} <br>
prop: {{prop}}
<collapsible>
data.prop: <input type="text" ng-model="data.prop" /> <br> // WILL CHANGE PARENT
prop: <input type="text" ng-model="prop" /> // WONT CHANGE PARENT
</collapsible>
</div>
I already read this topic and still I don't get it why I must use prefix to the model.
Confused about Angularjs transcluded and isolate scopes & bindings
Working example at http://plnkr.co/edit/z3IvR1a37jdNRCJWG0Yq?p=preview
In my app I'm using object for forms, so it works fine but I just want to know why is that.
When you use an object to bind to the model, your object is passed to the different scope as an reference, not a copy, in Javascript objects are passed to functions as a reference. In that case it will still reference to previous scope.
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.
So I have a button called "show more" that will increase the number of items in a list once it reaches the maximum number of list I want to change to a button called "show less" which brings the list back to its default value.
I use an ng-if to determine which button to be shown and ng-click to determine the action. When using them together the ng-click stops working and nothing happens when I click.
here is the html I have written in jade, feedLimit is the number of items showing in the list.
button.btn.btn-primary.btn-block.btn-sm.btn-outline(type='button')(ng-if= "feedLimit < notifications.all.length", ng-click="feedLimit = feedLimit + 4")
span(translate) Show More
button.btn.btn-primary.btn-block.btn-sm.btn-outline(type='button')(ng-if= "feedLimit >= notifications.all.length", ng-click="feedLimit = 4")
span(translate) Show Less
I've tried using ng-show and ng-hide and they work fine but it is better to use ng-if, there is no animation and it is faster.
Here is the show more button's html output
<button type="button" ng-if="feedLimit < notifications.all.length" ng-click="feedLimit = feedLimit + 4" class="btn btn-primary btn-block btn-sm btn-outline ng-scope" style=""><span class="ng-scope">Show More</span></button>
I think your running into a common issue with angularjs and child scope inheritance.
You are data-binding to a primitive value and ng-if is creating a child scope. When your ng-click directive changes the value of 'feedLimit', you are actually creating a new 'feedLimit' property on the child scope but the ng-if directive is bound to the 'feedLimit' of the parent scope.
The solution is to avoid binding to a primitive value. Instead make sure you use dot-notation by binding to an object.
Instead of
<button ng-if="feedLimit < notifications.all.length" ng-click="feedLimit = feedLimit + 4">
try something like
<button ng-if="someObj.feedLimit < notifications.all.length" ng-click="someObj.feedLimit = someObj.feedLimit + 4">
Here is a more detailed explanation of whats going on.
https://github.com/angular/angular.js/wiki/Understanding-Scopes
Scope inheritance is normally straightforward, and you often don't
even need to know it is happening... until you try 2-way data binding
(i.e., form elements, ng-model) to a primitive (e.g., number, string,
boolean) defined on the parent scope from inside the child scope. It
doesn't work the way most people expect it should work. What happens
is that the child scope gets its own property that hides/shadows the
parent property of the same name. This is not something AngularJS is
doing – this is how JavaScript prototypal inheritance works. New
AngularJS developers often do not realize that ng-repeat, ng-switch,
ng-view and ng-include all create new child scopes, so the problem
often shows up when these directives are involved. (See this example
for a quick illustration of the problem.)
This issue with primitives can be easily avoided by following the
"best practice" of always have a '.' in your ng-models
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()" ...