AngularJS ng-repeat track by $index and Protractor with Duplicates - angularjs

I have an array of strings, which I need to allow for duplicates.
vm.list = ["item","item","item","item","item"]
this is handled in the html with
<ul class="listItems>
<li ng-repeat="item in ctrl.list track by $index"></li>
</ul>
This displays fine in the DOM, no issues, but I run into problems when I try to protractor test the ng-repeat, since I can't unit test it.
So my test is something like.
Then("List items should contain {int} items.", function(listLength){
return element(by.css(".listItems").all(by.repeater('item in ctrl.list track by $index')).then(function(list){
return expect(list.length).equal(listLength);
});
});
I run my tests and it fails with Expect 0 to be 5
But if I make them all unique it works fine, how can I fix this?

I believe this should work for you:
Then("List items should contain {int} items.", function(listLength){
let list = element(by.css(".listItems").all(by.repeater('item in ctrl.list'));
expect(list.count()).toBe(listLength);
});
Source: http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.count
Edit: Sorry, I misinterpreted your question, it appears track by does not work with duplicate values in an array. See: https://docs.angularjs.org/api/ng/directive/ngRepeat. You can substitute your own tracking function.
Here is an example of a similar issue: https://stackoverflow.com/a/28232640/3111958
2nd Edit: After looking into it some more, protractor does not use the track by portion. I updated the code block to reflect that. Check out this issue: https://stackoverflow.com/a/37535098/3111958

Related

ng-repeat with track by over multiple properties

I have a problem with angular ng-repeat directive.
Currently I work on some project where from the API I get a list of items (some times it could be 1k items) and this list should be refreshed every 5 seconds (it is monitoring related project).
When the list length is a little bigger the website while re-rendering DOM could "slow". It comes out that angular regenerate the whole DOM (but 95% of item are the same ! )
One of the possible approach is to set "track by" expression for example to item.id. But here comes another problem, I also want regenerate items when for example descriptions was changed by other user. Since track by is expression to item.id changes in item.description didn't update item in DOM.
There is way to track by over multiple properties? Maybe some function?
Or maybe do comparison by "hand" ?
Any ideas, code samples I would appreciate :)
UPDATE
what I discover when I set track by to item.id angular didn't re-crete html for items, just update value in already created element and it seems to be "faster" then removing and creating.
Previously I though a little bit different.
FIX
For those who are looking for better performance over >1k items in ng-repeat USE track by item.id it will boost your performance ;)
You do not need to create a function to handle track by multi properties.
You can do:
<div ng-repeat="item in lines track by item.id+item.description">
As the comment suggested you could try something like this:
<select ng-model="item" ng-options="item.id as item.description for item in items track by itemTracker(item)">
In your controller:
$scope.itemTracker= function(item) {
return item.id + '-' + item.description;
}
This might help with the number of DOM elements being re-rendered when the list changes.
Based my knowledge, the angularjs model is bind to the ui view, so the model will rerender via $apply or $digest once the value changed. so in your case, u wan bind the model value to ui view but also do not want to re-render the view if the value has not change,that is impossbile. this is what i know.
however, u can just manipulate the dom element. for example
store the data to a variable
var x = [{id:"id1",value:"v1"},{id:"id2",value:"v2"}]
in html, manual append or using directive to append, then assign the id to the element,
<div id="id1">v1</div>
check and compare the value, based ur needs.
once found, then angular.element("#yourid").text()
this will solve your browser resources consume problems.

Protractor find element in repeater by binding?

I am trying to write a simple test that matches a binding in a repeater.
I have it working when I search by a CSS class, however I am "not allowed" to do that in our code. I can't use HTML tags as a locator, either. I can only find by attributes or direct binding.
I have tried many different ways including (but get errors or no result):
var productPageUrl = element.all(by.repeater('product in products').row(0).column('{{product.productPageUrl}}'));
Not sure if it makes a difference, but in the application the HTML template is included by ng-repeat.
This works (but cannot use):
products.then(function(prods) {
prods[0].findElement(by.className('homepage-panel-link')).getAttribute('href').then(function(href){
expect(href).toMatch('/products/1');
});
});
The HTML template being repeated:
<div data-ng-repeat="product in products">
<div data-property-name="productItem-{{$index}}">
</div>
</div>
Is there anyway of simply testing the binding product.productPageUrl??? From the code above that works, it seems a hell of a long way to go around just to get that value.
It seems like you're just looking for the locator by.binding? http://angular.github.io/protractor/#/api?view=ProtractorBy.prototype.binding
i.e.
var productPageUrl = element(by.binding('product.productPageUrl'));
expect(productPageUrl.getAttribute('href')).toMatch('/products/1');
or if you have many that match:
var productPageUrls = element.all(by.binding('product.productPageUrl'));
expect(productPageUrls.getAttribute('href').get(0)).toMatch('/products/1');
or
expect(productPageUrls.getAttribute('href')).toMatch(['/products/1', '/products/2', ...]);
This is my problem too and I can not find any protractor feature to solve that so this is my suggest solution. :) This solution bases on protractor can get element by ng-bind and get value attribute of input. (I have no idea why getText() input not work :D)
element(by.binding('mainImageUrl')).getAttribute('value')
.then(function(text){
expect(text.toMatch(/img\/phones\/nexus-s.0.jpg/));
});
..
<a href="{{product.productPageUrl}}"
class="homepage-panel-link" data-property-name="productPageUrl"></a>
<input type="hidden" ng-bind="product.productPageUrl"
value= "{{product.productPageUrl}}" >
..
in javascript:
element.all(by.repeater('product in products').row(0)
.column('{{product.productPageUrl}}'))
.getAttribute('value').then(function(value){
//matching value
});

Ambiguity in the use of ngRepeat

I have following problem with using of AngularJS ngRepeat.
The issue can be viewed in this jsFiddle.
http://jsfiddle.net/zono/9rmEs/2/
The user can choose character and after this get all combination
of chosen characters in alphabet. Eg:
A - A-B, A-C, A-D and etc.
B - B-A, B-C, B-D and etc.
Everithing works properly but when user change value of selected
character the combination does not get updated. I solved this problem
with adding following code.
<span style="display: none;">
{{item.combionations = getCombinations(item)}}
</span>
And "hack" it. But there must be normal solution.
I would be very grateful for any ideas and recommendations.
Best regards.
Update
In case you plan to do more complex calculations based on the selection this simplified approach would not work. In general it is also better to encapsulate state in some data structure. In your case you could design a structure like this:
{ letter: "A", combinations: ["A-B", "A-C", ... ] }
To update the combinations array you can use ng-change="updateItem(item)" and some update function. Whenever you change the selection the array combination gets updated:
$scope.updateItem = function(item) {
item.combinations = getCombinations(item.letter);
}
I put this in a new fiddle.
You can easily solve this issue by using the model you bound to ng-select in the ng-repeat.
In the select you used item.model. Angular will update its value in the scope whenever you change the selection.
<select data-ng-model="item.model" ng-init="item.model=allLetters[0]" ng-options="value for value in allLetters">
</select>
When you use the same scope variable in ng-repeat you should get the desired behavior.
<div ng-repeat="letter in allLetters">
{{item.model}}-{{letter}}
</div>
Take a look an the updated fiddle.
The problem is that you compute combionations once at the begenning (ng-init="item.combionations=getCombinations(item)"). After that it never gets updated when you change item.model.
You could solve this problem (and also make sure created[...].combionations is kept up-to-date) like this:
<div data-ng-repeat="item in created">
<div ng-repeat="combination in item.combionations = getCombinations(item)">
{{combination}}
</div>
...
See, also, this short demo.

Angular adds extra dom elements in ng-repeat when using filter

I recently updated angular from 1.1.5 to 1.2.14.
Now the filters in the ng-repeats produce some unexpected behavior:
ng-repeat renders as normal
enter text into filter model input : ng-repeat does not filter
remove text from input and angular adds extra dom elements to the ng-repeat, basically repeating the ng-repeat. The array bound to the ng-repeat does not change though.
repeating steps 2 and 3 causes more elements to be added
I have tried to recreate this in PLUNKR and it works fine. Any idea what could be causing this?
code:
<input type="text" data-ng-model="query" >
<div class="default-add" data-ng-repeat="array in arrays.arrays | orderBy:'name' | filter:{name: query}">
<div class="default-add-image">
<h1>{{array.title}}</h1>
</div>
</div>
The data is valid json, I have tested with mock data and have the same error.
Thanks
Removing the NG-REPEAT from the repeated element and adding it to a parent container solved it.
If it is running in Plunker fine, then most likely the problem lies in your code. Is this a custom filter? If yes, I would start looking there. If not, then I would check if it works properly on artificial data (thinking that the data might be the issue). If this does not work, then look at your data provider. If this works, then look into how you are processing the data.
I doubt that this is Angular's bug. I migrated from 1.0.7 to 1.2.x recently without such problem (there were few, but all fixed easily by following migration guidelines from Angular team).
Hope that helps!

unshifting to ng-repeat array not working while using orderBy

Here is my problem. I've got a comment roll thats using ng-repeat to display the content of a comment array. When the user submits a comment I wan't to unshift to that array in order to display the most recent comment at the top of the list. This works perfectly, but when I add the orderBy filter to the repeat the new comment is applied to the bottom of the repeat.
Here is the comment array HTML:
<ul ng-repeat="comment in comments | filter:{page_id:raceID} | orderBy:'id':'reverse' track by comment.id ">
<li>{{comment.id}}</li>
<li>{{comment.page_id}}</li>
<li>{{comment.user_name}}</li>
<li>{{comment.comment_copy}}</li>
</ul>
Here is the corresponding Controller:
$scope.comment = new newComments({page_id:3, comment_copy:'test comment copy'});
$scope.comment.$save(function(data) {
$scope.comments.unshift($scope.comment);
console.log($scope.comment.id);
});
.....
I scrapped the
orderBy:'id':'reverse'
and instead used a custom filer left on another post here, Angular ng-repeat in reverse. Here is the custom function
app.filter('reverse', function() {
return function(items) {
return items.slice().reverse();
};
});
Now the most recent comment was still not showing up at the top of the page so I had to change from unshift to push. This worked perfectly. Here's the code:
$scope.comment.$save(function(data) {
$scope.comments.push($scope.comment);
});
A few things are wrong in your original post:
orderBy:'id':'reverse' should be orderBy:'id':reverse, where reverse is a boolean (so either replace it by a variable available on the scope, or by a hard-coded true/false). Currently, it defaults to undefined and is interpreted as false.
In your controller code, the field comment.id is not assigned. It will default to undefined and that's the reason sorting does not work as expected.
Additionally:
Array unshift or push will not make a difference in your use case if you correct the aforementioned and the orderBy function is invoked.
In my experience in Angular 1.5.8, track by $index in fact prevented the orderBy function from working. Replacing it by a unique identifier on the object to be repeated, i.e. track by comment.id is preferred. In terms of performance, they should be analogous. Not using the track by clause is the slowest option though. (https://docs.angularjs.org/api/ng/directive/ngRepeat)
that worked for me, would love to know how it affects performance though:
change:
track by comment.id
to:
track by $index
both unshift() and push() should work now, but again, not sure about how much it slows down the DOM regeneration.

Resources