Multiple ng-repeat on single element - angularjs

Is this possible to achieve a code like this:-
<tr ng-repeat="data in dataArray,value in valueArray">
{{data}} {{value}}
</tr>
I am having two arrays I want to show them in single row.
PS: I am not asking for syntax. I am looking for logic to achieve this
Thanks
Like :- "http://jsfiddle.net/6ob5bkcx/1/"

You should be doing this in the controller, not in the view. Map the dataValues into a key/value pair object and reference the values array using an index. This assumes that each data key has a corresponding value key.
Controller:
var dataArray = [];
var valueArray = [];
this.repeatData = dataArray.map(function(value, index) {
return {
data: value,
value: valueArray[index]
}
});
View:
<tr ng-repeat="data in repeatData">
{{data.data}} {{data.value}}
</tr>

Does this suits your need
http://jsfiddle.net/jmo65wyn/
Your data, value array as object array
this.obj = [{data: 'a', value :true}, {data: 'b', value:true}];
And you loop like this
<div ng:repeat="o in obj">
{{o.data}} and {{o.value}}
<input type="checkbox" ng:model="o.value">
</div>

Angular ng-repeat does not support it but still you can write your own custom directive according to your requirements.
Update Section
var traverseCollection = angular.module("CollectionTraverse", []);
traverseCollection.directive("repeatCollection", function(){
return {
restrict: "A",
scope: {
model: "="
},
controller: controller: function($scope) {
var collectionList = $scope.model;
angular.forEach(collectionList, function(obj, index) {
angular.forEach(obj, function(data, index) {
});
});
}
}
});
Your scope should contains the list of your collection objects : $scope.collectionList = [dataArray, valueArray];
Usage in HTML:
-------------------------------------------
<div repeat_collection model="collectionList"></div>
This directive will be generic to traverse list of collections and yes in the above code there can be some syntactical errors because i did not run it. Its your luck.

If you have, for any reason, two arrays with the same length and where their contents are corresponding (array1[0] correspond to array2[0], ..., array1[n] correspond to array2[n]), you can use AngularJS's track by (which was introduced for the 1st time in the version 1.1.4) like this for example :
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular.min.js"></script>
<table ng-app="" ng-init="names=['Bill','Billa','Billy']; ages=['10', '20', '30']">
<tr ng-repeat="name in names track by $index">
<td>{{ name }} is {{ ages[$index] }} years old.</td>
</tr>
</table>
Hope that can help.

if you want something like a list with two or more items in the same row:
in html file:
<li ng-repeat="item in items">
<i class="material-icons">{{navItem[1]}}</i>{{navItem[0]}}</li>
in js file:
$scope.items = [
["Home", "home"],
["Favorite", "favorite"]
]

Related

Delay in ng-repeat

How can I insert delay between every ng-repeat iterations so my table will generate records slower. Is there any way to do it without using ngAnimate.
<table>
<tr ng-repeat="x in records">
<td>{{x}}</td>
</tr>
</table>
[Suggestion]
If you data is loading slow, maybe is because you have duped keys, so for test it you can try with track by $index like this
<table>
<tr ng-repeat="x in records track by $index">
<td>{{x}}</td>
</tr>
</table>
[Solution ]
If you still want to control the interaction of ng-repeat, it is best to create a dynamic variable that is manipulated as time passes, then you can have a primary array with all records
$scope.records = [
{
"name": "name1",
"data2": "data2.1",
"data3": "data3.1"
},
{
"name": "name2",
"data2": "data2.2",
"data3": "data3.2"
},
{
"name": "name3",
"data2": "data3.3",
"data3": "data3.3"
}
];
Then you could use setTimeout to call a function that passes data from the primary array to another final array, an index per interaction
//start to proccess
setTimeout(function(){$scope.Result();},1000);
//Here pass data from Records to FinalResult in each interaction
$scope.Result=function(){
dif=$scope.records.length-$scope.FinalResult.length;
currentRow=$scope.FinalResult.length;
if(dif>0){
$scope.FinalResult.push($scope.records[currentRow]);
}
if($scope.records.length>$scope.FinalResult.length){
setTimeout(function(){$scope.Result();},1000);
}else{
console.log('Finish Load');
$scope.FinishRender=true;
}
//refresh
$scope.$apply();
}
And finally deliver this variable with another function...
//get the finish Array
$scope.getFinalResult=function(){
return $scope.FinalResult;
}
and HTML
<body>
<div ng-controller="recordsCtrl">
<table style="border:1px solid black">
<tr ng-repeat="x in getFinalResult()">
<td>{{x.name}}</td>
<td>{{x.data2}}</td>
<td>{{x.data3}}</td>
</tr>
</table>
<div ng-if="FinishRender" style="color:red;font-weight:bold">Data Loaded!!!</div>
</div>
</body>
Please feel free to check a solution in punkler
[Optional]
Also you could use a directive to control the last interaction like this
myApp.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
console.log(element);
if (scope.$last === true) {
console.log('Finish Load');
}
}
}
});
and html
<table>
<tr ng-repeat="x in getFinalResult()" on-finish-render="onFinishRender">
....
...
</tr>
</table>
note:I'm not really sure but I think it's possible to capture every interaction with this method
A possible solution in your situation could be to take the source array and populate the ng-repeat array in increments with a delay using _.chunk and $timeout, as such:
index.html
<table>
<tr ng-repeat="x in records track by $index">
<td>{{x}}</td>
</tr>
</table>
appCtrl.js
$scope.sourceData = [data, data, data];
$scope.records = [];
/**
*#param source (array): the array with the data used to populate the ng-repeat array
*#param target (array): the array to which ng-repeat points
*#param delay (integer): the render delay, in milliseconds
*#param renderSize (integer): the amount of list items to render between each delay
*
**/
function delayedRender(source, target, delay, renderSize) {
var promise = $q.resolve();
function scheduleRender(partial) {
Array.prototype.push.apply(target, partial);
// the timeout will ensure that your next render won't occur before the delay
return $timeout(function(){}, delay);
}
// _.chunk is a Lodash function that takes an array and chops it into smaller chunks.
// 'renderSize' is the size of these chunks.
var partials = _.chunk(source, renderSize);
var next;
// here we schedule renders to occur only after
// the previous render is finished through the use of $q promises
_.forEach(partials, function(partial) {
next = scheduleRender.bind(null, partial);
promise = promise.then(next);
});
}

Bind data from pagination to filter in Angular

I have a simple pagination with letters:
<div ng-controller="DataController">
<ul class="pagination">
<li ng-repeat="letter in data_letters">
{{letter}}
</li>
</ul>
<table class="table table-striped">
<tbody>
<tr>
<th>Name</th>
<th>State</th>
</tr>
<tr ng-repeat="person in persons | startsWithLetter:letter">
<td>{{person.Name}}</td>
<td>{{person.State}}</td>
</tr>
</tbody>
</table>
</div>
What is the easiest way to bind the letter (on which we clicked at the paginaton) to the table filter.
Here is a fully functional plunkr: http://plnkr.co/edit/Trr5LzrcMfZqonD0jvjX?p=preview
I have everything implemented already. It is just the data-binding which is missing. Any ideas?
One issue with your code is that you are unnecessarily injecting $scope into your filter:
app.filter('startsWithLetter', function($scope) {
should be
app.filter('startsWithLetter', function() {
The other issue is using 'C-D' as a filter value. I would change $scope.data_letters to be an array of objects that contain a key/value pair like this:
$scope.data_letters = [{
key: 'A',
value: "A"
}, {
key: "B",
value: "B"
}, {
key: ['C', 'D'],
value: "C-D"
}];
Then change your check in the filter to evaluate all keys, like this:
angular.forEach(letters, function(letter, key) {
if (itemName.startsWith(letter)) {
filtered.push(items[i]);
}
});
You can see it working here where I forked your plunk.
Updated your plunk here
Problem was that your filter was not recognized by angular.
Also, I would suggest using
Chrome Developer Tools
it shows useful debug information
app.filter('startsWithLetter', function() {
console.log("Inside startsWithLetter");
return function(items, letter) {
console.log("Inside Filter: Starts with " + letter);
var filtered = [];
for (var i = 0; i < items.length; i++) {
var itemName = items[i].Name;
if (itemName.startsWith(letter)) {
filtered.push(items[i]);
}
}
return filtered;
};
});

Angular $watch just parent object instead of its multiple children

I have a div, listing properties of the object POI = {"address":"Martinsicuro (TE), Italy", "phone":"+39 333 45657", "website':'http://mysite.it"}. The object POI si owned by a Service. The directive's controller has the function getPoi() that gets the POI from the service, and returns it to the directive.
My current HTML is something like this:
<table ng-controller="Controller as ctrl">
<tr> <!-- address -->
<td>{{ctrl.getPoi().address}}</td>
</tr>
<tr> <!-- phone -->
<td>{{ctrl.getPoi().phone}}</td>
</tr>
<tr> <!-- website -->
<td>
<a ng-href="{{ctrl.getPoi().website}}">
{{ctrl.getPoi().website}}
</a>
</td>
</tr>
</table>
The controller
.controller('Controller', function(CurrentPoiService)
{
this.getPoi = function()
{ return CurrentPoiService.POI; }
}
The service
.service('CurrentPoiService', function()
{
this.POI = {"address":"Martinsicuro (TE), Italy", "phone":"+39 333 45657", "website':'http://mysite.it"}
}
In this way I am adding 3 watchers. Is there a way to add just 1 watcher, since it's the same parent object? Here it is a JSFiddle
Thank you
[UPDATE 1]
This is the (still not working) JSFiddle using the solution proposed by #Charlie
[UPDATE 2]
This is the working JSFiddle
As Claies has mentioned in a comment, you should never call your data from
the view through a function this way.
In this scenario you can create a watch for the POI object with the objectEquality argument true to watch the properties of the object in a single $watch. Then find your elements inside the listener and change the value in the way you want.
$scope.$watch('POI', function(){
//Assume that $scope.propertyIndex is the current property to display
angular.element($document[0].querySelector("#myTd" + $scope.propertyIndex)).html(POI.address);
angular.element($document[0].querySelector("#myTd" + $scope.propertyIndex)).html(POI.phone);
//etc...
}, true);
You have a better control this way. But please keep in mind that this method is not suitable if POI is a complex object.
UPDATE:
Here is a working example of showing a random number every second using a watch and a factory. You should be able to learn from this and apply it to your project.
myApp.controller('myController', function($scope, dataSource) {
$scope.poi = {rand: 0};
$scope.$watch('poi', function() {
$('#rand').html($scope.poi.rand);
}, true);
dataSource.open($scope);
});
myApp.factory('dataSource', function($interval) {
return {
open: function(scope){
$interval(function() {
scope.poi.rand = Math.random();
}, 1000);
}
}
});
Try inside your controller :
$scope.POI = ctrl.getPoi();
HTML :
<tr> <!-- address -->
<td>{{POI.address}}</td>
</tr>
<tr> <!-- phone -->
<td>{{POI.phone}}</td>
</tr>

ng-repeat not working with set data structure

I'm trying to use ng-repeat with a field that is a Set in javascript https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Set. I tried use values(), keys(), entries() method from Set with ng-repat but is not working, anybody know a way, hack for this work?
Resumed code:
function Campaign () {
this.site = {
type : '',
domains : new Set()
};
}
controller:
.controller('CampaignStep1Ctrl', ['$scope', 'languages', 'geoLocationService',
function ($scope, languages, geoLocationService) {
var vm = this;
vm.campaign = $scope.campaign;
}
html:
<tr class="ui-widget-content ui-datatable-empty-message" ng-repeat="domain in vm.campaign.site.domains">
<td colspan="2">{{domain}}</td>
</tr>
Note: If i use console.log() and print domains attribute, it prints, i think that problem only is with ng-repeat.
An ES6 option -
return [...domains];
I used this solution
var devSet = new Set();
devSet.add(""One);
devSet.add(""Two);
...
$scope.deviceTypes = Arrays.from(devSet);
And in html
<ul>
<li ng-repeat="type in deviceTypes">
<p>{{type}}</p>
</li>
</ul>
This is probably not the best solution but you can make it work like this:
View:
<p ng-repeat="value in getSetAsArr(set)">{{ value }}</p>
Controller:
$scope.getSetAsArr = function (set) {
var arr = [];
set.forEach(function (value) {
arr.push(value);
});
return arr;
};

Re-evaluating controller functions from within ng-repeat

I have the following angular code:
<tr ng-repeat="vm in ...">
<span ng-if="myLookupFunc(vm)"> {{myLookupFunc(vm)).label}}, {{myLookupFunc(vm).uuid}}
<span ng-if="!myLookupFunc(vm)">-</span>
</tr>
As you can see myLookupFunc is called 4 times for a single item.
How can this be optimized so that it is called only once for a given 'vm' instance?
I did try to use ng-init at 'tr' level but it doesn't re-evalute after properties of 'vm' change -- and it is expected, according to the documentation ng-init should not be used for such cases.
So what is a proper way in angularjs to accomplish this?
Please check the below code for better optimization.
<tr ng-repeat="vm in ..." ng-init="lookupData=myLookupFunc(vm)">
<span ng-if="lookupData"> {{lookupData.label}}, {{lookupData.uuid}}
<span ng-if="!lookupData">-</span>
</tr>
ng-init will run once on the start of the ng-repeat. That's why it doesn't change for you.
You have to use the controller to fetch the variable data, but you can improve it.
One way is to do this on your controller:
function lookup(vm, firstRun) {
if (firstRun) {
$scope.lookupVar = myLookupFunc(vm);
}
else {
return $scope.lookupVar;
}
}
and then you can keep your html code almost the same:
<tr ng-repeat="vm in ...">
<span ng-if="lookup(vm, true)"> {{lookup(vm)).label}}, {{lookup(vm).uuid}}
<span ng-if="!lookup(vm)">-</span>
</tr>
A better solution would be to just keep one span in the HTML and then do:
<tr ng-repeat="vm in ...">
<span>{{getVmText(vm)}}</span>
</tr>
And define a function getVmText on the controller that checks for VM value and returns the text. I believe this is the preferable way.
Use a data model and update it whenever necessary (even when "external events" occur). This way you don't have to bother with "re-evaluating controller functions", it's just pure angular data binding.
Example:
$scope.vms = ["id1", "id2", "id3"];
// this var will hold your data model (it could also be on the $scope, but for
// demo let's leave it like this)
var data = {
"id1": {
uuid: "123-123",
label: "label 1"
},
"id2": {
uuid: "456-456",
label: "label 2"
},
"id3": {
uuid: "abc-abc",
label: "label 3"
}
};
$scope.myLookupFunc = function(id) {
return data[id];
};
And then you can use it like this:
<div ng-repeat="vm in vms" ng-init="lookupData=myLookupFunc(vm)">
<span ng-if="lookupData"> {{lookupData.label}}, {{lookupData.uuid}}</span>
<span ng-if="!lookupData">-</span>
</div>
Plunker
OK, after considering the suggestions above, I found what I think is the easiest solution.
We just need to run
{{lookupData = myLookupFunc(vm)}}
and after that we can reference 'lookupData' variable. If we just run the code above inline, it will also evaluate and show the result (as JSON stringified text) inline, which is not what we want. So I ended up creating a dedicated directive which is a noop directive:
app.directive("ngAssign", function () {
return {
restrict: 'A'
};
});
Then one can just say:
<tr ng-repeat="vm in ..." ng-assign={{lookupData = myLookupFunc(vm)}}>
<span ng-if="lookupData"> {{lookupData.label}}, {{lookupData.uuid}}
<span ng-if="!lookupData">-</span>
</tr>
Full plunker: http://plnkr.co/edit/34DwCGR7Po8zg2mnCAtB?p=preview

Resources