The following works:
*ngFor="let child of items || []; let i = index;"
This doesn't:
*ngFor="let child of items || []; let specialVersionOfI = someFunction(index);"
I get:
Parser Error: Unexpected token (, expected identifier, keyword, or string at column 56 in [ngFor let child of items |
What's the reasoning behind this, and is there an alterantive?
Much more readable will be version with mapping in component, sth like:
this.items.map((child, i) => {
child['specialVersionOfI'] = this.someFunction(i);
return child;
})
and then in template just {{child.specialVersionOfI}}
Use the function later within the loop, not when you assign the index.
<u>
<li *ngFor="let item of items || []; let i=index">
use function here >> {{someFunction(i)}}
</li>
</u>
You could also manipulate the array in the model and store the special index in a second parallel array, then access it within the template.
<u>
<li *ngFor="let item of items || []; let i=index">
access special index >> {{customIndices[i]}}
</li>
</u>
Related
I normally filter an array using input tag like this :
$scope.filtername = function (vname) {
//repopulate the array from a copy every time input changes
$scope.items = $scope.items_filter;
//filter array if vname is a substring from item.name
if (vname && vname.trim() != '') {
$scope.items = $scope.items.filter((item) => {
return (item.name.toLowerCase().indexOf(vname.toLowerCase()) > -1);
})
}
};
<input type="search" ng-model="namev" ng-change="filtername(namev)">
But since the user needs to filter the array while editing hundreds of items with unsaved changes, I'm not sure how to proceed. Should I use ng-show instead ? or is there a better vanilla javascript way?
do you have a better way to filter array using a search input?
One approach is to create a custom filter:
app.filter("myFilter", function() {
return function(inputArr,vname) {
//repopulate the array from a copy every time input changes
var result = inputArr.concat();
//filter array if vname is a substring from item.name
if (vname && vname.trim() != '') {
result = result.filter((item) => {
return (item.name.toLowerCase().indexOf(vname.toLowerCase()) > -1);
});
};
return result;
};
})
Usage:
<input type="text" ng-model="namev" />
<div ng-repeat="item in items | myFilter : namev">
{{item.name}} {{item.contents}}
</div>
For more information, see AngularJS Developer Guide - Creating Custom Filters
can you explain the purpose of inputArr ?
The first argument to the filter function is the array to be filtered. This way it can be used with other scope variables:
<div ng-repeat="item in items | myFilter : namev">
{{item.name}} {{item.contents}}
</div>
<div ng-repeat="o in otherItems | myFilter : namev">
{{o.name}} {{o.contents}}
</div>
When AngularJS pipes data to a filter, it invokes the filter function with the data as the first argument. Subsequent arguments come from expressions separated by colons (:).
For more information, see AngularJS Developer Guide - Using filters in view templates
This is the loop i have.
I need to use the activity object and also the next activity object in a single iteration.Any Thoughts on how to do that ?
*ngFor="let activity of course.activities; let i = index"
Thanks :)
Assuming that you don't want the last element to be displayed alone
You have to loop only through course.activities.length - 1. That is exclude the last item, because that gets displayed as the next item of last but one item.
public course = {activities : [1,2,3]};
<div *ngFor = "let a of course.activities.slice(1); let i = index">
{{course.activities[i]}}-- {{course.activities[i+1]}}
</div>
Loop is required just get the index of an element inside an array
DEMO
I'd rather create chunks of your array, then loop them and just take 0 and 1. For example, to get chunks, you can use (may be different depending on your rxjs version)
export function chunk<T>(arr: T[], chunkSize: number): Observable<T[][]> {
return Observable.from(arr).pipe(bufferCount(chunkSize)).toArray();
}
then loop in the template
<div *ngFor="let activity of chunkedActivities | async">
{{ activity[0] }} - {{ activity[1] }}
</div>
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>
I have a set of items that come in as follows. (I have no control over the variable's name)
pubDate0, pubDate1, pubDate2
They are accessed like so
<div>
<i>
{{newsData.pubDate0[0]}}
</i>
</div>
<div>
<i>
{{newsData.pubDate1[0]}}
</i>
</div>
<div>
<i>
{{newsData.pubDate2[0]}}
</i>
</div>
Is there anyway to concatenate this variable name using ng-repeat so that I do not have to write all of this repetitive code?
I have tried the following inside a ng-repeat, and many more similar combinations.
<p ng-repeat="t in getTimes(10) track by $index"> //force looped 10 times
{{(newsData.pubDate+$index+[0])}}
</p>
//Tried the following as well
{{(newsData.pubDate+$index+[0])}}
{{('newsData.pubDate'+$index+[0])}}
{{('newsData.pubDate'+$index+'[0]')}}
{{newsData.pubDate+$index+[0]}}
{{newsData.pubDate+($index)+[0]}}
{{newsData.pubDate+{{$index}}+[0]}}
{{newsData.pubDate($index)[0]}}
{{newsData.pubDate$index[0]}}
{{newsData.pubDate{{$index}}[0]}}
{{newsData.pubDate+$index+[0]}}
Running out of guesses. :(
You have two ways to access a js property:
var obj = {prop1: 'p1', prop2: 'p2'};
console.log(obj.prop1); //p1
var propName = 'prop';
var index = 1;
console.log(obj[propName + index]); //p1
Simply, use the second way:
newsData['pubDate'+$index][0];
JSFIDDLE.
You can achieve simply using new Array(10) if you know the length see the below code;
//Controller:
$scope.getTimes = function(x) {
return new Array(x);
}
<p ng-repeat="t in getTimes(10) track by $index"> //force looped 10 times
{{(newsData['pubDate'+$index][0])}}
</p>
But I would convert the data the way I wanted just before resolving the promise (or assigning the data to the scope) and if you use the data all over your application I definitely suggest that.
Assuming the below code is your service call;
var getAllNews = function() {
var deferred = $q.defer();
$http.get('/api/news')
.success(function (resultNews) {
var pubDates = []; //will be new pubDate array
for (var key in resultNews) {
//check if it is a pubDate
if (key.indexOf("pubDate") === 0) {
pubDates.push(resultNews[key]); // add it to array
delete resultNews[key]; // remove from the object
}
}
//finally assign new array as a property of result
resultNews.pubDates = pubDates;
deferred.resolve(resultNews);
});
return deferred.promise;
};
But the second approach might have some problem related to order of items as browsers new promise to return them in order (even they are).
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