Why I get error Error: [ngRepeat:dupes] in Angular JS? - angularjs

I use ng-repeat:
<option ng-selected="key == formData.city" ng-repeat="(key, value) in data.cities | orderBy:value" value="{{key}}">{{value}}</option>
data.cities is array.
Also I have method that gets array of cities from AJAX response and sets it to exists array $scope.data.cities:
request.success(function (data) {
var arr = []
angular.forEach(data.res, function(item) {
arr[item.id] = item.name;
});
$scope.data.cities = arr;
});
Why after response I get error: [ngRepeat:dupes]?

You have to use track by $index with your ng-repeat to avoid duplicates:
ng-repeat="(key, value) in data.cities track by $index | orderBy:value"
https://docs.angularjs.org/error/ngRepeat/dupes
Update:
You memory leak might be a reason of using array:
var arr = [];
For example, if your cities.id looks like 1001 this will produce an array with 1000 empty items and one with your city.
In your situation I would recommend to use an Object instead of Array:
request.success(function (data) {
var obj = {};
angular.forEach(data.res, function(item) {
obj[item.id] = item.name;
});
$scope.data.cities = obj;
});
Also, you can replace ng-repeat with ng-options here:
<select ng-model="formData.city" ng-options="key as value for (key, value) in data.cities">

Related

Array with 2 properties, AngularJs

I need to save 2 Strings into an array like this:
array[0].name = "William"
array[0].dni = "00112233Z"
so i can use ng-repeat:
<ul>
<li ng-repeat="item in array">
{{item.name}}
{{item.dni}}
</li>
</ul>
But I don't know how to declare it in Angular, I keep getting this error no matter how i try: TypeError: Cannot set property 'name' of undefined.
Here's the code where I'm getting the data:
$scope.array=[];
$scope.initial = function () {
$http.get('data/people.json').success(function (data) {
$scope.jsonData = data;
for(var i=0; i<$scope.jsonData.persons.length;i++){
$scope.array[i].name=$scope.jsonData.persons[i].person.nombre;
$scope.array[i].dni=$scope.jsonData.persons[i].person.dni;
}
});
};
Any help would be appreciated.
Regards.
You have to set an object first inside the array to assign properties like:
$scope.array=[];
$scope.initial = function () {
$http.get('data/people.json').success(function (data) {
$scope.jsonData = data;
for(var i=0; i<$scope.jsonData.persons.length;i++){
$scope.array[i] = {};
$scope.array[i].name=$scope.jsonData.persons[i].person.nombre;
$scope.array[i].dni=$scope.jsonData.persons[i].person.dni;
}
});
};
Otherwise array[i] is undefined because is an empty array

Filtering an array with an array in AngularJS

I tried filtering a group of checkboxes with an array like so:
<ion-checkbox ng-repeat="user in users | filter: {id: group.members}" ng-model="user.checked">{{user.info.name}}</ion-checkbox>
where group.members is an array of user.id and it just doesn't show anything.
users Array:
[12345,123456]
group.members Array:
[12345]
I'm trying to accomplish not showing the group.members in the list of users, because in this case a user is trying to invite another user to the group and why invite someone who's already a member?
I tried creating my own filter, but its just a mess:
.filter('existingMembers', function() {
return function(users, members) {
return users.filter(function(user) {
for (var i in user.id) {
if (members.indexOf(user.id[i]) != -1) {
return;
}
}
return user;
});
};
})
After some messing around, this is the solution. See the plunkr for a working example.
I think this should do the trick:
Template:
<ion-checkbox ng-repeat="user in users | filter: excludeMembers:group.members" ng-model="user.checked">{{user.info.name}}</ion-checkbox>
Angular filter:
app.filter('excludeMembers', function(){
return function(users, members){
return users.filter(function(user){
return members.indexOf(user.id) === -1;
});
}
})
Long explanation
The filter takes the array you are filtering against as a first parameter as a default, then with the colon notation (:) you can supply optional arguments, in your case: the group. The filter should return a function, which will be run. The return value should be a filtered array. We also use a native javascript filter function (confusing, whoa) to check against the group array.
By utilizing a look up table (LUT) object you may do some filtering like this with pure JS.
var gmems = [12345, 567890],
users = [12345,123456,432567,1234987,567890],
lut = gmems.reduce((p,c) => {p[c]=true; return p},{}),
res = users.filter(e => !lut[e]);
document.write('<pre>' + JSON.stringify(res, 0, 2) + '</pre>');

Updating selected array element after deleting the element from a filtered ng-repeat

I have a list of users where you click on a user to select it and put the element into an object $scope.selectedMember when selected. I'm using ng-repeat with a search box filter. The important thing is that $scope.selectedMember should always be populated with a member.
Problem is that i'm trying to overcome:
- splicing the last user out needs to automatically select the last user in the filtered array, even if it's filtered some members out with the search.
HTML
<div ng-app="myApp" ng-controller="MainCtrl">
<input ng-model="search"></input>
<div ng-repeat="(key, member) in members | filter:search | orderBy :'-name'">
<li ng-class="{active: retActive(key)}"
ng-click="selectThis($index)">
name: {{member.name}} key: {{key}}
<button ng-click="deleteThis(key)">delete</button>
</li>
</div>
Selected member name: {{selectedMember.name}}
</div>
JS
angular.module('myApp', []);
function MainCtrl($scope) {
$scope.members = [
{"name":"a", "viewIndex":0},
{"name":"b", "viewIndex":1},
{"name":"bb", "viewIndex":2},
{"name":"bb", "viewIndex":3},
{"name":"c", "viewIndex":4},
{"name":"d", "viewIndex":5},
{"name":"e", "viewIndex":6}
];
$scope.activeIndex = 0;
$scope.selectedMember = $scope.members[0];
$scope.selectThis = function (index) {
$scope.activeIndex = index;
//put array member into new object
$scope.selectedMember = $scope.members[index];
}
//splice the selected member from the array
$scope.deleteThis = function (index) {
$scope.members.splice(index, 1);
$scope.selectThis(index);
}
//angular copy and push member to the array
$scope.duplicateThis = function (index) {
}
// change class if member is active
$scope.retActive = function (index) {
return $scope.activeIndex == index;
}
}
CSS
.active {
color:blue;
}
Link to JSFiddle
One problem I see is that you are passing $index to selectThis($index). When you filter data, the loop's $index no longer represents the actual index of an item in the array - so you should pass selectThis a key, not $index.

Filter a ng-repeat with values from an array

I have this table with a ng-repeat.
ng-repeat="project in projects"
I have a property in project, prj_city. I'd like to filter this value.
I can do this with:
ng-repeat="project in projects | filter={prj_city: <value>}
But I want the <value> to be an array with multiple cities instead of a string. Is there any easy way to do this or do I have to do this filter manually in my controller?
Most likely a custom filter in the controller, should be easy enough tho:
var filteredCities = ["LosAngelos", "etc.."];
$scope.arrayFilter = function(project) {
for (var i = 0; i < filteredCities.length; i++) {
if (filteredCities[i] == project.prj_city)
return true;
}
return false
}
And the call:
ng-repeat="project in projects | filter: arrayFilter"
You need to create a filter function on your controller. Something like:
$scope.filteredCities = function(city) {
return ($scope.userFilteredCities.indexOf(city) !== -1);
};
$scope.userFilteredCities;//List of filtered cities
Define the following function in your controller:
// use a map for faster filtering
var acceptedCityMap = {};
angular.forEach(acceptedCities, function(city) {
// case insensitive search. But you're not forced to
acceptedCityMap[city.toLowerCase()] = true;
});
$scope.isProjectedAccepted = function(project) {
// case insensitive search. But you're not forced to
return acceptedCityMap[project.prj_city.toLowerCase()];
}
And then in your view:
ng-repeat="project in projects | filter:isProjectAccepted"

AngularJS Filter-- two filters with an 'or' relationship

Just wondering: in AngularJS, is there a native way to filter such that it has an 'or' relationship instead of the 'and' ?
for example:
<tr ng-repeat="account in accounts | filter1 *OR* filter2 *OR* filter3" >
so if any of the filters match, it returns that object. As of right now, all three have to pass in order for it to show up.
Thanks a lot,
Y
You could write a custom filter. It could accept the names of other filters and check each one of them to see if each item in the accounts array is a valid match against any of the filters. The following example shows kind of the idea, thought I haven't actually run the code to see if it works, so forgive me for any typos:
app.filter('anyOf', function($filter) {
return function(){
var array = arguments[0];
var result = [];
angular.forEach(array, function(item){
for(var i=1; i<arguments.length; i++){
var filter = $filter(arguments[i]);
if(filter([item]).length){
result.push(item);
break;
}
}
});
return result;
}
});
And then you would use it like so:
<tr ng-repeat="account in accounts | anyOf:'filter1':'filter2':'filter3'" >
I think it should work, though its not very efficient because its taking every item in the input array and checking it against any of the filters. But that might work if your accounts array wasn't super long.
You can use $filter to call filters in your own code, so you can pass the filter names as arguments to a new filter to merge the results (JSFIDDLE).
app.filter('merge', function($filter) {
return function(input, extra) {
var result = [];
for (var i = 1; i < arguments.length; i++) {
angular.forEach($filter(arguments[i])(input), function(item) {
if (result.indexOf(item) < 0) {
result.push(item);
}
});
}
return result;
};
});
And you can pass arguments to your filter like this:
<ul>
<li ng-repeat="item in items|merge:'color':'rating'">
{{item.rating}}: {{item.color}}
</li>
</ul>
If you wanted to pass arguments to the filter you're merging, you could parse them yourself in the merge filter:
<h3>Merge brown and rating >= 5</h3>
<ul>
<li ng-repeat="item in items|mergeWithArgs:['color','brown']:['rating',5]">
{{item.rating}}: {{item.color}}
</li>
</ul>
Code:
app.filter('mergeWithArgs', function($filter) {
return function(input) {
console.dir(arguments);
var result = [];
for (var i = 1; i < arguments.length; i++) {
var params = arguments[i];
var filter = $filter(params[0]);
// remove filter name, prepend input
params.splice(0, 1, input);
angular.forEach(filter.apply(this, params), function(item) {
if (result.indexOf(item) < 0) {
result.push(item);
}
});
}
return result;
};
});

Resources