optimize angularjs search filter - javascript

Here are almost 2000 rows in the page. I use AngularJS filter to search some items which contains the typed strings. The problem is that efficiency is bad when typing one character into the input control.
Do you guys have good ideas how to improve this filter?
Here is the search code:
input box:
<input class="searchText ng-cloak" ng-model="searchText.ValueName" placeholder="Search Value" />
in the table's ng-repeat:
<tr ng-repeat="registry in currentSettings | filter:searchText" ....
string filter:searchText is used to filter.

The bottleneck is likely adding and removing elements from the DOM. Avoid this by using css to hide elements. In stead of filtering the ng-repeat, use ng-show:
<li ng-repeat="registry in currentSettings" ng-show="([registry] | filter:searchText).length > 0">
http://php.quicoto.com/use-ng-show-filtering-data-angularjs/
There is also the virtual ng-repeat plugin, it only adds the dom nodes which are going to be rendered for better performance

I would also suggest to use the track by in the repeater. It will prevent unnecessary removal and reinsertion of elements and result in dramatic improvement in speed in some cases. Just make sure you are tracking by some unique property.

Related

Kendo toolbar update overflow text

my kendo toolbar has items using template and overflowtemplate, e.g.
template:
<label id='piName'>Hello</label>
overflowtemplate:
<label id='piName'>Hello</label>
In runtime, based on conditions, I want to update the words in the overflow popup (say from Hello to Good) Anyone know who to do it in js/jquery? Thanks
This is not kendo specific, just DOM manipulation. You have already given the element an id, so just select for that and update the content:
$("#piName").text("Good");
As an aside, elements ids should be unique for html to be semantically correct so I would recommend using a different one in each template. Or perhaps you can use a class which does not need to be unique and additionally gives you the option to style the label with css:
<label class='toolbar-label'>Hello</label>
$(".toolbar-label").text("Good");

ng-repeat with includes: best performant way?

I am facing a performance issue with Angular (Ionic), and I'd like to know which would be the most performant way to solve this problem.
I get a list of objects from a service that I have to show in my app:
$scope.objectList = [
{
id: 123456,
name: "abcdefg",
state: [1|2|3|4|5|6|7|8...],
....
},
....
];
The list won't never have more than 20-25 objects.
The problem is that the elements in the list, although they are of the same type, they have to be shown in a quite different way. I have a different template for each posible state an object can be in.
I am using ng-repeat this way to show the list. The list won't change so I can use one way binding.
<div ng-repeat="obj in ::objectList track by obj.id">
....
</div>
Inside this ng-repeat I have to include the presentation template of each object, which changes depending on the object state.
I have tried different solutions but don'f find one that improves substantialy the rendering performance.
For example, I've tried this but I'm not sure that binding a function to ng-include is very performant.
HTML:
<div ng-repeat="obj in ::objects track by obj.id">
<div ng-include="getTemplateToInclude(obj)"></div>
</div>
JS:
$scope.getTemplateToInclude = function(obj){
if(obj.state === 1){
return "tmpls/template-a.html";
}else if(obj.state === 2){
return "tmpls/template-b.html";
}
....
};
Any suggestion of how to deal with this problem? Whicch would be the best way to include this different templates in the same list?
Do I have to use the one way binding notation(::) in the included templates or it's enough with the one used in the ng-repeat?
thanks in advance
I haven't used the one-way binding template syntax (::) so I won't speak to that. But based on your comments and original question, I think the best approach would be something along these lines:
create a single item renderer template that will process your repeater items
continue to use track by syntax in your repeater in order to recycle the item renderers
based on the item's type, use a combination of ng-show & ng-hide to display the correct children nested in your template.
<!-- item renderer template.html -->
<div>
<div ng-show="item.type == 'foo'"></div>
<div ng-show="item.type == 'bar'"></div>
<div ng-show="item.type == 'baz'"></div>
</div>
Creating a single template combined with track by in the repeater will reuse the DOM elements, negating the need to pull them from the DOM and then insert new ones.
Using ng-show or ng-hide vs. ng-if is based on the same idea as above. You are toggling the visibility of the DOM elements rather than destroying/creating them.
The reason I argue for this is that in nearly all performance-related tweaking, reusing or recycling objects are usually faster than destroying and then recreating new objects. This is certainly true of Javascript, and while I've yet to test that theory in DOM manipulation, I would bet it too is true. I'm making an educated guess but since you are using the Ionic framework, this should benefit mobile performance as well,
Just keep in mind, given the various permutations of what you could be showing and the number of rows of data, you might only see marginal performance gains. Depending on the number of bindings inside your template, you might reach a point where the performance penalty for Angular's digest cycle to complete is the same as the performance gains from recycling your DOM elements.

How to manipulate many elements inside an ng-repeat?

I have an array that I want to show through a ng-repeat. But I have inside the div (with the ng-repeat) many other elements which I want to manipulate for each iteration of that ng-repeat. For example:
<div ng-repeat="data in result">
{{data.name}}
<img src="value">
<p class="class">text</p>
</div>
If I want to change the image source with a different source for each element of my array, how can I do this? I can't change anything of result array. I want to change it later, when the user clic in a bottom for example.
Daniel
EDIT:
img tag was just an example. I added other element which could be necessary to change the class. So a different class for each iteration.
To change the image source, use ng-src like:
<div ng-repeat="data in result">
{{data.name}} <img ng-src="{{data.imageUrl}}">
</div>
In this I am assuming you have a property on each data item called imageUrl.
To change the value, just assign a new value to the imageUrl property and angular's two way bindings will do the rest. An example is:
<button ng-click="result[0].imageUrl = 'img/new-image.jpg';">Change Image</button>
Of course, you would want to make sure you have the correct index and that there is at least one element in the result array, but that should do it!
Live Example
To see this in action, check out the following plunker: http://plnkr.co/edit/gYLH7Z4b03lS2gZBHceC?p=preview
EDIT
Based on the edit to the question - if you need to change the class use ng-class. In fact, there is a directive already written for most of the common things that you need to change. Always search the angular documentation first: angularjs.org. If you do need to do something more advanced that isn't already written, read the documentation and create your own directive: https://docs.angularjs.org/guide/directive. Best of luck!
Use ng-src instead of src for string interpolation. See https://docs.angularjs.org/api/ng/directive/ngSrc.

Handling duplicate elements in an ng-repeat

I am building an app which features a kind of "playlist". This is represented an ng-repeated custom directive with ng-repeat = "element in playlist"
Because I want to allow a user to re-use the same element twice in the playlist, I tried using the track by $index addition.
Now, what's confusing is: when I came to remove an element from the playlist (I have a function removeElement(index) which essentially contains something like this:
$scope.removeElement = function(index){
$scope.playlist.splice(index, 1);
}
Something weird happened: the element was removed correctly from $scope.playlist, but for some reason the view didn't update properly. The last element appeared to be removed.
Furthermore, I couldn't properly re-order the elements in the array either.
When I removed track by $index this problem disappears, so I assume this is because when you remove an item from the array, if you're only looking at the indices, then it appears you've just deleted the last one.
The behaviour is odd though, because transcluded content is correctly removed -- see this plunker
EDIT: The above link has been modified to show the problem better and also show the answer I settled on.
The question has also been slightly edited, to make it clearer what I was getting at. KayakDave's answer below is still correct, but is more suited to an array of primitives (which my original plunker featured).
TL;DR: How do you put duplicate elements in an ng-repeat without sacrificing the ability to control their position, or remove elements correctly?
One of the big performance advantages of using track by is Angular doesn't touch any DOM element whose tracking expression hasn't changed. This is a huge performance improvement for long ng-repeat lists and one of the reasons for the addition of track by.
That performance optimization is at the root of what you're seeing.
When you use $index in track by you're telling ng-repeat to tie each DOM element it creates to it's position ($index) on the first run of ng-repeat.
So the element with color style red is tagged 0, orange 1, yellow 2 ... and finally indigo 5.
When you delete a color Angular looks at the indexes you told it to track and sees that you longer have an index #5 (since your list is one shorter than before). Therefore it removes the DOM element tagged 5- which has a color style of "indigo". You still have an index #2 so the element with the color yellow stays.
What makes it confusing is that, due to data binding, the text inside the DOM element does get updated. Thus when you delete "yellow" the DOM element with the color yellow gets the text "green".
In short what you're seeing is ng-repeat leaving the DOM element styled with yellow untouched because it's tracking value (2) still exists but data binding has updated the text inside that element.
In order to add multiple entries with the same color you need to add your a unique identifier to each entry that you use for the track by. One approach is to use key-value pairs for each entry where the key is your unique identifier. Like so:
$scope.colorlist = {1:'red', 2:'orange',3:'yellow',4:'green',5:'blue',6:'indigo',7:'yellow'};
Then track by the key as follows:
<color-block ng-repeat="(key, color) in colorlist track by key" color="{{color}}" ng-transclude>
{{color}}
</color-block>
And make sure to use that key for your delete select:
<option value="{{key}}" ng-repeat="(key,color) in colorlist">{{color}}</option>
Now the DOM element with the color styling yellow is tied to the key you specified for the "yellow" array element. So when you delete "yellow" the ng-repeat will remove the correct DOM element and everything works.
You can see it work here: http://plnkr.co/edit/cFaU8WIjliRjPI6LInZ0?p=preview
I'd like to add another answer to this question, because I discovered a simpler solution.
There's an important section of the documentation for ng-repeat which is easy to miss, specifically on the dupes error.
It states:
By default, collections are keyed by reference
After reading this, the solution was obvious - as I wasn't dealing with primitives (yes, the plunker is, but that was an over-simplification) I needed to copy the duplicate object and add its copy to the array. This means everything works as expected when you remove track by $index and just let the default behaviour take over.
Angular makes this especially easy because jqlite has a .copy. method.
Here's what I'm saying demonstrated in a plunker.

JQuery "#id.class" Selector Mashup

I have some bullet points which I want to show more text below them on clicking them. They are both two separate Ps that are paired together by sharing a common id. So, what I am trying to do below is to find the element with (id_same_as_this.class), so that the element with the class "expand" as well as the id that matches the clicked on P is toggled. Does that make sense?
$(document).ready(function(){
$(".expandable").click(function(){
$(this.attr('id')+"."+"expand").toggle(800);
});
});
I only ask if the above code could be made to work because it would make the expandable bullet points in my web page significantly less code intensive than a lot of the examples I have read about.
$(this.attr('id')+"."+"expand").toggle(800);
Must be
$("#" + this.id +".expand").toggle(800);
You missed the # there. That said, you shouldn't ever have a common ID. By definition IDs are meant to be unique. If you have the same ID on multiple elements, while it may work now on the browsers you try, you have no guarantee it won't break in the next rev of jQuery (or Chrome, or Konqueror, or iOS Safari). There's also no reason to do it. You could just use classes or data-* attributes.
Yes this will work but you need a # before the ID
They are both two separate Ps that are paired together by sharing a common id.
IDs are unique. Two elements can't share a common ID, as that defeats the whole purpose of having a unique identifier. JavaScript assumes that you're using valid HTML, so document.getElementById() will return only the first element with a matching id. By using non-unique IDs, things will start breaking in unpredictable ways:
$('#foo').find('.bar') // Won't search past first #foo
$('#foo .bar') // Will search past first #foo in IE8+
Try restructuring your HTML to make this task easier. Maybe you could do something like this:
<ul id="bullets">
<li>
<h2>Title</div>
<div>Text</div>
</li>
</ul>
And then use a simple event handler:
$('#bullets h2').click(function() {
$(this).next().toggle(800);
});
You don't need id values for this at all (which is good, as from the comments on hungerpain's answer, you're using the same id value on more than one element, which is invalid).
Just do this:
$(document).ready(function(){
$(".expandable").click(function(){
$(this).find(".expand").toggle(800);
});
});
That will find the element with the class expand within the expandable that was clicked. No relying on unspecified behavior of selectors.
If you really need that data on the expandable, just put it in a data-* attribute. So instead of this invalid structure:
<!-- INVALID -->
<div id="foo27" class="expandable">
<div class="expand">...</div>
</div>
<div id="foo27" class="expandable">
<div class="expand">...</div>
</div>
Do this
<!-- VALID -->
<div data-id="foo27" class="expandable">
<div class="expand">...</div>
</div>
<div data-id="foo27" class="expandable">
<div class="expand">...</div>
</div>
Use the above code to do the expansion. If you need the value, use .attr("data-id") or .data("id") to get it.

Categories