I am new to Angularjs and my app(built with Angular & Rails) is taking too much time to load the images/data. So I plan to optimize the code. Here is very quick and short question. What is the best way to use ng-repeat ? I planned to use track by in ng-repeat.
In my $scope.attributes I am getting thousands of data from server. In each there are many fields. But I wanted to use single field from those. SO I tried something like this:
<div ng-repeat="attribute in attributes track by attribute.name" >
<li> {{attribute.name}} </li>
</div>
Is this correct way? I have seen many examples everywhere use track by task.id So Can I use any specific field name instead of id ? Can I use filter also? what is the meaning of $$hashkey approach? I wanted to load images and it takes more time. Please want Experts advice.
if you track by .name it will use .name as the identifier, eg if the list changes it will find the ROW by .name
If the list will not change, look at one way binding
<div ng-repeat="attribute in ::attributes track by $index" >
<li> {{attribute.name}} </li>
</div>
-- I am sure that this will give you a DIV around the LI
Not related to your Q, but having just done some AngularJS performance tuning.
Consider using ng-if instead of ng-show / ng-hide , ng-show/hide will still produce the DOM , ng-if will not. (You may have done this already)
Related
As I've recently found out, ngSwitch acts a bit like ngIf, in that it outright removes elements from the DOM rather than just hide them. Now in my application this strikes me as both needlessly expensive and potentially problematic down the line (if I need to access properties of my hidden DOM elements). At the same time I like the cleanliness of the ngSwitch syntax as opposed to a bunch of different ngShow directives (which would not allow me to include a 'default' behaviour either). Is there any way I can modify the way this directive works to have it merely hide elements, not remove them altogether? Thanks.
OK, I understood better your need and I have the same need before.
I advice you to use ngSwitch with custom directives like this :
<div ng-switch="mode">
<directive-a ng-switch-when="a"></directive-a>
<directive-b ng-switch-when="b">...</directive-b>
<directive-c ng-switch-default>...</directive-c>
</div>
and in the definitions of your directives directiveA, directiveB and directiveC you use the templateUrl wich will use the cache from the second call of the directive.
The second approach is to use ng-show on every directive but I prefer the ng-switch because it let the dom lighter.
I know this is a bit old but I faced the same issue today and I managed to cache segments views by not using ngSwitch but directly the [hidden] attributes with the segment condition this way :
<ion-segment [(ngModel)]="segmentName">
<ion-segment-button value="profile">
Profile
</ion-segment-button>
<ion-segment-button value="friends">
Friends
</ion-segment-button>
</ion-segment>
<page-profile [hidden]="segmentName != 'profile'" ></page-profile>
<page-friends [hidden]="segmentName != 'friends'" ></page-friends>
Just replace ngSwitchCase by the [hidden] conditional
No, there isn't any way to keep nodes hidden as ngShow and ngHide do if you use ngSwitch. Only the ngSwitchWhen node will be created if its condition is true, all others are commented.
The advantage of ngSwitch is that the dom will be lighter.
I'm currently working on a project that will need to display editable tables of arbitrary dimensions. I'm using AngularJS, but as the tables can get very large I expect things will get slow if I don't use some form of bind-once. The problem is, as far as I can tell, bind-once is absolute -- I want it to actually keep or at least reinstate all the watchers for rows currently being edited.
Here's the basic idea of how I'm currently doing this.
<tr ng-repeat="row in rows track by key" hackonce>
<td ng-repeat="col in cols">
<div ng-click="editStuff()">{{contents}}
directive('hackonce', function() etc
link: function(scope, elem, attrs) {
if (!scope.row.editing)
setTimeout(function(){ scope.$destroy() }, 0)
}
I'm generating a string key on the row from its various identifiers. When the item is flagged for editing, the controller switches it out for a copy and changes its key to include what will eventually be transaction lock data. This triggers a replacement in the ng-repeat, and the next time the tr is constructed with the replaced row data, the scope is left intact and updates as you edit stuff.
Somehow, it actually works (so far anyway). It drops me from over 500 watchers on a small list to under 100, and what remains is mostly top-level controls, so I hope it will scale well. It also seems to retain and properly clean up ng-click listeners and such even though the scope is gone.
However, it's the sort of hack that I cringed just typing it out in brief, and I get the feeling it may induce vomiting in sane people who know this library better than I do. I was wondering if there was a cleaner and/or safer way to do the same thing. What I'm looking for is a better way to conditionally bind once, or have an element/scope otherwise skip its $digest for itself and all its children based on some exposed flag.
Is there any way to do this that doesn't involve taking a meat cleaver to the scope or manually reattaching all the jQuery garbage I was hoping to avoid with Angular?
It's kind of difficult without more implementation detail, but I'm assuming that you're using some mechanism to show/hide the read-only/editable elements for each row? If you use ng-if to do it, it will attach the elements to the DOM and re-bind when the condition is true and detach when false.
So, something like:
<td ng-repeat="col in cols">
<span ng-if="!isEditing(row)">Read-only stuff here</span>
<span ng-if="isEditing(row)">Editable stuff here.</span>
</td>
I created a really simple example plunk here:
http://plnkr.co/edit/SkdgTuK5UvEEsZmzWat6?p=preview
I want to use an ng-if inside an ng-repeat (as described in this question):
However, I want to display a row in a table if and only if the if condition holds. In the answers to the question above, you will get empty div tags where the if condition does not hold. This does not cause much of a problem with divs, but when doing the same with tables, you do not want to have div (or span) tags inbetween tr tags.
Is there another tag or directive that I could use?
A better idea is to use a (custom) filter.
That way, only the needed rows are generated, in stead of hiding/removing existing ones.
I just made a plunk for another question, that showed how to use a filter:
the relevant line is this one:
<tr ng-repeat='item in appVm.users |filter:test'>
you can use a object directly in your code like this:
<tr ng-repeat='item in appVm.users |filter:{age:32}'>
If you put that line in the sample I linked in, you can see how that would work out!
Does this help you?
If you just want to hide these elements, you can consider using ng-show instead of ng-if.
Maybe you can also add the ng-show in the ng-if tag to hide it when empty.
Now, if you do not want to render the content at all, I think the only solution is to remove these elements from your collection in your controller
you can use ng-show.its works for show and hide tags.if you don't want to angular compile inside the tag you can use ng-if.
It seems that getting an element in AngularJS is a bad idea, i.e. doing something like:
$('.myElement')
in say, a controller is not an angular way of doing things.
Now my question is, how should I get something in angular?
Right now, what I'm doing (and is an accepted way of doing it) is by watching a variable, and my directive does something based on it.
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.id == varToWatch)
{
//Run my Directive specific code
}
});
However, while this particular design works for most cases, watch is an expensive operation, and having lots of directives watching can really slow down your application.
TL:DR - What is an angular way of getting a directive based on a variable on the directive? (like the one above)?
If you want to get/set values you don't need to fetch the element using jQuery. Angular data binding is the way to do it.
directives is the way to go if you want to do animations or any kind of element attributes and DOM manipulation.
Your code is basically right; the directive should watch something in the $scope and perform it's logic when that thing changes. Yes, watch statements are expensive, and that is a problem once your number of watches start to approach ~2000.
Looking at your code though, I see one problem:
The variable $scope.varToWatch references an id in the template.
When this variable changes, you want something to happen to the element which has this id.
The problem here is in the first point: The controller should know nothing about the DOM, including the id of any element. You should find another way to handle this, for example:
<div my-directive="one"> ... </div>
<div my-directive="two"> ... </div>
<div my-directive="three"> ... </div>
...etc
And in your directive:
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.myDirective == varToWatch)
{
// Run my Directive specific code
}
});
You are very vague as to what you're trying to achieve, but I'll try to answer in context of your last comment.
I have a lot of the same directives (therefore the code will run on all of them), but I need to get only one directive from the lot.
You talk a lot about getting the right element. The directive element is passed to the link function in the directive. If you are not using this element (or children of it) directly, but rather trying to search for the element you want somehow, you are most likely approaching the problem the wrong way.
There are several ways to solve this, I'm sure. If you're thinking about animations, there is already support for that in Angular, so please don't try reinvent the wheel yourself. For other logic, here are two suggestions:
Secondary directive
If the logic you want to apply to this directive is generic, i.e. it could be applied to other directives in your application, you could create a new directive which works together with directives. You can set prioritization in directive in order to control which directive is executed first.
<main-directive ... helper-directive="{{condition_for_applying_logic}}"></main-directive>
jsFiddle example
Expanding main directive
If the logic is tightly coupled to this directive, you can just create a new attribute, either dynamic or static, and bind to it in the directive. Instead of checking 'attrs.id == varToWatch', you check if $scope.apply-logic === 'true' and apply the logic then.
<main-directive ...></main-directive> <!-- Not applied here -->
<main-directive apply-logic="true" ...></main-directive> <!-- Applied here -->
<main-directive apply-logic="{{some.varOnScope}}"...></main-directive> <!-- Conditional -->
Please comment if something is unclear.
I am trying to create the application that user can choose different types of block and stack them together to create unique template.
Since I want the user be able to add same block into the template multiple times, I have to use 'track by $index' to accomplish this:
<li ng-repeat="chosen in chosenlist track by $index">
However, when I tried to add animation with ng-animate, the animation for removing block is animate on last block in the template instead of the removing block. I put the code in jsfiddle here http://jsfiddle.net/FC9c7/6/.
Try adding new block by choosing layout 1, 2, or 3. And when you click 'remove block' you will see the problem.
Here's what I believe it's happening: since you're tracking the items by their indices, every time you remove one from the list, what changes is the index of the last element, making Angular believe that it was the one removed. That becomes sort of obvious when you print the index next to its element. Take a look at this modified jsFiddle.
One solution would be to create new elements with unique ids and then track by those ids:
Javascript
$scope.add_layout = function(new_layout) {
new_layout = angular.copy(new_layout);
new_layout.id = new Date().getUTCMilliseconds();
$scope.chosenlist.push(new_layout);
};
HTML
<li ng-repeat="chosen in chosenlist track by chosen.id" ng-animate="'animate'">
jsFiddle here.
But since it creates new elements, you won't be able to keep them in sync with the original object, and I don't know whether that's acceptable to you.
I'll try to check out if the new animation system in Angular 1.2 RC1 solves this particular problem and if I find out something I'll update this answer. But I'm not confident it does, though. :(
You could make a copy of the object before adding it to the chosen list. This way you could track by $id(chosen),which is the default. You are adding the same object to the chosen list so angular will see duplicates in the repeater for ng-repeat.
change the add_layout function the following and remove the track by expression in the ng-repeat.
This is just another solution. You might have large objects where performing a deep copy may not make sense.
$scope.add_layout = function(new_layout) {
$scope.chosenlist.push(angular.copy(new_layout));
};