ng-click stops working when using ng-if - javascript

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

Related

Updating Controller Object Properties in the console

Not sure if this is possible, but I have some content with an ng-if that I want to test by updating a property on one of the controller's variables within the console.
<div class="manager-only" ng-if="teamSchedule.userObject.isManager">
<a href="#add-game">
<input class="btn btn-success" type="button" value="Add New Game" />
</a>
</div>
As you can probably surmise, the userObject has an isManager property that is a boolean. On page load, it's initially set to false. I'm trying to update this within the console to test the ng-if.
I found that I could add window.teamSchedule = teamSchedule; to the controller in order to have a reference to it and its variables within the console, and this does allow me to change the object properties of the userObject, it appears. However the ng-if does not seem to respond to the change.
I've tried changing the ng-if to an ng-show and that didn't seem to work either. Anyone have a way to make this work or know if it's not possible?
Thanks!
First explanation why it doesn't work.
You change the Object out of the AngularJs scope, so Angular doesn't realize that you changed something.
If you don't use production mode on your development system, you can use this to access the scope.
angular.element($0).scope()
$0 is the last element selected in the chrome element inspector.
Try to access your object this way, if this does not trigger the changes, try to use
scope.$apply(function() { your modification })

Hide parent element on click

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

Using class with angular vs ng-class while using a mixed expression

I have a div that I want to give a dynamic class with AngularJS.
The div is withing an ng-repeat statement where lang.prefix is first en then sv
Using the following code works and sets the class to i-flag-en than i-flag-sv, but is it correct?
<div class="float-left flag i-flag-{{lang.prefix}}"></div>
I know there exist a ng-class directive which can be used to dynamically set the class of an element with AngularJS.
I think I read somewhere in a book, that the normal class directive not should be used to set the class property dynamically with AngularJS because of the way Angular manipulates the dom.
However, the following code does not work:
<div class="float-left flag" ng-class="i-flag-{{lang.prefix}}"></div>
and I rather want to set the class in this way instead of creating another scope variable.
Is it bad practice to use the class attribute with AngularJS to dynamically set the class? Does it work all the time even if it would be bad practice?
The book you have mentioned may have talked about the problems of using ng-class and class {{}} interpolations together wherein updates in the interpolation removes the ng-class classes, this problem has already been resolved, reference. Thus, using interpolation within class attributes is totally fine, and does not break good practice because both has its own quirks.
Your usage of ng-class however is incorrect, you have to concatenate the literal string value with your scope string variable:
<div class="float-left flag" ng-class="'i-flag-' + lang.prefix"></div>
but I think it is much preferable to use your first example instead:
<div class="float-left flag i-flag-{{lang.prefix}}"></div>

angular js input bind value

I have an input like this :
<input ng-model="mysearch.myfield" id="myid"/>
that is bound to a filter
<table><tr ng-repeat="row in list|filter:mysearch">...</tr></table>
If I modify the input value in the GUI, it works perfectly, but if I try to modify its value via javascript/jquery
$("#myid").val("newvalue")
The input value is updated but the mysearch.myfield is not modified
Actually, I have a list that appears on user actions (it does not exist on page load):
<li onclick="changeTheInputValue('newvalue1')">newvalue1</li>
<li onclick="changeTheInputValue('newvalue2')">newvalue2</li>
...
with
function changeTheInputValue(v) {
$("#myid").val(v);
}
And it does not work when I click on an "li" (the input value is updated, ut the mysearch.myfield is not modified)
I also tried
<li ng-click="mysearch.myfield = 'newvalue1'">newvalue1</li>
<li ng-click="mysearch.myfield = 'newvalue2'">newvalue2</li>
...
but it does not work :(
Any idea ?
Thanks,
any javascript executing outside the angulars event loop won't be efective in angular untill you apply it. in order to do that you need to get the relevant scope and $apply the changes, since is not clear, how and why you are modifying the value outside the angular scope there is nothing much i can say except you could do something like
function changeTheInputValue(v) {
$("#myid").val(v);
angular.element($("#myid")).scope().$apply();
}
that should let angular know about the changes, how ever this is a bad design if using angular. there are far better ways to accomplish this same thing w/o having mixed execution scopes. (angular/the rest);
You are modifying the value of the input "#myid" using direct DOM manipulation. AngularJS is not aware of this. If you want both the html and the value of mysearch.myfield to update correctly, you must do so by modifying the mysearch.myfield property directly, either in a controller or via an ng-model binding or something similar.
The main reason this isn't working for you has to do with how AngularJS modifies the DOM. When you use jQuery to modify the DOM, you are circumventing angular. Angular has no way of knowing if you have changed something in the DOM except if you do it directly through Angular itself. In particular, if you are curious, read about the $compile and $digest services.
Hope this helps shed some light on the subject!

AngularJS - ng-repeat and ng-show

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()" ...

Categories