How to get values from ng-repeat in protractor? - angularjs

<div ng-repeat="obj in list">
</div>
This obj is having 6 properties like name, country, state, city, street, and contact. I have written following code for this but it is giving me undefined as the value if i try to fetch the stored value in obj.
var elm = element.all(by.repeater('obj in list'));
elm.then(function(obj){
console.log("Number of Rows : "+ rows.length);
for(var i=0; i< rows.length; i++){
console.log("App name : " + rows[i].name);
}
});
This is printing "App name : undefined" only.

Keep in mind that you are using promises so you can't log a unresolved promise. All executions in Protractor are promises.
Secondly, you are using the rows, but you never defined it. Is rows the same as obj?
Third, try to avoid the a for loop with Promises. It's better to use for example the each from protractor. If you do that your code looks like this
var elm = element.all(by.repeater('obj in list'));
elm.each(function(obj, index) {
// Will print 0 First, 1 Second, 2 Third.
obj.getText().then(function (text) {
console.log(index, text);
});
});

firstly, the ng-repeat does not contain an object, it contains a list. in your case, an array.
if you have nested lists (ng-repeat in ng-repeat) please specify that in your question. otherwise, the following code should work for you.
let elm = element.all(by.repeater('obj in list'));
elm.getText().then(function(text){
console.log(text);
});

Related

AngularJS Filter throws infdig error when it creates new array

i am about to bang my head to walls. i thought i had an understanding of how angular works (filters too). but i just cant find the problem about my filter. it causes infdig. and i even dont change source array in filter.
(function () {
angular.module('project.filters').filter('splitListFilter', function () {
return function (data, chunk) {
if(!data || data.length === 0){
return data;
}
var resultArray = [];
for (var i = 0, j = data.length; i < j; i += chunk) {
resultArray.push(data.slice(i, i + chunk));
}
return resultArray;
};
});
})();
i have lists where i need to split data to x columns. it is complicated to solve with limitTo.
(limitTo: $index*x | limitTo: $last ? -z : -x)
it causes a dirty template file. so i decided to create a filter which splits an array to groups.
[1,2,3,4,5,6,7,8] -> [[1,2,3],[4,5,6],[7,8]]
so i can easily use it in my template.
Can u help me about what causes infdig in this filter?
Edit: the error message itself looks strange with some numbers in that don't appear anywhere in the code, which can be seen at http://plnkr.co/edit/pV1gkp0o5KeimwPlEMlF
10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn: regularInterceptedExpression","newVal":23,"oldVal":20}],[{"msg":"fn: regularInterceptedExpression","newVal":26,"oldVal":23}],[{"msg":"fn: regularInterceptedExpression","newVal":29,"oldVal":26}],[{"msg":"fn: regularInterceptedExpression","newVal":32,"oldVal":29}],[{"msg":"fn: regularInterceptedExpression","newVal":35,"oldVal":32}]]
HTML Template
<div class="row" ng-repeat="chunk in docProfile.SysMedicalInterests | splitListFilter: 3">
<div class="col-md-4" ng-repeat="medInterest in chunk">
<label style="font-weight:normal;">
<input type="checkbox" value="{{medInterest.ID}}" ng-click="docProfile.saveInterest(medInterest.ID)" ng-checked="docProfile.isMedChecked(medInterest.ID)"> {{medInterest.Name}}
</label>
</div>
</div>
Controller Code
var me = this;
me['SysMedicalInterests'] = null;
var loadMedicalInterests = function(){
var postData = { 'Data': me['data']['subData'] };
return docService.loadMedicalInterests(postData).then(function(resp) {
me['SysMedicalInterests'] = resp['data'];
}, function(){});
};
loadMedicalInterests();
so array starts with a null reference and loads data from server. which changes array causes a second filter run. but it doesnt stop after that
Edit: here is plunkr http://plnkr.co/edit/OmHQ62VgiCXeVzKa5qjz?p=preview
Edit: related answer on so https://stackoverflow.com/a/21653981/1666060 but this still doesn't explain angular built in filters.
here is angularjs limitTo filter source code
https://github.com/angular/angular.js/blob/master/src/ng/filter/limitTo.js#L3
About what exactly causes it, I suspect is something to do with the fact that every time you run the filter a new array reference is created and returned. However, Angular's built-in filter filter does the same thing, so I'm not sure what is going wrong. It could be something to do with the fact that it's an array of arrays that is being returned.
The best I have come up with is a workaround/hack, to cache the array reference manually as an added property, which I've called $$splitListFilter on the array, and only change it if it fails a test on angular.equals with the correct results calculated in the filter:
app.filter('splitListFilter', function () {
return function (data, chunk) {
if(!data || data.length === 0){
return data;
}
var results = [];
for (var i = 0, j = data.length; i < j; i += chunk) {
results.push(data.slice(i, i + chunk));
}
if (!data.$$splitListFilter || !angular.equals(data.$$splitListFilter, results)) {
data.$$splitListFilter = results;
}
return data.$$splitListFilter;
};
});
You can see this working at http://plnkr.co/edit/vvVJcyDxsp8uoFOinX3V
The answer uses Angular 1.3.15
The JS fiddle works fine: http://jsfiddle.net/3tzapfhh/1/
Maybe you use the filter wrongly.
<body ng-app='app'>
<div ng-controller='ctrl'>
{{arr | splitListFilter:3}}
</div>
</body>

Protractor - count elements in repeater and print it

I'm trying to count the elements in repeater and to print it to console.
This is the markup:
<div class="col-md-3 ng-scope" ng-repeat="app in userApps" >...< /div>
currently I'm counting and comparing:
expect(element.all(by.repeater('app in userApps')).count()).toEqual(4);
it works, but I want to be able to print it also.
I've tried this:
var rows = element.all(by.repeater("app in userApps"));
var sum = rows.count();
console.log(sum.getText());
but I'm getting:
TypeError: Object [object Object] has no method 'getText'
there are two question actually-
1. am I doing it the correct way?
2. how do I print it to console?
If I understand your problem correctly, you actually want to print the count and not the entire content, right?
element.all(by.repeater('app in userApps')).count().then(function(count) {
console.log(count);
});
the more 'modern' way of doing this is using async/await
it('test case', async () => {
let count = await element.all(by.repeater('app in userApps')).count();
console.log(count);
});

Angular nested ng-repeat filter items matching parent value

I am passing in 2 arrays to my view. I would like my nested loop to only display where it's parent_id value matches the parent.id. Eg.
arr1 = {"0":{"id":326,"parent_id":0,"title":"Mellow Mushroom voucher","full_name":"Patrick","message":"The voucher says $10 Voucher; some wording on the printout says, \"This voucher is valid for $20 Pizza\" but my purchase price or amount paid also says $20. Shouldn't that be $10","type":"Deals"}};
arr2 = {"0":{"id":327,"parent_id":326,"title":"Re: Mellow Mushroom voucher","full_name":"Patrick Williams","message":"Some message here","type":null};
...
<div data-ng-repeat = "parent in arr1">
<span>{{parent.title}}<span>
<div data-ng-repeat="child in arr2 | only-show-where-child.parent_id == parent.id">
<li>{{child.body}}</li>
</div>
</div>
Is this possible/best practice in angular of should I be filtering the object in node before passing it into angular? Thank you!
There are a couple of ways you could do it... You could create a function to return just the children:
$scope.getChildren = function(parent) {
var children = [];
for (var i = 0; i < arr2.length; i++) {
if (arr2[i].parent_id == parent.id) {
children.push(arr2[i]);
}
}
return children;
};
html:
<div ng-repeat="child in getChildren(parent)">
You could define a filter to do the same thing:
myApp.filter('children', function() {
return function(input, parent) {
var children = [];
for (var i = 0; i < input.length; i++) {
if (input[i].parent_id == parent.id) {
children.push(input[i]);
}
}
return children;
};
});
html:
<div ng-repeat="child in arr2|children:parent">
Both of those methods will execute every digest cycle though. If you have a large list of elements you would definitely want to improve performance. I think the best way would be to pre-process those results when you get them, adding a children array to each object in arr1 with only its children (here using array.filter instead of for loop and array.forEach):
arr1.forEach(function(parent) {
parent.children = arr2.filter(function(value) {
return value.parent_id === parent.id;
};
});
Then in the html you are already working with the parent so you can repeat over its children property:
<div ng-repeat="child in parent.children">
Instead of using filters, data-ng-if can achieve the same result.
<div data-ng-repeat="parent in arr1">
<span>{{parent.title}}<span>
<div data-ng-repeat="child in arr2" data-ng-if="child.parent_id == parent.id">
<li>{{child.body}}</li>
</div>
</div>
The solution depends on how often arrays are changed and how big arrays are.
The fist solution is to use filter. But in this case it would be called at least twice (to make sure that result is "stabilized" - selected same elements).
Other solution is to $watch by yourself original array and prepare "view" version of it injecting children there. Personally I would prefer the second as more explicit.
However if you can reuse "find-the0child" filter in other parts of your application you can go with first one - AngularJS will re-run filter only after original array modified.
If needed I can provide here an example of implementation of one of these options - add the comment to answer.

Issue with Angularjs Dropdown and a custom filter

I'm having an issue using a dropdown that is populated with ng-repeat option values or even when using ng-options.
Basically I'm pulling a list of subsidiaries from the database. I then have a dropdown to choose a company, which in turn should populate the subsidiary dropdown with subsidiaries of the chosen company. Since many of the subsidiaries are of the same company, if I try and pull the the company name in ng-repeat, I get the same company several times. So I have created a custom filter that filters out the companyName and companyID of each company listed only once.
Everything works in the theory that when I change the value of the company dropdown, the correct subsidiaries are listed. However the value shown in the company box is stuck on the first option listed and will not change. If I remove the custom filter and allow it to list all the repeat names, the box displays correctly.
My first thought is to make a separate HTTP call that would just get companies from my companies table, but I would think I want to limit HTTP calls to as few as possible. Plus it would seem that I should be able to accomplish this.
What concept am I not grasping that prevents this from displaying correctly when I use my filter and what should I do to fix this?
thanks
HTML:
<div class="col-sm-5">
<select ng-model ="parentCompany" name="company">
<option ng-repeat="company in companies | uniqueCompanies:'companyName'" value="{{company.id}}" >{{company.name}}</option>
</select>
</div>
<div class="col-sm-5">
<select name="subsidiary">
<option ng-repeat="subsidary in companies" value="{{subsidary.subID}}" ng-hide="$parent.parentCompany !== subsidary.companyID">{{subsidary.subName}}</option>
</select>
</div>
Controller:
getCompanies();
function getCompanies(){
$http.get("get.php?table=getcompanies").success(function(data) {
$scope.companies = data;
});
}
Filter:
.filter("uniqueCompanies", function() {
return function(data, propertyName) {
if (angular.isArray(data) && angular.isString(propertyName)) {
var results = [];
var keys = {};
for (var i = 0; i < data.length; i++) {
var val = data[i][propertyName];
var val2 = data[i]['companyID'];
if (angular.isUndefined(keys[val])) {
keys[val] = true;
results.push({'name':val, 'id':val2});
}
}
return results;
} else {
return data;
}
};
});
Sample Data :
[{"subID":null,"subName":null,"companyID":"1","companyName":"DWG"},
{"subID":null,"subName":null,"companyID":"2","companyName":"Vista"},
{"subID":"1008","subName":"Data Services","companyID":"3","companyName":"Medcare"},
{"subID":"1009","subName":"Companion","companyID":"3","companyName":"Medcare"},
{"subID":"1010","subName":"GBA","companyID":"3","companyName":"Medcare"},
{"subID":"1011","subName":"PGBA","companyID":"3","companyName":"Medcare"},
{"subID":"1013","subName":"Health Plan","companyID":"3","companyName":"Medcare"},
{"subID":"1014","subName":"PAISC","companyID":"3","companyName":"Medcare"},
{"subID":"1015","subName":"CGS","companyID":"3","companyName":"Medcare"}]
You are creating new objects in your filter with different properties so they will be different every time. You can you track by as mentioned by others. Since filters are executed every digest cycle you may want to set up a $watch and only create a new list of unique companies when your companies change. I actually get the 10 $digest() iterations reached error without doing this.
$scope.$watchCollection('companies', function(newValue) {
$scope.filteredCompanies = $filter('uniqueCompanies')($scope.companies,
'companyName');
});
You could also set a watch on parentCompany and create the list of subsidiaries only when it changes, as well as clear out the value you have for subsidiaryCompany:
$scope.$watch('parentCompany', function(newValue) {
$scope.subsidiaries = [];
for (var i = 0; i < $scope.companies.length; i++) {
var c = $scope.companies[i];
if (c.companyID === newValue) {
$scope.subsidiaries.push(c);
}
}
$scope.subsidiaryCompany = undefined;
});
I may not be fully understanding you're issue here, but it looks like you could filter the data when you get it. Such as ...
function getCompanies(){
$http.get("get.php?table=getcompanies").success(function(data) {
$scope.companies = data.reduce(function (prev, cur) {
// some code for skipping duplicates goes here
}, []);
});
}
Array.reduce may not be the best way to get a new array without duplicates, but that's the general idea, anyway.

How do I loop through the children of a Firebase instance

I want to know how to loop through the children of everyone. I'm using Firebase and AngularJS.
My firebase object looks like:
To me it looks like a dictionary, so from Getting a list of associative array keys I have tried
syncData('everyone').$bind($scope, 'everyone').then(function() {
var keys = $scope.everyone.$getIndex();
for (var key in $scope.everyone) {
console.log("key : " + key + " value : " + $scope.everyone[key]);
}
});
The log does contain the child objects, but it also includes all the methods. Like so
... Before this line is all the other methods.
key : $on value : function (a,c){if("loaded"==a&&b._loaded)return b._timeout(function(){c()}),void 0;if(!b._on.hasOwnProperty(a))throw new Error("Invalid event type "+a+" specified");b._on[a].push(c)} controllers.js:58
key : $off value : function (a,c){if(b._on.hasOwnProperty(a))if(c){var d=b._on[a].indexOf(c);-1!==d&&b._on[a].splice(d,1)}else b._on[a]=[];else b._fRef.off()} controllers.js:58
key : $auth value : function (a){var c=b._q.defer();return b._fRef.auth(a,function(a,b){null!==a?c.reject(a):c.resolve(b)},function(a){c.reject(a)}),c.promise} controllers.js:58
key : $getIndex value : function (){return angular.copy(b._index)} controllers.js:58
key : -JH45WOOAtnZfUZkrJb1 value : [object Object] controllers.js:58
key : -JH45YdfwptGv3y6UqyV value : [object Object] controllers.js:58
key : -JH45_zxptV_dmibyGzL value : [object Object]
Is there a way I can get just the children?
I'm doing this because my code was designed to use an array, but Firebase discourage using arrays (for values that multiple people could change). So I'm trying to loop through the firebase dictionary and copy the objects into an array on the client side. So I don't have to change too much of my code.
UPDATE: As of AngularFire 0.8.x, one can use $asArray() to obtain a sorted array of the records and this answer is no longer necessary
The correct way to iterate values in an angularFire object is by using $getIndex(). You have this in your code above, but did not utilize it in the for loop.
Since you are already using the angularFire lib (syncData is the angularFire-seed service that uses angularFire), there is no need to worry about calling $apply() or any of the other complexities of coaxing data into Angular detailed in the previous answer (which is a good response for a raw Firebase/Angular implementation).
Instead, just change your for loop to iterate the keys instead of the angularFire instance:
syncData('everyone').$bind($scope, 'everyone').then(function() {
var keys = $scope.everyone.$getIndex();
// utilizing Angular's helpers
angular.forEach(keys, function(key) {
console.log(key, $scope.everyone[key]);
});
// or as a for loop
for(var i=0, len = keys.length; i < len; i++) {
console.log(keys[i], $scope.everyone[keys[i]]);
}
});
To utilize the object in the DOM, use ng-repeat:
<ul>
<li ng-repeat="(key,user) in everyone">{{key}}, {{user|json}}</li>
</ul>
The way I do it is pretty simple, you just push all the children into an array when they arrive like this.
Everyone.on('child_added', function(snap) {
implementLogic(snap.val());
$scope.$apply();
});
function implementLogic(person) {
$scope.everyone.push(person);
if (something) {
$scope.todaysPeople.push(person);
}
if (something else) {
$scope.peopleToTest.push(person);
}
...
}
That leaves you with an array of the child objects you want.
Use ng-fire-alarm with collection: true like this:
angular.module('demo', ['ng-fire-alarm']).controller('IndexCtrl', IndexCtrl);
function IndexCtrl ($scope) {
var everyone = new Firebase(URL).child('everyone');
everyone
.$toAlarm({collection: true}) // will transform object into native array
.$thenNotify(function(everyones){ // notify you on ANY changes
$scope.everyones = everyones;
});
}

Resources