AngularJS Multiple ng-repeat - javascript

I want to use ng-repeat for two different item at the same time.
I mean, ng-repeat="mylist1 in list1 && mylist2 in list2"
I need to use repeat for list1 and list2.
How can i use ng-repeat for more than one item?

I think you'd be better off creating a combined list. For example (in your controller)
$scope.lists = list1.map(function(item, i) {
return {
item1: item,
item2: list2[i] || null
};
});
Then you can just iterate over this
<div ng-repeat="item in lists">
<p>list1 item: {{ item.item1 }}</p>
<p>list2 item: {{ item.item2 }}</p>
</div>
Alternatively, you could just use $index to reference the corresponding item in list2.
<div ng-repeat="item1 in list1" ng-init="item2 = list2[$index]">

Use a function in the controller to concat them:
$scope.mergeLists = function (arr1, arr2) {
return arr1.concat(arr2);
}
Then you have:
<div ng-repeat="mylist1 in mergeLists(list1, list2)">
Fiddle

You can take advantage of ng-init
<div ng-repeat="mylist1 in list1" ng-init="i=0;lst2 = list2[i++]">
Now you can use lst2

I am assuming you have two lists as follows:
mylist1=[1,2,3,4,5]
mylist2=[6,7,8,9,0]
and you want to iterate over them such that the resulting elements are
[[1,6,], [2,7], [3,8], [4,9], [5,0]]
I'd suggest you take a look at zip in lodash.js (or underscore.js) - Amazing library of functions that HELP you comb and keep all your hair.
Example:
_.zip(['fred', 'barney'], [30, 40], [true, false]);
// → [['fred', 30, true], ['barney', 40, false]]
and in this case:
mergedLists = _.zip(mylist1, mylist2)
ng-repeat="item in mergedLists"
In your application js file, you can assign the result of zipping the two lists, then use ng-repeat to iterate over the values in the list. Note that each item in the list is also a list.

Related

Grouping over an {#each} loop in Svelte

I have an array of non-homogeneous objects that are each rendered in a loop using a <svelte:component this={type}> component, and I would like to group adjacent things that are of the same type within a div.
For example, I have some code similar to this:
<script>
let things = [
{type: A, content: "One"},
{type: B, content: "Two"},
{type: B, content: "Three"},
{type: A, content: "Four"}
];
</script>
{#each things as thing, i}
{()=>someMagicHere1(things, thing, i)}
<svelte:component this={thing.type}>
{()=>someMagicHere2(things, thing, i)}
{/each}
And I want the output to group the things like so:
<div class="group type-A">
<div class="thing type-A">One</div>
</div>
<div class="group type-B">
<div class="thing type-B">Two</div>
<div class="thing type-B">Three</div>
</div>
<div class="group type-A">
<div class="thing type-A">Four</div>
</div>
In the things array, the things are not sorted (actually, they are, but by date, unrelated to their type), but the idea is to visually group together the ones that are the same type. Ideally, I'd be able to group only certain types (like group all of type A, but type B's would remain separate), but I feel like I would be able to derive a separate solution if I could group at all. There are also more than two types; this is just a minimal sample.
In Svelte, the individual A and B components can't have partial HTML elements like this inside, because Svelte won't allow conditionals around unclosed elements:
{#if groupStart}
<div class="group">
{/if}
From within each someMagicHereX() I could output some HTML with {#html customTags} to get the DOM output that I want, but then I lose the style encapsulation and other Svelte component benefits.
What I'd really like is a more "sveltian" solution. Perhaps I need to create something new with use? Anyone have any good ideas?
Update: A key feature I seem to have left out is that any controls must ultimately bind to the original dataset. So even if the data is transformed somehow, the original data must be updated at runtime and vice-versa on any bound controls.
Not absolutely sure if the following satisfies what you are looking for, as I can't tell from your question if you want to keep the order of values untouched or if you want to reorder them by group first.
The idea is to re-arrange your data in order to loop through it in the way you want to. It's always easier to manipulate data in order to fit a layout than the other way around.
The following would turn your initial array into an array with the following structure:
[
{
cssClass: 'type-A',
values: [ /* all objects of type 'A' */ ],
},
{
cssClass: 'type-B',
values: [ /* all objects of type 'B' */ ],
},
// etc.
]
The data transformation is pretty straightforward:
let groups = things.reduce((curr, val) => {
let group = curr.find(g => g.cssClass === `type-${val.type}`)
if (group)
group.values.push(val)
} else {
curr.push({ cssClass: `type-${val.type}`, values: [ val ] })
}
return curr
}, [])
With this new data structure available, it's fairly easy to achieve the layout you had in mind:
{#each groups as group}
<div class="group {group.cssClass}">
{#each group.values as value}
<div class="thing {group.cssClass}">
{value.content}
</div>
{/each}
</div>
{/each}
Demo REPL
Edit: If you prioritize the order of objects as it stands in your initial array, the data transformation would be slightly different. Basically, you'd want to create a new 'group' every time the type changes.
Something like the following would do the trick (note that the svelte #each structure would remain the same, only the data transformation changes):
let groups = things.reduce((curr, val) => {
let group = curr.length ? curr[curr.length - 1] : undefined
if (group && group.cssClass === `type-${val.type}`) {
group.values.push(val)
} else {
curr.push({ cssClass: `type-${val.type}`, values: [ val ] })
}
return curr
}, [])
Option 2 Demo REPL

Implementing search in angular with angular.copy

I have a simple button that when clicked, it will filter a list and return the filtered list:
var originalArray = [{name: "A", number: 1},{name: "B", number: 2},....]
And here is the filter function
function filterList(filterName, filterNumber) {
var filteredList = angular.copy(originalArray);
filteredList = filteredList.filter(function(item){
return item.name === name
}
return filteredList
}
My question is am I using the right way to implement this feature? suppose that user clicks search button 10000 times ! do I have a 10000 copy of my originalArray?
As filter returns just an array, use that instead and you won't need to use angular.copy
function filterList(filterName, filterNumber) {
return originalArray.filter(function(item){
return item.name === name
}
}
That said there are better ways of doing this if you're doing this from a view. Angular already has built in tools for filtering.
As str commented - you don't need to copy the array, filter returns you a new array with only the appropriate items.
You should take a look at ngFilter - there is an example there and it looks very much what you are looking for, effortless.

Use of "for...of" in ng-repeat

Looking through ng-repeats source, it doesn't look like theres any instance of it using for-of. Is there any custom directive that does this or some other way of achieving this loop in templates to make use of iterator functions?
Class with iterator
class Cache{
constructor(items){
this.cache = {
"one" : 1,
"two" : 2
};
};
// custom iterator that turns our cache into an array
// for use in "for...of" loops
[Symbol.iterator](){
var index = 0;
// turn cache object into array of its values (underscore method)
var data = _.values(this.cache);
return {
next: function(){
if(index < data.length){
return {
value: data[index++],
done: false
};
}else{
return { done:true };
}
}
};
};
};
var myCache = new Cache();
// looping my cache in simple js would look like
for(let val of myCache){
console.log(val);
}
// 1, 2
proposed angularjs ng-repeat directive
<ul>
<li ng-repeat="item in myCache track by $index"></li>
</ul>
However that does not work as ng-repeat does not implement for...of. My question is: is there a way to get the ng-repeat directive to work nicely with iterators with minimal interface changes, or better yet, a custom directive identical to ng-repeat that is made for for...of loops?
You could just use Array.from to convert your iterable source to an array, which ngRepeat will be able to iterate:
<ul>
<li ng-repeat="item in Array.from(myCache) track by $index"></li>
</ul>
Ideally this would happen in your javascript directive/controller:
scope.myCache = Array.from(new Cache());
View:
<ul>
<li ng-repeat="item in myCache track by $index"></li>
</ul>

How can I replace an object in array that is displayed using ng-repeat?

I have an array of items that is displayed in a table using ng-repeat. When you click on an item, that item is pulled from the server and the table should then be updated with the updated item.
Function to get the updated item when clicking on an item in the table:
$scope.getUpdatedItem = function(item){
itemService.getItem(item).then(
function(updatedItem){
item = updatedItem;
},
function(error){
//Handle error
}
);
};
I'm displaying the items using:
<tr ng-repeat="item in myItems">
The problem: The item in the table is never updated.
What's the best way to update the item in the ng-repeat? Can i use "track by $index" in the ng-repeat for this? Or do I have to iterate over myItems to find the item I want to replace?
Update:
A possible solution is instead of using
item = updatedItem,
to use:
var index = $scope.myItems.indexOf(item);
$scope.myItems[index] = updateItem;
However, I feel that there should be a "cleaner" way of doing this.
There isn't a much cleaner way (then your update).
As you noticed, when you change item in your callback function you change the local reference, and not the original item in the array.
You can improve this a bit by using the $index from the ng-repeat, instead of calculating it yourself:
<div ng-click="getUpdatedItem(item, $index)"> </div>
And in your controller:
$scope.getUpdatedItem = function(item, index){
itemService.getItem(item).then(
function(updatedItem){
$scope.myItems[index] = updateItem;
},
function(error){
//Handle error
}
);
};
You can also use angular.copy instead but it's much less efficient:
function(updatedItem){
angular.copy(updateItem, item);
},
If I understand your problem properly
could something like this work?
<!-- template code -->
<table>
...
<tr ng-repeat="(index, item) in items">
<td>{{item.name}}</td>
<td>
{{item.detail}}
<button ng-if="!item.detail" ng-click="loadItem(index)">
</td>
</tr>
</table>
// Controller Code
$scope.items = [...]
$scope.loadItem = function(index){
itemService.getItemDetail($scope.items[index]).then(function(itemDetail){
$scope.items[index].detail = itemDetail;
});
};
item may start as a reference to an item in your list, but when you say:
item = updatedItem;
You reseat that binding -- you are no longer referring to the item in the list, but to the disconnected one that was returned in your promise. Either you will need to modify the item, like so:
function(updatedItem){
item.varA = updatedItem.varA
item.varB = updatedItem.varB
...
}
Or, if it gets too hairy, you might consider an item array that looks more like this:
var items = [
{ data: item1 },
{ data: item2 },
{ data: item3 }
};
At which point your update function will look like this:
function(updatedItem){
item.data = updatedItem;
},
I've just spent hours on this issue. I couldn't use the $index solution from #eladcon, as my ng-repeat also used a filter, to the index isn't correct if the rows/items are filtered.
I thought I would be able to just do this:
$filter('filter')($scope.rows, {id: 1})[0] = newItem;
but that doesn't work.
I ended up iterating the array until I found a match, and then using the $index from the iteration (not from the ng-repeat) to set the array item to the new item.
// i'm looking to replace/update where id = 1
angular.forEach($scope.rows, function(row, $index) {
if (row.id === 1) {
$scope.rows[$index] = newItem;
}
})
See here:
https://codepen.io/anon/pen/NpVwoq?editors=0011

With an angularjs ng-repeat, can I force one element to be first in the list?

I have an array, keywords, printing with an ng-repeat:
<li ng-repeat="keyword in keywords"> {{ keyword }} </li>
Which when it's sorted alphabetically would display, e.g:
Apples
Cucumbers
Daikon
Turnip
I want that when a user searches a specific keyword, that keyword gets "pinned" to the top of the list, no matter how else the list is sorted. So if the user searches "Turnip", Turnip is first in the list, while the rest remains sorted alphabetically:
Turnip
Apples
Cucumbers
Daikon
I am wondering if this functionality is possible with ng-repeat, or if I will need to construct it by inserting an additional element at the top and then filtering just that one from the array.
I'm adding another answer, as I think both could be used, but this one with sorting is much slicker!
Here, I just do a sort of the array of objs on the pinned first then on the name value as you wanted it:
<li ng-repeat="obj in array | orderBy:['pinned','name']:reverseSort ">{{ obj.name }} [<label>Pin</label><input type="checkbox" ng-model="obj.pinned" ng-click="pinObj(obj)" />]</li>
http://plnkr.co/edit/8NGW3b?p=info
Cheers
You can create a custom angular filter that handles the sorting. Then you could just use
<li ng-repeat="keyword in keywords|my_sort"> {{ keyword }} </li>
http://docs.angularjs.org/guide/filter
good luck!
I could imagine that you could have instead of just a key in your array, you could have an array of objects for example:
array {
[ key: "Turnip",
pinned: true],
[ key: "Apples",
pinned: false] }
And then, in your ng-repeat, then you could have a filter that splits out the pinned versus unpinned as required.
app.filter('pinned', function() {
return function (list, pinned, scope) {
var test = (pinned == 'true' ? true : false);
var returnArray = [];
for (var i = 0; i < list.length; i++) {
if (list[i].pinned === test) {
returnArray.push(list[i]);
}
}
return returnArray;
};
});
I've created this plunk to show what I mean above. A potentially slicker solution would be to sort your array by the pinned attribute.
http://plnkr.co/edit/onFG7K61gLLqX31CgnPi?p=preview

Categories