Is it possible to have 2 datasources for ng-repeat? - angularjs

Is it possible to have
<tbody ng-repeat="Emp in Employees" ng-repeat="Dept in Department">
<td> <span>{{Emp.id}}</span></td>
<td> <span>{{Emp.name}}</span></td>
<td> <span>{{Dept.Deptid}}</span></td>
</tbody>
That means two different sources.... It is for a composite UI requirement. If not available, what is the alternate way?
I am not looking for
<tbody ng-repeat="Emp in Employees" >
<td> <span>{{Emp.id}}</span></td>
<td> <span>{{Emp.name}}</span></td>
</tbody>
<tbody ng-repeat="Dept in Department">
<td> <span>{{Dept.Deptid}}</span></td>
<td> <span>{{Dept.Deptname}}</span></td>
</tbody>
Something similar to LINQ in C# like
var x = (from a in AList
join b in BList on a.item = b.item
select new{....});

You can do it by merging two arrays
app.controller('MainCtrl', function($scope) {
$scope.Employees = [{
'id':1,
'name':'rachit1'
},
{
'id':2,
'name':'rachit2'
}];
$scope.Department=[{
'Deptid':'1D',
'Deptname':'dep1'
},
{
'Deptid':'2D',
'Deptname':'dep2'
}];
$scope.result=merge($scope.Employees,$scope.Department);
function merge(obj1,obj2){ // Our merge function
var result = {}; // return result
for(var i in obj1){ // for every property in obj1
if((i in obj2) && (typeof obj1[i] === "object") && (i !== null)){
result[i] = merge(obj1[i],obj2[i]); // if it's an object, merge
}else{
result[i] = obj1[i]; // add it to result
}
}
for(i in obj2){ // add the remaining properties from object 2
if(i in result){ //conflict
continue;
}
result[i] = obj2[i];
}
return result;
}
});
HTML:-
<body ng-controller="MainCtrl">
<table>
<tbody ng-repeat="Emp in result">
<td> <span>{{Emp.id}}</span></td>
<td> <span>{{Emp.name}}</span></td>
<td> <span>{{Emp.Deptid}}</span></td>
<td> <span>{{Emp.Deptname}}</span></td>
</tbody>
</table>
</body>
Plunker

The answer is :Can't. Because may the looping count is difference.
can you write a looping statement on a another one looping statement?
for (var i=0;i<Empcount;i<DeptCount;i++)
{
// code
}
Is it impossible, Same as what the answer for your question. You can think the result.

I know that it is not really an answer, but I must to say this: your data organised incorrectly. Data integrity is violated.
But if you can't change the source structure for some reason try to combine that data in the controller:
$scope.employees = employees.map(function (employee, index) {
employee.department = departments[index];
return employee;
})
Then use it in the ng-repeat:
<tbody>
<tr ng-repeat="employee in employees">
<td>{{ employee.id }}</td>
<td>{{ employee.name }}</td>
<td>{{ employee.department.deptId }}</td>
</tr>
</tbody>
NOTES:
Do not repeat <tbody> tag.
Apply the technique if Departments.length === Employees.lengh and the order is correct.
Do not capitalize not constructors. Rename Departments to departments, Employees to employees.
If you don't have any styles on td > span then remove unnecessary span's

Related

ng-repeat or something similar on two arrays

I recently started learning angular js. I have following object ResultRow:
ResultRow = function ( cars, prices) { //cars and prices are arrays
this.prices = prices;
this.cars = cars;
};
for example,
ResultsRow=new ResultsRow(["Ford","Honda","Nissan"],[20,22,18]);
I want to display the ResultsRow in table as:
Car Price
Ford 20
Honda 22
Nissan 18
So basically print car[0],price[0] in one table row, car[1], price[1] in next table row and so on.
I tried following using ng-repeat:
<tr>
<td ng-repeat="car in ctrl.ResultsRow.cars">
{{car}}
</td>
<td ng-repeat="car in ctrl.ResultsRow.prices">
{{price}}
</td>
</tr>
I am having a hard time figuring out how to display the exact format that I want. Any help will be appreciated.
P.S. It is quite complicated for me to change the format of ResultRow as I am generating it after quite a few data operations.
You should make an array of objects with properties to iterate in stead of trying to simultaneously iterate two separate arrays.
Example:
var row1 = {
car: "Ford",
price: 20
};
var row2 = {
car: "Honda",
price: 22
};
$scope.rows = [row1, row2];
Then in the template:
<tr ng-repeat="row in rows">
<td>{{row.car}}</td>
<td>{{row.price}}</td>
</tr>
if you want to use a multi-dimension array then I would suggest use new Array and if your car array and price array content is in order then you could do something like this;
vm.ResultsRow=new Array(["Ford","Honda","Nissan"],[20,22,18]);
<tr ng-repeat="car in vm.ResultsRow[0]">
<td>
{{car}}
</td>
<td>
{{vm.ResultsRow[1][$index]}}
</td>
</tr>
angular.module("app",[])
.controller("ctrl",function($scope){
var vm = this;
vm.ResultsRow=new Array(["Ford","Honda","Nissan"],[20,22,18]);
console.log(vm.ResultsRow)
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl as vm">
<table>
<tr ng-repeat="car in vm.ResultsRow[0]">
<td>
{{car}}
</td>
<td>
{{vm.ResultsRow[1][$index]}}
</td>
</tr>
</table>
</div>
change the function for mapping data, like this :
var repeatObject = new Array();
ResultRow = function ( cars, prices) { //cars and prices are arrays
if(cars.length == prices.length) {
for(let i in cars){
this.repeatObject.push({'cars' : cars[i], 'prices' : prices[i]});
}
}
};
So you can use 'ng-repeat' :
<tr ng-repeat="row in repeatObject">
<td>{{row.car}}</td>
<td>{{row.price}}</td>
</tr>

Ranking table with sorting & dynamic columns

I'm trying to do a ranking table :
I have an object scores with the total of points, and each "challenge" with the points associated to this challenge. My principal problem is to run through scores to create a th tag for each challenge & to sort these after.
The aims :
The number of challenge must be dynamic
Sort by total (done) & by challenge
My problems :
How use ng-repeat for the array of challenge
How to sort these
My advancement :
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.sortType = 'points';
$scope.sortReverse = false;
$scope.scores = {
"6":{
"total":5,
"challenges":{
"1":{
"challengeId":1},
"2":{
"point":2,
"challengeId":2}
}
},
"21":{
"total":2,
"challenges":{
"1":{
"point":1,
"challengeId":1},
"2":{
"point":1,
"challengeId":2}
}
},
}
}
myApp.filter('orderObjectBy', function() {
return function(items, field, reverse) {
var filtered = [];
angular.forEach(items, function(item) {
filtered.push(item);
});
filtered.sort(function (a, b) {
return (a[field] > b[field] ? 1 : -1);
});
if(reverse) filtered.reverse();
return filtered;
};
});
<div ng-controller="MyCtrl">
<table>
<tr>
<th>Rank</th>
<th>
<span ng-click="toggleSort($index);sortReverse=false">+</span>
Total
<span ng-click="toggleSort($index);sortReverse=true">-</span>
</th>
<th ng-repeat="challenge in scores.challenges">
<span ng-click="toggleSort($index);sortReverse=false">X</span>
Challenge n°
<span ng-click="toggleSort($index);sortReverse=true">Y</span>
</th>
</tr>
<tbody ng-repeat="score in scores | orderObjectBy:sortType:sortReverse">
<tr>
<td>{{ $index+1 }}</td>
<td>{{ score.total }}</td>
<td ng-repeat="challenge in score.challenges">{{ challenge.point }}</td>
</tr>
</tbody>
</table>
</div>
JSFiddle
Thank you!
I updated the fiddle. When you want to order by a nested value this is how you specify the orderString (In your case it will be by a particular challenge ID):
$scope.orderType = "challenges."+challengeId+'.point';
Also I added a filter which convert the object in an array:
.filter('orderObjectBy', function() {
return function (items, field, reverse) {
var filtered = [];
angular.forEach(items, function(item, key) {
item.key = key;
filtered.push(item);
});
return filtered;
};
});
Then your html should look like this:
<tbody ng-repeat="score in scores| orderObjectBy | orderBy: orderType:sortReverse">
<tr>
<td>{{ $index+1 }}</td>
<td>{{ score.total }}</td>
<td ng-repeat="challenge in score.challenges">{{ challenge.point }}</td>
</tr>
</tbody>
Check this working sample: http://jsfiddle.net/Lvc0u55v/13564/

Dynamically setting new ng-model when pushing model to array

Can't figure out how to dynamically add a new model whenever a new row is added to the page. For example, the input select box ng-model= infos.rigBonusInfo.rigName is used for all select box I've added to the page. I would like to have a different model attached to a each select inputs. I tried using ng-model= infos.rigBonusInfo.rigName[rigBonus] but it doesn't work for the rates as the same model gets attachedto each rate field.
Pretty much what I want to do is to bind a new model whenever a new row gets pushed into the array.
Currently, I have a nested table which is the following:
<div class="col-lg-5">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Rig</th>
<th>Rig Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="rig in rigs">
<td>{{ $index + 1 }}</td>
<td>{{ rig.name }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-lg-2"></div>
<div class="col-lg-5">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Bonus Name</th>
<th>Rate</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="bonus in create.rigBonusRates">
<td>{{ bonus.rateName }}</td>
<td>{{ bonus.rate }}</td>
</tr>
</tbody>
</table>
</div>
<table>
<thead>
<tr>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="rigDate in rigDateList track by $index">
<td><input ui-date="datepickerOptions" ng-model="date" /></td>
<td>
<table>
<thead>
<tr>
<th>Rig</th>
<th>Rate1</th>
<th></th>
<th>Rate2</th>
<th></th>
<th>Rate3</th>
<th></th>
<th>Rate4</th>
<th></th>
<th>Comments</th>
</tr>
</thead>
<tr ng-repeat="rigBonus in rigBonusList track by $index">
<td><select ng-options="rig as rigs.indexOf(rig) + 1 for rig in rigs" ng-model="infos.rigBonusInfo.rigName[rigBonus]" ></select></td>
#for (var i = 1; i < 5; i++)
{
<td><select ng-options="rigBonus.rateName for rigBonus in create.rigBonusRates" ng-model="infos.rigBonusInfo.rate#(#i)"></select></td>
<td><input type="text" ng-disabled="infos.rigBonusInfo.rate#(#i).rateName != 'Special' " ng-model=infos.rigBonusInfo.rate#(#i).rate /></td>
}
<td><input ng-model="info.rigBonusInfo.comments" /></td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<div>
<button type="button" ng-click="add()">Add</button>
<button type="button" ng-click="addDate()">Add Date</button>
</div>
My current controller has the following:
angular.module('RigBonus').controller('rigCreateController', ['$scope', '$http', 'PayPeriodService', 'RigLegendService',
function ($scope, $http, PayPeriodService, RigLegendService, RigBonusRateService) {
$scope.rigs = RigLegendService.getRigLegend();
$scope.datepickerOptions = {
orientation: 'top',
startDate: PayPeriodService.getStartDate(),
endDate: PayPeriodService.getEndDate()
};
$http({ method: 'GET', url: '/Home/CreateData' }).success(function (data) {
$scope.create = data;
$scope.infos = {
rigBonusInfo: {
rigName: $scope.rigs[0],
rate1: $scope.create.rigBonusRates[0],
rate2: $scope.create.rigBonusRates[0],
rate3: $scope.create.rigBonusRates[0],
rate4: $scope.create.rigBonusRates[0],
comment: ""
}
};
$scope.add = function () {
$scope.rigBonusList.push();
};
$scope.addDate = function(){
$scope.rigDateList.push("");
};
});
$scope.rigBonusList = [$scope.rigBonusInfo];
$scope.rigDateList = [];
$scope.submit = function () {
$http.post('/Home/Create', {model: "testing"} );
};
}]);
I figured out my issue. My problem was that I was not sure how to generate a new object when a new row of controls are added. Think I should have put something on fiddleJS to help people visualize it better. As a static model was used ($scope.infos) as ng-model, the same model was used for two different controls which I don't want. I want all my controls to be unique.
The fix was to create the object I had in mind which is the following:
$scope.rigDateList = [{
date: "",
rigBonusList: [{}]
}];
So it is an array of objects where the object contains a date and another array of objects.
When I want to push new objects to the inside array which I didn't know I could just create objects like this at the time. I was trying to figure out a way to dynamically create new models ng-model could by declaring them in the controller. I use the following function:
$scope.rigDateList[$scope.rigListIndex].rigBonusList.push({
rigName: "",
rate1: "",
rate2: "",
rate3: "",
comments: ""
});
I also didn't know that I could use elements inside the array from ng-repeat. In the following case, it is rigBonus that I could have used as a model instead of infos model.
<tr ng-repeat="rigBonus in rigDate.rigBonusList track by $index">
<td><select ng-options="rig as rigs.indexOf(rig) + 1 for rig in rigs" ng-model="rigBonus.rigName"></select></td>
and when I want to push to the outside array I use the following:
$scope.rigDateList.push({
date: "",
rigBonusList: [""]
});
$scope.rigListIndex = $scope.rigListIndex + 1;
I use an index to keep track of which object I'm in.
A more closest question and answer is that:
Ng-repeat with dynamic ng-model on input not working
please, take a look.

AngularJS - ng-repeat not working with a map-entry named length and value 0

I would like to show Events with Subevents I got from an API as JSON
[
{
"class":"de.ff.prg.EventItem",
"id":27667,
"additional_info":null,
"comments":null,
"event":{"class":"Event","id":27657},
"length":0,
"runningorder":0,
"screening":{"class":"Screening","id":27529},
"title_eng":"'71",
"title_ger":"'71",
"venue":{"class":"Venue","id":1}},
{"class":"de.ff.prg.EventItem",
"id":27676,
"additional_info":null,
"comments":null,
"event":{"class":"Event","id":27657},
"length":5,
"runningorder":0,
"screening":null,
"title_eng":"NEW",
"title_ger":"NEW",
"venue":{"class":"Venue","id":8}
}
]
In order to display the fields of the items in rows and not in columns, I have nested two tables with ng-repeat so that I get a table of tables.
<!--Items-->
<table>
<thead>
<td colspan="6" style="background-color: #b9da73">
<button class="btnAdd" ng-click="addAndEditEventItem()">Add Item</button>
</td>
</thead>
<tbody>
<tr ng-repeat="item in eventItems">
<h1>{{eventItems.length}}</h1>
<th>
<table>
<thead>
<td colspan="2" style="background-color: #c0da86">{{item.runningorder}}</td>
<td colspan="4" style="background-color: #c0da86">
<button class="btnAdd" ng-click="deleteEventItem(item.id)">Delete Item</button>
</td>
</thead>
<tbody>
{{item}}
<tr ng-repeat="(key,value) in item">
<th colspan="2" style="background-color: #ceeca1">{{key}}</th>
<th colspan="4" style="font-weight: normal;">{{value}} </th>
</tr>
</tbody>
</table>
</th>
</tr>
</tbody>
</table>
Up until now this was no problem, but somewhere along the way I have lost the possibility to display the body of the first sub-table (all other rows render fine). I have inserted {{item}} before the body tag and it shows the missing data, so it's there all right.
Any ideas? Or do you need to see the other code to tell? I have no clue...
Here is a Fiddle
Interesting case.
Empirically, I found that this is because of
"length":0
Just looked into angular source code and found that it transforms object's properties in repeat to an array, and then takes it's length property to iterate through it.
...ngRepeatDirective...
if (isArrayLike(collection)) {
collectionKeys = collection;
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
} else {
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
// if object, extract keys, sort them and use to determine order of iteration over obj props
collectionKeys = [];
for (key in collection) {
if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
collectionKeys.push(key);
}
}
collectionKeys.sort();
}
arrayLength = collectionKeys.length; <<<--- HERE
// locate existing items
length = nextBlockOrder.length = collectionKeys.length; <<<--- AND HERE
So nothing really happens.
Seems like a bug actually. I've posted an issue.
Just try then iterating through your array and change name of this property. Like:
for(var i = 0; i < $scope.eventItems.length; i++){
$scope.eventItems[i].itemLength = $scope.eventItems[i].length;
delete $scope.eventItems[i].length;
}

knockout js showing related objects(selected id) on click

I'm new to Knockout js and need some advice. What I am trying to do (the correct way) is have orders listed in a grid and a "production" button that when it is click, will show only the production objects that have matching id's to the order id. I'm trying to wrap my head around Knockouts binding, but I think I am over thinking things.
right now I have 2 objects Order and Production with are observable arrays filled with observables. Order has value of orderId and Production have value of prodId that I am checking for a match. I'm now wondering if I should not make this on object with mutli-dimensional array. Would it be easier to show selected data that way?
here is an example of the initial arrays
var initProduction = [
new Production({
proId:"183175",
pType:"Art TIme",
startTime:"11:20",
stopTime:"11:50",
totalTime:"",
by :"MJ"
})
var initData = [
new Order({
date:"06-09-2014",
orderId:"183175",
name:"Columbus Africentric",
dateRec:"05-23-2014",
rushDate:"",
totalQty:55,
parts:"1",
auto:"No",
type:"Local",
})
]
so should I combine into a multidimensional array? And if so, how would I do that? And how would I create a click event to show related data in another table showing only the production info.
I hope this makes sense and someone can help me. I apologize for my ignorance.
here is a stripped down version of my html bindings
<table>
<tbody data-bind="foreach:filteredOrders">
<tr>
<td>
<label class="read" data-bind="text:orderId, visible:true" />
</td>
<!-- controls -->
<td class="tools">
<button class="button toolButton" data-bind="click: $root.showSummary">Show Production</button>
</td>
</tr>
</tbody>
</table>
<h3>Production Summary</h3>
<table class="ko-grid" id="menu" >
<tbody data-bind="foreach:filteredProds">
<tr>
<td>
<div>
<label class="read" data-bind="text:proId, visible:true" />
</div>
</td>
</tr>
</tbody>
</table>
I would just have an orders array and then link the production object to the order.
var model = {
orders: [
{
date:"06-09-2014",
orderId:"183175",
name:"Columbus Africentric",
dateRec:"05-23-2014",
rushDate:"",
totalQty:55,
parts:"1",
auto:"No",
type:"Local",
production: {
proId:"183175",
pType:"Art TIme",
startTime:"11:20",
stopTime:"11:50",
totalTime:"",
by :"MJ"
}
},
{
date:"06-09-2014",
orderId:"183176",
name:"Angle Africentric",
dateRec:"05-23-2014",
rushDate:"",
totalQty:55,
parts:"1",
auto:"No",
type:"Local"
}
]
};
In the above json the second order doesn't have a production object.
Then in the viewModel I would use a computed which will return the orders depending on if all orders or only production orders should be shown. I've created a toggle here which is linked to the button.
var ViewModel = function (model) {
var self = this;
self.orders = $.map(model.orders, function (order) { return new Order (order); });
self.toggleProductionMode = function (order) {
order.showProductionOrder(!order.showProductionOrder());
};
};
var Order = function (order) {
var self = this;
ko.utils.extend(self, order);
self.showProductionOrder = ko.observable(false);
};
View:
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody data-bind="foreach: orders">
<tr>
<td data-bind="text: orderId"></td>
<td data-bind="text: name"></td>
<td data-bind="if: production"><button data-bind="click: $root.toggleProductionMode">Toggle Production Orders</button>
</td>
</tr>
<tr data-bind="visible: showProductionOrder, with: production">
<td colspan="3">
<table>
<tr>
<th>proId</th>
<th>pType</th>
</tr>
<tr>
<td data-bind="text:proId"></td>
<td data-bind="text:pType"></td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
Demo here: http://jsfiddle.net/X3LR6/2/

Resources