performance of angularjs ng-click inside ng-repeat - javascript

It's more a suspicion than an verified problem but..
I've worked with knockoutjs for a while and there it was a performance issue to create lots of ko click bindings - the better way was to use much fewer jQuery .on('click', ...) to handle these.
Now that I'm diving into angularjs I have a ng-repeat within ng-repeat and inside this second one I have a few buttons with ng-click..
<ul>
<li ng-repeat="el in collection">
<button ng-click="someFn()">click me</button>
<button ng-click="someFn2()">click me</button>
<button ng-click="someFn3(el)">click me</button>
</li>
</ul>
Doesn't this create a lot of click event bindings? Or does angular optimise this somehow?

It's hardly any optimization in this case. What if you have several nested ngRepeats. Against which one should optimization be performed? Not easy to answer indeed. Moreover, repeated items can be controlled by another controller.
I see the following a bit hackish way of accomplishing the task.
We can apply ngClick to the parent element calling some method and passing the value allowing to identify clicked item.
<ul ng-click="itemClicked(itemIdentifier)">
<li ng-repeat="el in collection">
<button>click me</button>
</li>
</ul>
The only question left to answer is how we get this identifying value. We need our own directive to apply to repeated DOM element which attaches this value to the element. After that we can get the value from $event object.
<ul ng-click="itemClicked($event.target.itemIdentifier)">
<li ng-repeat="el in collection">
<button click-optimiztation="el">click me</button>
</li>
</ul>
Sure, you've to check for undefined values.
This approach must be adapted to your needs cause you want to have several clickable elements inside each repeated template. Nonetheless, I hope the idea is clear.

Related

How to bind a class on click event of elements obtained through a loop?

I go through an array of objects to display in my html some information boxes that are selectable. For it I bind a class in the event click, but as I obtain the elements through a v-for, when I select one it bind the class to all of them.
How can I differentiate only the selected one?
I have seen several examples with jquery but I would like to know if there is any other method.
My template:
<div class="list-services">
<div class='column-service'
v-for='estimation in filteredEstimation'
v-bind:key="estimation.name"
:class="{ focusService }"
#click="focusService = !focusService"
>
<div class="service-name">
{{estimation.name}}
</div>
<div class="service-description">
{{estimation.description}}
</div>
<div class="service-price">
{{estimation.price}}
<span class="price-currency">€</span>
</div>
</div>
</div>
Thank you very much for your time and help.
I was trying to make a jsfiddle to answer your question, but then I found this jsfiddle on the vue.js forum: https://jsfiddle.net/mogtfpze/2/
It offers three different options for highlighting content by clicking.
li v-for="item in items"
:class="{highlight:item.id == selected}"
#click="selected = item.id">
{{item.id}}
</li>
Although it's an old answer, I believe it should still be compatible with the current Vue version.
If not, or if something is not clear, just let me know and I'll try to help you out!
The method that I use personally (If I understand your question correctly) is the this keyword.
For example:
index.html
<ul id="myList">
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
script.js (Vanilla JavaScript)
var myList = document.getElementById("myList");
myList.addEventListener("click", function () {
console.log(this);
console.log(this.innerText);
});
script.js (jQuery)
var myList = $("#myList").on("click", function() {
console.log(this);
console.log(this.innerText);
});
In this case, using the this keyword allows us to get the innerText of the li element that was clicked by only attaching the event listener to the parent element. innerHtml is only one of many ways to use this keyword. You can see more of them by doing console.log(this) in your event listener function.
Let me know if this helps!

How do I select "this" in angular?

I have 5 buttons using the same ng-click function. Basically each of the buttons operate similarly to a tabbed navigation, where you click one of the buttons and it takes you to that tab's pane. Each of these buttons can be repeatable and are housed in a template. The tab panes are also all in a template but aren't all active until a user clicks one of the buttons and creates a page. So basically there are multiple click functions nested within click functions that do different things depending on what user has activated.
In jQuery, I could just use "this" and select the object that was clicked and do all my manipulations to that object easily; however, it doesn't appear there's a way to do that using just angular. Currently, when you click one of these buttons it does the same thing to all of them. I figure I could create 5 separate functions, but I don't want to do that for scalability reasons.
So to summaraize:
Is there a way to select "this" in Angular?
I'd like a solution that is just using Angular and no jQuery
Is there an efficient way of dealing with click functions within click functions?
<nav class="block--menu">
<section class="content--menu" ng-controller="ActiveCtrl">
<div class="menu" >
<button class="menu__item" ng-click="showConfirm()"></button>
<button class="menu__item" ng-click="showConfirm()"></button>
<button class="menu__item" ng-click="showConfirm()"></button>
<button class="menu__item" ng-click="showConfirm()"></button>
<button class="menu__item" ng-click="showConfirm()"></button>
</div>
</section>
You can access jQuery event object using $event in angular events check the documentation for details but if you are sending that to your controller it most likely means you are not doing it in angular way.
the usage is
<button class="menu__item" ng-click="showConfirm($event)"></button>
and in the controller
$scope.showConfirm = function($event){
//$event.target should be your link
};
You should stop thinking in a jQuery way and don't try to manipulate the DOM directly. In your controller you should only manipulate the data, which is then reflected in the view. When you think Angular-way, your code usually looks as follows:
HTML
<section ng-controller="ActiveCtrl as ctrl">
<div class="menu" >
<button ng-repeat="button in ctrl.buttons track by $index"
ng-click="ctrl.showConfirm(button)"
ng-class="{'menu__item_active':button.active, 'menu__item':true}"
>{{button.name}}</button>
</div>
</section>
JavaScript
angular.module('app',[]).
controller('ActiveCtrl', ['$window', function($window) {
this.buttons = [{
name: 'First'
}, {
name: 'Second'
}, {
name: 'Third'
}];
this.showConfirm = function(button) {
button.active = !button.active;
$window.alert(button.name);
}
}]);
Plunker
http://plnkr.co/edit/Dg10cXqFxEKgEt7jWQ7Z?p=preview

Setting a variable in parent div and using it in children in angular

I'm new to AngularJS. I'm developing a page with a couple of similar blocks but I don't really want to use ng-repeat for those. The thing is, in their ng-click, ng-class and other directives I need to use some variable identifying the block. Is there a way to set it once in parent div and then use in children? Something like this:
<div ng-var="Potatoes">
<button ng-click="buy($parent.var)">Buy</button>
<span>This is a {{$parent.var}}</span>
<img ng-class="{{$parent.var}}">
</div>
<div ng-var="Tomatoes">
<button ng-click="buy($parent.var)">Buy</button>
<span>This is a {{$parent.var}}</span>
<img ng-class="{{$parent.var}}">
</div>
I would use ng-repeat over a specifically crafted object but in this case I have to manually position some other things that I omitted in the code above so ng-repeat is not an option, unfortunately.
You can still use 'ng-repeat' then use of '$index' can differentiate each loop.

AngularJS ng-repeat with no html element

I am currently using this piece of code to render a list:
<ul ng-cloak>
<div ng-repeat="n in list">
<li>{{ n[0] }}</li>
<li class="divider"></i>
</div>
<li>Additional item</li>
</ul>
However, the <div> element is causing some very minor rendering defects on some browsers.
I would like to know is there a way to do the ng-repeat without the div container, or some alternative method to achieve the same effect.
As Andy Joslin said they were working on comment based ng-repeats but apparently there were too many browser issues. Fortunately AngularJS 1.2 adds built-in support for repeating without adding child elements with the new directives ng-repeat-start and ng-repeat-end.
Here's a little example for adding Bootstrap pagination:
<ul class="pagination">
<li>
«
</li>
<li ng-repeat-start="page in [1,2,3,4,5,6]">{{page}}</li>
<li ng-repeat-end class="divider"></li>
<li>
»
</li>
</ul>
A full working example can be found here.
John Lindquist also has a video tutorial of this over at his excellent egghead.io page.
KnockoutJS containerless binding syntax
Please bear with me a second: KnockoutJS offers an ultra-convenient option of using a containerless binding syntax for its foreach binding as discussed in Note 4 of the foreach binding documentation.
http://knockoutjs.com/documentation/foreach-binding.html
As the Knockout documentation example illustrates, you can write your binding in KnockoutJS like this:
<ul>
<li class="header">Header item</li>
<!-- ko foreach: myItems -->
<li>Item <span data-bind="text: $data"></span></li>
<!-- /ko -->
</ul>
I think it is rather unfortunate AngularJS does not offer this type of syntax.
Angular's ng-repeat-start and ng-repeat-end
In the AngularJS way to solve ng-repeat problems, the samples I come across are of the type jmagnusson posted in his (helpful) answer.
<li ng-repeat-start="page in [1,2,3,4,5]">{{page}}</li>
<li ng-repeat-end></li>
My original thought upon seeing this syntax is: really? Why is Angular forcing all this extra markup that I want nothing to do with and that is so much easier in Knockout? But then hitautodestruct's comment in jmagnusson's answer started making me wonder: what is being generated with ng-repeat-start and ng-repeat-end on separate tags?
A cleaner way to use ng-repeat-start and ng-repeat-end
Upon investigation of hitautodestruct's assertion, adding ng-repeat-end on to a separate tag is exactly what I would not want to do in most cases, because it generates utterly usesless elements: in this case, <li> items with nothing in them. Bootstrap 3's paginated list styles the list items so that it looks like you did not generate any superfluous items, but when you inspect the generated html, they are there.
Fortunately, you do not need to do much to have a cleaner solution and a shorter amount of html: just put the ng-repeat-end declaration on the same html tag that has the ng-repeat-start.
<ul class="pagination">
<li>
«
</li>
<li ng-repeat-start="page in [1,2,3,4,5]" ng-repeat-end></li>
<li>
»
</li>
</ul>
This gives 3 advantages:
less html tags to write
useless, empty tags are not generated by Angular
when the array to repeat is empty, the tag with ng-repeat won't get generated,
giving you the same advantage Knockout's containerless binding gives you in this regard
But there is still a cleaner way
After further reviewing the comments in github on this issue for Angular, https://github.com/angular/angular.js/issues/1891,
you do not need to use ng-repeat-start and ng-repeat-end to achieve the same advantages.
Instead, forking again jmagnusson's example, we can just go:
<ul class="pagination">
<li>
«
</li>
<li ng-repeat="page in [1,2,3,4,5,6]">{{page}}</li>
<li>
»
</li>
</ul>
So when to use ng-repeat-start and ng-repeat-end? As per the angular documentation, to
...repeat a series of elements instead of just one parent element...
Enough talk, show some examples!
Fair enough; this jsbin walks through five examples of what happens when you do and when you don't use ng-repeat-end on the same tag.
http://jsbin.com/eXaPibI/1/
ngRepeat may not be enough, however you can combine that with a custom directive. You could delegate the the task of adding divider items to code if you don't mind a little bit of jQuery.
<li ng-repeat="item in coll" so-add-divide="your exp here"></li>
Such a simple directive doesn't really need an attribute value but might give you lots of possiblities like conditionally adding a divider according to index, length, etc or something completely different.
I recently had the same problem in that I had to repeat an arbitrary collection of spans and images - having an element around them was not an option - there's a simple solution however, create a "null" directive:
app.directive("diNull", function() {
return {
restrict: "E",
replace: true,
template: ""
};
});
You can then use a repeat on that Element, where element.url points to the template for that element:
<di-null ng-repeat="element in elements" ng-include="element.url" ></di-null>
This will repeat any number of different templates with no container around them
Note: hmm I could've sworn blind this removed the di-null element when rendering, but checking it again it doesn't...still solved my layout issues though...curioser and curioser...
for a solution that really works
html
<remove ng-repeat-start="itemGroup in Groups" ></remove>
html stuff in here including inner repeating loops if you want
<remove ng-repeat-end></remove>
add an angular.js directive
//remove directive
(function(){
var remove = function(){
return {
restrict: "E",
replace: true,
link: function(scope, element, attrs, controller){
element.replaceWith('<!--removed element-->');
}
};
};
var module = angular.module("app" );
module.directive('remove', [remove]);
}());
for a brief explanation,
ng-repeat binds itself to the <remove> element and loops as it should, and because we have used ng-repeat-start / ng-repeat-end it loops a block of html not just an element.
then the custom remove directive places the <remove> start and finish elements with <!--removed element-->
There is a comment directive restriction, but ngRepeat doesn't support it (since it needs an element to repeat).
I think I saw the angular team say they would work on comment ng-repeats, but I'm not sure. You should open an issue for it on the repo. http://github.com/angular/angular.js
There is no Angular magic way to do this, for your case you can do this, to get valid HTML, if you are using Bootstrap. Then you will get same effect as adding the li.divider
Create a class:
span.divider {
display: block;
}
Now change your code to this:
<ul ng-cloak>
<li div ng-repeat="n in list">
{{ n[0] }}
<span class="divider"></span>
</li>
<li>Additional item</li>
</ul>

javascript doesn't work with LI tag

hi i have this code
html code
<ul>
<input type="button" onclick="appear()"/>
<li id="addQuestionChoices">roma</li>
</ul>
css code
#addQuestionChoices{display:none;}
javascript code
function appear()
{document.getElementById('addQuestionChoices').style.display="block";}
but when i press the button , nothing happend, is javascript doesn't work with LI tag ? or what ?
thank you for answering
The <li> tag must be inside an <ul> or <ol>, and the only allowed children for <ul> and <ol> are <li> tags. This means your <input> should be outside the <ul>:
<input type="button" onclick="appear()"/>
<ul>
<li id="addQuestionChoices">roma</li>
</ul>
just be sure to define the function before, like in this fiddle: http://jsfiddle.net/2dUfa/
<script>
function appear() {
document.getElementById('addQuestionChoices').style.display= "block";
}
</script>
<input type="button" onclick="appear()" value="appear" />
<ul>
<li id="addQuestionChoices">roma</li>
</ul>
As a sidenote: the default display property of a <li> element is list-item (and not block)
It's bad practice to embed JavaScript calls within HTML. It makes the code much more maintainable when the functionality, style and markup are kept seperate. Secondly your <li> element should be nested within either a pair of <ul> or <ol> tags.
I have written a jsFiddle example of how you could tackle this task:
http://jsfiddle.net/dLqja/1/
In this code I have created a 'click' listener, this is attached to your button via its id. Upon the button press it triggers an anonymous callback function which dynamically changes the display style of your 'li' element.
Inclusion of jQuery
Make the following is the first JavaScript that you include in your page.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
This jQuery script is hosted by Google, which has its advantages such as (it's probably already cached in the clients browser from visiting a previous website using it).
Any JavaScript code which you write which uses the functionality of jQuery should be included after the above script.
None jQuery Version...
You can achieve a similar result as the above by assigning an event listener to the button. This approach is preferable to using onclick="..." as sticks to the rule of seperating functionality from markup. If none of these answers work you should check your browsers console for error messages.
http://jsfiddle.net/SvufY/1/
Try putting the <li> inside of a <ol> or <ul> tag.
You should avoid using inline Javascript code, and instead focus on keeping it separated. Attach your event handler to the object in a script tag (or, better yet, a script file loaded at the end of the document), something like this:
<input id="clickButton" type="button" value="submit" />
<ul>
<li id="addQuestionChoices">roma</li>
</ul>
<script>
document.getElementById('clickButton').onclick = function() {
document.getElementById('addQuestionChoices').style.display="block"
};
</script>
You can see a working example of this at http://jsfiddle.net/xxgdB/
Note also you can use either list-item or inherit in the display field to achieve the same effect.

Categories