Angular merge nested arrays - angularjs

I have a dataset that looks like this:
[
{
'title' : 'cats',
'names' : [
'felix',
'tom',
... more names
]
},
{
'title' : 'dogs',
'names' : [
'fido',
'rover',
... more names
]
},
{
... More animal types
]
And I would like to have the following:
<p ng-repeat='name in names'>{{ name }}</p>
But, to do that I really need to at some stage set
$scope.names = ['felix', 'tom', 'fido', rover'];
My question is: is there an 'Angular' way to merge arrays or take content from multiple places from one object? Or do I need to use a for loop with a concat function to create the array I use?

Sure, just defined names based on your data, demo.
$scope.names = function() {
return Array.prototype.concat.apply([], animals.map(function(animal) {
return animal.names;
}));
};
Then use that method in your view
<p ng-repeat='name in names()'>{{ name }}</p>
Or we could assume the list of animals won't change, and use a library like lodash for readability, demo.
$scope.names = _.chain(animals)
.pluck('names')
.flatten()
.value()

It is not likely to be a core functions for such feature in javascript nor in angular as angular is just mvc framework on javascript. function you need is not common enough for javascript, or specific enough for mvc

Related

How to fetch particular objects by their attribute from Array?

I need to get objects with special attribute "type" out of Array. These objects I am going to assign to scope. How can I do this?
The following approach didn't work out for me.
$scope.vendors = {}
$scope.clients = {}
$scope.loadCounterparties = function() {
Counterpartie.query(function(response) {
$scope.vendors = response.type.Vendor;
$scope.clients = response.type.Client
});
};
Response objects look like this
Thanks in advance!
Angular doesn't have something dedicated for this. You need to filter the arrays via plain java script. However you can try using a 3rd party library by the name underscore.js.
It adds many usefull functions like "where":
_.where(list, properties)
Looks through each value in the list, returning an array of all the values that contain all of the key-value pairs listed in properties.
_.where(listOfPlays, {author: "Shakespeare", year: 1611});
=> [{title: "Cymbeline", author: "Shakespeare", year: 1611},
{title: "The Tempest", author: "Shakespeare", year: 1611}]
Here is a link to the library's page
http://underscorejs.org/#where
can use angular forEach but I would use lodash
// assuming one array and two search arguments i.e. client and vendor
var data = response;
$scope.loadCounterparties = _.filter(data, {type: 'Vendor', type: 'Client'});

Multiple array grouping - ngRepeat

I get my data like below;
[
{
// restaurant details here,
"restaurant_class": {},
"city": {},
"location": {},
"menu_categories": [],
"menu_items": [],
"menu_modifier_groups": [],
"menu_modifier_items": []
}
]
How can I use ng-repeat to group by menu_categories? menu-items is a child of menu_categories
Basically I want to display menu_items, menu_modifier_groups, menu_modifier_items grouping them by menu_categories
i think you can sort (generally manipulate) your array using a filter and then do the ng-repeat, and it won't necessarily change the original data. so inside the html you can do it like this: ng-repeat="item in myArray | myFilter: otherData".
and defining your filter in the js file like:
.filter('myFilter', function () {
return function(input, otherData) {
var output = [];
//some changes using "if"s and other things depending on your sorting algorithm
return output;
};
})
if you have problem with the sorting algorithm, it would help if you could give us the real data...
hope this helps

Angular ng-repeat with constant & Restangular

I'm working with a weird data model (no way around it at this point). I'm using restangular to make a rest call to get back a single resource object
Normally, the resource object returned by restangular is just whatever I set my
$scope.resource = response to and I can do resource.name , resource.id in the view/template, etc..
Except this group of resources instead of returning the key, value pairs in the response object, it returns an object within an object like so
resource1: {name: 'value', stuff: 'value', etc}
which is fine because then I would just set my $scope.resource = response.resource1 in my controller
except the problem is, is that there's 5 different kind of resource object names so if I make a resource by id call I might get back resource2, resource4, resource1, etc. so setting my $scope.resource = response.resource1 would only work when I get resource1.
My first attempt to solve this was to just use ng-repeat in which I set
<ul ng-repeat="(resource, value) in resource">
<li class="list-group-item">
Name:
<span class="pull-right"> {{ resource.name }} </span>
</li>
</ul>
which works great except because restangular returns all this extra stuff it's looping through each object it's repeating a bunch of blank html stuff if that makes sense.
My other thought was to try making a constant and make an object that has all 5 resources there and my ng-repeat would only populate based off that constant object (ie: it would check for those strings "resource1, resource2, etc" and if it's there then it will populate the template. But I'm not exactly sure how to do this.
Any other options or are there ng-repeat features i'm just not utilizing? any Help thanks
Here's the example I will be working from. Initially your incoming data looks something like this I believe...
$scope.data = [
{
resource1 : { name: 'r1' }
},
{
resource2 : { name: 'r2' }
},
{
resource2 : { name: 'r2' }
}];
When you receive the data you can normalize it by flattening it out into the following structure...
$scope.normalized = [
{ name : 'r1' },
{ name : 'r2' },
{ name : 'r2' }
];
Or you can add a common field for the object "type"
$scope.expanded = [
{
type : 'resource1',
resource1 : { name: 'r1' }
},
{
type : 'resource2',
resource2 : { name: 'r2' }
},
{
type : 'resource2',
resource2 : { name: 'r2' }
}];
Or you can normalize but retain type data...
$scope.normalizedType = [
{ type : 'resource1', name : 'r1' },
{ type : 'resource2', name : 'r2' },
{ type : 'resource2', name : 'r2' }
];
Normalizing upon retrieval of the data is probably your best bet. The question then becomes do you need to retain the objects type information.
So another solution I came up with was put all the resource key names into a list
resources = ['resource1', 'resource2', 'resource3', 'etc..']
and in my restangular service promise I just checked for which resource number it would be with a for loop like this
return ResourceRestangular.all('resource').get(resourceId).then(function(response){
for (i = 0; i < resources.length ; i++){
if (resources[i] in response){
self.resource = response[resources[i]];
}
}
return self.resource;
No need for ng-repeat anymore!

In AngularJS, is it possible to filter inside of a specific element of an array using only a view?

I've got some data like this:
people = [
{
names: [
{first: "Joe", last: "Smith"},
{first: "Joseph", last: "Smith"},
...
]
},
...
]
In other words, an array of objects with an array of names. For example, a person could be called "Joe Smith" or "Joseph Smith". How can I use a filter to only search the first element of names? IE: If I typed in "Jo" or "Smith" it would find the first person. But, if I typed in "seph" it wouldn't.
I've been looking at the examples on this page, but there isn't really an example of filtering inside arrays. Here's what I've tried but it gives me an error:
<input ng-model="search.names[0].$">
TypeError: Cannot set property '$' of undefined
Working Code
Input HTML
<input ng-model="searchTerm">
Results
<tr ng-repeat="p in people | filter:searchFunc">...</tr>
Controller
$scope.searchFunc = function(person) {
var firstPersonsName = [person.names[0]]; // Wrapping in array since the 'filter' $filter expects an array
var matches = $filter('filter')(firstPersonsName, $scope.searchTerm); // Running firstPersonsName through filter searching for $scope.searchTerm
return matches.length > 0;
}
Plunker Demo
Answer to the question in your title
I played around with filter and it doesn't seem like you can go beyond one level deep when specifying a pattern object for it e.g. ng-model="search.names" works but ng-model="search.names.otherVal" doesn't.
Also, even if filter supported going beyond one level deep, you still wouldn't be able to do ng-model="search.names[0]". This is because filter expects the input to be an array, but the elements of your names array are all objects e.g. people[0].names[0] == {first: "Joe", last: "Smith"} so filtering will never work.
The only way to do what you are asking purely through the view and no extra code in your controller is to just create your own custom filter that handles your case.
Would this do the trick?
Say that your input is
<input ng-model="search.name" type="text">
Then, you can display results like this:
<div ng-repeat="person in people[0].names | filter: alias">...</div>
Controller:
$scope.alias = function (object) {
if (object.first.match(new RegExp($scope.search.name))) {
return true;
}
return false;
};

How to sort object data source in ng-repeat in AngularJS?

Suppose I have the following users:
$scope.users = {
"2": {
email: 'john#gmail.com',
name: 'John'
},
"3": {
email: 'elisa#gmail.com',
name: 'Elisa'
}
}
I would like to create a <select> with the following options:
<option value="3">Elisa</option>
<option value="2">John</option>
In other words, users should be sorted by name.
I tried the following using the (key, value) in expression syntax, but it doesn't work:
<option ng-repeat="(user_id, user) in users | orderBy:'user.name'"
value="{{ user.id }}">
{{ user.name }}
</option>
Live example here.
What am I missing?
Please do not suggest solutions with ng-options as I use ui-select2 which is incompatible with ng-options.
While you would see this in the thread the author's answer references in a link, I thought it would be good to put up one of the mentioned workarounds up on SO:
It is true that this is technically not implemented, but there is an easy work around if you're willing to change your data model slightly: use a custom filter to generate an array of the object's properties (without replacement). If you add the key field to the objects ("id" in the above case), you should be able to get the behavior your looking for:
app.filter("toArray", function(){
return function(obj) {
var result = [];
angular.forEach(obj, function(val, key) {
result.push(val);
});
return result;
};
});
...
$scope.users = {
1: {name: "John", email: "john#gmail.com", id: 1},
2: {name: "Elisa", email: "elisa#gmail.com", id: 2}
};
Here's the ng-repeat directive that could be used:
<option value="{{user.id}}" ng-repeat="user in users | toArray | orderBy:'name'">{{user.name}}</option>
And here's the plunkr.
Notice that the orderBy filter takes name as its parameter and not user.name.
Unfortunately, adding the id property to your objects does create potential for mismatch with it's key in the containing object.
In the link you mentioned in your answer, there are also proposed solutions that create the id property in the user objects on the fly, but I feel like this approach is a little less messy (at the cost of introducing data replication).
OK, I found the answer:
It is not implemented yet :(
Adding to the accepted answer and seeing your data format, you should transform your data as
app.filter('myFilterUsers',function(){
return function(data)
{
var newRes = [];
angular.forEach(data,function(val,key){
val["id"] = key; //Add the ID in the JSON object as you need this as well.
newRes.push(val);
});
return newRes;
}
});

Resources