AngularJS - two ng-repeats where first.id =second.id - angularjs

I am trying to show name of city against the city id I receive from branches. What is the angular way of doing it?
$http.get(baseURL + '/city/getList?token=' + token)
.then(function(response) {
$scope.cities = response.data.data;
});
$http.get(baseURL + '/store/getAllBranch?token=' + token)
.then(function(response) {
$scope.branches= response.data.data;
});
<tbody>
<tr ng-repeat="x in branches| orderBy:'name'">
<td>{{x.name}}</td>
<td>{{x.address}}</td>
<td ng-repeat="city in cities track by x.city">{{city.name}}</td>
</tr>
</tbody>

Create a filter to convert cityID to cityName
<tbody>
<tr ng-repeat="x in branches| orderBy:'name'">
<td>{{x.name}}</td>
<td>{{x.address}}</td>
<td>{{x.cityId | cityName}}</td>
</tr>
</tbody>
app.filter('cityName', function(cityId) {
return function(cityId){
for(var ci = 0; ci < cities.length; ci++)
{
if( cities[ci].id == cityId )
return cities[ci].name;
}
return '';
};
});
And use it like that..
But this way is not good for performance. If you could declare related city names in related branches then you can just print that to screen with no calculation.

The 'angular way' is to construct an object which will be data-wise identical to how your table should look like and then you'd use ng-repeat to display the info.
You say you have the city id given in your branch. So you can just construct an object in which you push all your city names:
$scope.tableInfo = [];
for(var i = 0; i < branches.length; i++) {
var tableCell = {
name: branches[i].name,
address: branches[i].address,
city: branches.filter(function(element){
for(var city in cities) {
if(element.cityId === city.id) return city.name;
}
})[0];
};
}
That [0] is there because it'll always be a 1-element array(because of the filter function).
In the HTML, just use a <table> to show the tableInfo object.
<table>
<tr ng-repeat="tableCell in tableInfo">
<td>{{tableCell.name}}</td>
<td>{{tableCell.address}}</td>
<td>{{tableCell.city}}</td>
</tr>
</table>

Related

Prevent users from selecting same value in multiple dropdowns

I am loading a table from an API call , table rows are dynamic and it is based on the returned values from API call. I am displaying sort order and value should be unique and user shouldn't select a previously selected values. I tried to follow as per this (http://jsfiddle.net/jnash21/oqezom4y/) but i am not able to achieve as mine is dynamic.
I tried this (http://embed.plnkr.co/QU4r05n9rQprwyL9Ltxh/) .
editor.controller('EditorController', function($scope) {
$scope.entities = [{name:"pencil",sortOrder:""} ,{name:"notepad",sortOrder:""} ,
{name:"bookshelf",sortOrder:""}
];
$scope.sortOrderValues=[1,2,3];
});
<table>
<tr ng-repeat="x in entities">
<td>{{ x.name }}</td>
<td><select ng-model="x.sortOrder"
ng-options="col for col in sortOrderValues">
</select>
<span ng-show="!x.sortOrder"> * sort order required</span>
</td>
</tr>
</table>
How can I prevent a user from selecting same sort order in each row using angular js?
This plunker might help you.
First of all, genereate an array from 1 to entities.length (this case, 3).
When you select an option, tirgger the optionSelected function. This function will generate your inital array and calculate the used sortOrders by your entities. Then it filters the second ones from the first array.
HTML
<div ng-controller="EditorController">
<table>
<tr ng-repeat="x in entities">
<td>{{ x.name }}</td>
<td><select ng-model="x.sortOrder"
ng-options="col for col in sortOrderValues"
ng-change="optionSelected()">
</select>
<span ng-show="!x.sortOrder"> * sort order required</span>
</td>
</tr>
</table>
</div>
CONTROLLER
editor.controller('EditorController', function($scope) {
$scope.entities = [{name:"pencil",sortOrder:""} ,{name:"notepad",sortOrder:""} ,
{name:"bookshelf",sortOrder:""}
];
// Genereate all the numbers between 1 and $scope.entities.length
$scope.sortOrderValues= $scope.entities.map(
function (item, index) {
return index + 1;
}
);
// Function executed when you select a sortOrder
$scope.optionSelected = function () {
// Genereate all the numbers between 1 and $scope.entities.length
var allIndexes = $scope.entities
.map(function (entity, index) { return index + 1; });
// Get all the sortOrder used
var usedIndexes = $scope.entities
.map(function(e) { return e.sortOrder; });
// Remove from the [1, .., $scope.entities.length] array all the sortOrder used
$scope.sortOrderValues = allIndexes
.filter(function (order) {
return !usedIndexes.find(function(index) { return index === order; });
});
}
});

Displaying data with ng-repeat from JSON array in angular

Hi I have following data that is returned from service which I do not have any control on how it is returned:
{"day_1":[{"classroom":"Nursery","count":0},{"classroom":"Junior Kindy","count":1}],"day_2":[{"classroom":"Nursery","count":4},{"classroom":"Junior Kindy","count":0}]}
but I need to display it in pivot format that is like below:
classroom | day_1 | day_2
============ ======== ======
Nursery | 0 | 4
Junior Kindy | 1 | 0
This is the code in controller
$scope.rolls=[];
Rolls.getRollMarked().then(
function(data){
console.log(data.data);
$scope.rolls = data.data;
}
)
in the view I am using following but it doesn't display any count for the day and I am not sure how to display it..so please let me know how can I display it in the above format?
<table class="table table-bordered">
<tr>
<td>Classroom</td>
<td>day_1</td>
<td>day_2</td>
</tr>
<tr ng-repeat="roll in rolls">
<td>
{{roll[$index]['classroom']}}
</td>
<td>
{{roll.day_1}}
</td>
<td>
{{roll.day_2}}
</td>
</tr>
</table>
You need to convert your data. ng-repeat as you have set it up expects an array.
Using some easy code you can get it to an array though, and then your code will work alright.
Also, you should update your html. You don't need to reference items using $index since each item is bound to the iterator variable in that case
<table class="table table-bordered">
<tr>
<th>Classroom</th>
<th>day_1</th>
<th>day_2</th>
</tr>
<tr ng-repeat="roll in rolls">
<td>
{{roll.classroom}}
</td>
<td>
{{roll.day_1}}
</td>
<td>
{{roll.day_2}}
</td>
</tr>
</table>
And then call a convert function that makes the data into an array. I've used lodash.find here, so you either need to reference that or use your own find method.
Rolls.getRollMarked().then(
function(data){
console.log(data.data);
$scope.rolls = convert(data.data);
}
)
function convert(json) {
var rolls = [];
var days = ['day_1', 'day_2'];
for (var d = 0; d < days.length; ++d) {
var day = days[d];
for (var i = 0; i < json[day].length; ++i) {
var classroom = json[day][i];
var existing = _.find(rolls, { "classroom": classroom.classroom });
if (!existing) {
existing = { classroom: classroom.classroom };
rolls.push(existing);
}
existing[day] = classroom.count;
}
}
return rolls;
}

How can I easily filter on more than one property?

Using Angular v1.5 I've got a simple one property filter working but I would like to filter on two properties. Searching came up with some custom filters but that seemed like a bunch of code to just look at an extra property.
In my below example I would like the filter to work on both FirstName and LastName.
Filter Name: <input type="text" ng-model="memberFilter.FirstName" />
<p />
<div class="table-responsive">
<table class="table table-bordered table-striped table-hover">
<tr class="info">
<th ng-click="doSort('FirstName')">First Name</th>
<th ng-click="doSort('LastName')">Last Name</th>
<th ng-click="doSort('EmailPrimary')">Email</th>
<th ng-click="doSort('PhonePrimary')">Phone</th>
<th> </th>
</tr>
<tr ng-repeat="member in members | filter:memberFilter | orderBy:sortBy:reverse">
<td>{{ member.FirstName }}</td>
<td>{{ member.LastName }}</td>
<td>{{ member.EmailPrimary }}</td>
<td class="text-center">{{ member.PhonePrimary | tel }}</td>
<td class="text-center">View</td>
</tr>
</table>
</div>
I tried adding an array to the ng-model but that didn't work:
ng-model="[memberFilter.FirstName,memberFilter.LastName]"
Updated Question
I'm new with Angular in general and just wrote my first custom filter the other day to display formatted telephone numbers. You can see my filter in the original code but I changed the name from "tel" to "phone". It's one I found on SO and I was able to get it to work. So I assumed I could just add the search filter to this file and not create a new js file for each filter. However, when I added it my Angular stopped working. I thought I added it correct but I might have a semi-colon in the wrong spot or maybe it's not possible. I still haven't figured out a good way to debug in Angular.
Here is a file I created for my filters called filter.js.
angular.module('appFilters', [])
.filter('phone', function () {
return function (phone) {
if (!phone) { return ''; }
var value = phone.toString().trim().replace(/^\+/, '');
if (value.match(/[^0-9]/)) {
return phone;
}
var country, city, number;
switch (value.length) {
case 10: // +1PPP####### -> C (PPP) ###-####
country = 1;
city = value.slice(0, 3);
number = value.slice(3);
break;
case 11: // +CPPP####### -> CCC (PP) ###-####
country = value[0];
city = value.slice(1, 4);
number = value.slice(4);
break;
case 12: // +CCCPP####### -> CCC (PP) ###-####
country = value.slice(0, 3);
city = value.slice(3, 5);
number = value.slice(5);
break;
default:
return phone;
}
if (country == 1) {
country = "";
}
number = number.slice(0, 3) + '-' + number.slice(3);
return (country + " (" + city + ") " + number).trim();
};
})
.filter('customMemberFilter', function () {
return function (memberList, query) {
// make sure a query value was passed
if (query) {
query = query.toLowerCase();
var out = [];
angular.forEach(memberList, function (member) {
if ((member.FirstName.toLowerCase().includes(query)) ||
(member.LastName.toLowerCase().includes(query))) {
out.push(member);
}
});
return out;
} else {
return memberList;
}
}
});
and this is my app.js where the appFilters are added
(function () {
var app = angular.module('troopApp', ['ngRoute','appFilters']); //('moduleName', [array of injected modules])
app.config(function($routeProvider) {
$routeProvider
.when('/', {
controller: 'MembersController',
templateUrl: 'app/views/members.html'
})
.when('/memberDetail/:memberId', {
controller: 'MemberDetailController',
templateUrl: 'app/views/memberDetail.html'
})
.otherwise({ redirectTo: '/'});
});
}());
This is what I changed my HTML code to:
Filter Name: <input type="text" ng-model="memberFilter" />
and
<tr ng-repeat="member in members | customMemberFilter:memberFilter | orderBy:sortBy:reverse">
The solution is very simple.
Change this line:
Filter Name: <input type="text" ng-model="memberFilter.FirstName" />
to this:
Filter Name: <input type="text" ng-model="memberFilter" />
By default Angular will do deep property filtering. Since you were creating an object with a FirstName property to use as your filter you were effectively limiting the filter to that property on your target object(s). By making the filter more generic Angular will attempt to match on any property of the target object(s).
Update: This will, of course, match on all properties of your member object which may be undesirable. If this is the case then, as you suspected, you'll have to write a custom filter. Here's one way you could write it:
.filter('customMemberFilter', function() {
return function(memberList, query) {
// make sure a query value was passed
if (query) {
query = query.toLowerCase();
var out = [];
angular.forEach(memberList, function(member) {
if ((member.FirstName.toLowerCase().includes(query)) ||
(member.LastName.toLowerCase().includes(query))) {
out.push(member);
}
});
return out;
} else {
return memberList;
}
}
})
And then you would use it:
<tr ng-repeat="member in members | customMemberFilter:memberFilter | orderBy:sortBy:reverse">

Angular object property value change not propagated into view using ng-repeat

I'm trying to generate a table using ng-repeat.
Use case
The data to generate the table from looks as follows:
$scope.data = [
{
name : 'foo1',
group : 1
},
{
name : 'foo2',
group : 1
},
{
name : 'foo3',
group : 1
},
{
name : 'foo4',
group : 1
},
{
name : 'foobar',
group : 2
},
{
name : 'foobarbar',
group : 3
}
];
The html generated should look like this:
<tr>
<th>Group</th>
<th>Name</th>
</tr>
<tr>
<td rowspan="4">1</td>
<td>foo1</td>
</tr>
<tr>
<td>foo2</td>
</tr>
<tr>
<td>foo3</td>
</tr>
<tr>
<td>foo4</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobar</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobarbar</td>
</tr>
Implementation
I know the easiest way would probably be to pre-process the data and group the items per group in a new array of arrays. However, I chose a different approach:
<td
ng-if = "isDifferentFromPrev(items, $index, groupingData)"
rowspan = "{{item._groupSize}}"
>
with
$scope.isDifferentFromPrev = function(array, index, groupingData){
if(index === 0){
groupingData.startI = 0;
groupingData.counter = 1;
array[0]._groupSize = 1;
return true;
}
var eq = equalsMethod(array[index], array[index-1]);
if(eq){
groupingData.counter++;
array[groupingData.startI]._groupSize = groupingData.counter;
}
else{
groupingData.startI = index;
groupingData.counter = 1;
array[index]._groupSize = 1;
}
return !eq;
};
Problem
For some reason the rendered value for rowspan is always 1.
The attribute is only set for the first td of the first tr of a group, as intended, but the value for it is 1.
If I put a breakpoint inside isDifferentFromPrev(), the values seem to be updated correctly. This does not reflect in the html though.
Solution?
It occured to me that maybe ng-repeat renders each step sequentially, without returning to it. So maybe the _groupSize values for the first item of each group do get properly updated, but since they are updated after that item has already been rendered by ng-repeat, the update isn't processed anymore.
I have no idea if this reasoning is correct, nor about how to solve it. Any suggestions please?
This solution, even if a bit orthodox, does work:
var app = angular.module("myApp", []);
app.controller("myController", function($scope) {
$scope.data = [{
name: 'foo1',
group: 1
}, {
name: 'foo2',
group: 1
}, {
name: 'foo3',
group: 1
}, {
name: 'foo4',
group: 1
}, {
name: 'foobar',
group: 2
}, {
name: 'foobarbar',
group: 3
}];
$scope.itemHasRowspan = function(item) {
return typeof item === "object" && item.hasOwnProperty("rowspan");
};
var groupData = {},
currentGroup = null,
addGroup = function(firstItem) {
currentGroup = firstItem.group;
groupData[firstItem.group] = {
"firstItem": firstItem,
"count": 1
};
};
angular.forEach($scope.data, function(item, index) {
if (item.group !== currentGroup) {
addGroup(item);
} else {
groupData[item.group].count++;
}
});
angular.forEach(groupData, function(group, index) {
group.firstItem["rowspan"] = group.count;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<table>
<thead>
<tr>
<th>Group</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<td ng-if="itemHasRowspan(item)" rowspan="{{ item.rowspan }}" valign="top">
{{ item.group }}
</td>
<td>
{{ item.name }}
</td>
</tr>
</tbody>
</table>
</div>
</div>

AngularJS orderby with array of arrays and separate keys

I have tabular data that I'm returning from the server in the form of an array of arrays for the data, and an array of keys associated with that data. Then, I want to sort by a particular key. Now, I know I can pre-process the data and zip together an array of objects, but say I don't want to do that. Is there an easy, built-in way to do this?
Some code that doesn't actually sort but does display the data. CodePen.
JS:
var app = angular.module('helloworld', []);
app.controller('TestController', function() {
this.headers = ['foo', 'bar'];
this.data = [
[ 'lol', 'wut' ],
[ '123', 'abc' ]
];
this.predicate = '';
});
HTML:
<table ng-app="helloworld" ng-controller="TestController as test">
<thead>
<tr>
<th ng-repeat="heading in test.headers" ng-click="test.predicate = heading">{{ heading }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>Predicate:</td>
<td>{{ test.predicate }}</td>
</tr>
<tr ng-repeat="row in test.data | orderBy: test.predicate">
<td ng-repeat="column in row">{{ column }}</td>
</tr>
</tbody>
</table>
You can accomplish this but I would suggest that you instead have your server return you data as a list of json objects.
To sort your multidimensional array you basically sort by the inner array's index.
Your predicate would hold the index of the column you want to sort on (either 0 or 1 in your case)
<th ng-repeat="heading in test.headers"
ng-click="test.predicate = $index">
{{ heading }}
</th>
Create a sorting function in your controller as below:
this.sorter = function(item){
return item[test.predicate];
}
Apply this sorter as your orderBy expression as below:
<tr ng-repeat="row in data | orderBy: test.sorter">
I've forked and updated your CodePen for you: http://codepen.io/anon/pen/qvcKD
For reference, the solution where the array is zipped together with standard JS:
var app = angular.module('helloworld', []);
app.controller('TestController', function() {
this.headers = ['foo', 'bar'];
var data = [
[ 'lol', 'abc' ],
[ '123', 'wut' ]
];
this.data = [];
for (var i = 0, n = data.length; i < n; i++) {
this.data.push({});
for (var j = 0, m = this.headers.length; j < m; j++) {
this.data[i][this.headers[j]] = data[i][j];
}
}
this.predicate = '';
});
Or instead with LoDash as suggested by #Antiga:
_.each(data, function(item) {
this.data.push(_.zipObject(this.headers, item));
}, this);

Resources