Getting related array element AngularJS - javascript

I have a php api that returns a list of companies and a list of users,
Each user has a company_id assigned.
(Users : CustomerList) (Companies : CompanyList)
The companies have a company_id and company_name value.
I'm using resolve to get both these arrays.
When displaying the CustomerList, the company_id of the customer is displayed and everything is working fine.
But what i require is instead of the company_id being shown, I need the company_name being displayed in the CustomerList.
The company_id of the CustomerList is related to the id in the CompanyList.
Just that the company_name is contained in the companyList and not in the CustomerList. But the ID's are related and contained in the userList.
I need to get the company_name of the id that's in CustomerList and display it in the view.
resolve: { userList:function($http,$stateParams){
return $http.post('/api/get_users.php',{role:$stateParams.role},{headers: { 'Content-Type': 'application/x-www-form-urlencoded' }})
.then(function(response){
console.log(response);
return response.data.data;
})
},
companyList:function($http,$stateParams){
return $http.get('/api/get_companies.php')
.then(function(response){
console.log(response);
return response.data.data;
})
}
}
controller("CustomerList", function($scope,$location,$http,$stateParams,userList,companyList){
$scope.customers = userList;
$scope.companies = companyList;
})
VIEW
<table>
<thead>
<tr>
<th>Username</th>
<th>Company Name</th>
<th>Contact Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="customer in customers">
<td>{{ customer.username }}</td>
<td>{{ customer.company_id }}</td>
<td>{{ customer.contact_name }}</td>
</tr>
</tbody>
</table>
the customer.company_id is related to the ID in the companyList , i need to return the companyList.company_name instead of showing the company ID in the customers.
Thanks in advance for any help.

You need to join them.
So you need to create a new object with will contain the username, contact_name, company_name and other properties you need. You need to use the map() which will return an array of new objects. With username and contract_name it is easy, just assign the properties to the new created object. With company_name, we need to find the appropriete company and assign it's name to the newly created object.
Working Example with simple data, which joins two arrays based on the id.
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function($scope){
var userList = [
{username: 'A user', contact_name: 'A contract', company_id: 1},
{username: 'B user', contact_name: 'B contract', company_id: 2},
{username: 'C user', contact_name: 'C contract', company_id: 3},
];
var companyList = [
{id: 1, name: 'A Company'},
{id: 2, name: 'B Company'},
{id: 3, name: 'C Company'},
];
$scope.customers = userList.map(item => {
var foundCompany = companyList.find(company => company.id === item.company_id);
return {
username: item.username,
contact_name: item.contact_name,
company_name: foundCompany ? foundCompany.name : ''
};
});
$scope.companies = companyList;
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app" ng-controller="ctrl">
<table>
<thead>
<tr>
<th>Username</th>
<th>Company Name</th>
<th>Contact Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="customer in customers">
<td>{{ customer.username }}</td>
<td>{{ customer.company_name }}</td>
<td>{{ customer.contact_name }}</td>
</tr>
</tbody>
</table>
</body>

You can structure your companies list in such a way that each object in list is key value pair with key being id of company and value being complete object of company details and then access it easily.
eg
$scope.companies = {
{
"companyId1" : {
"name" : "companyName",
"field2" : "vlue2"
}
},
{
"companyId2" : {
"name" : "companyName2",
"field2" : "vlue2"
}
}
}
and then in your html access it using
{{companies[customer.company_id].name}}
This will work for you

map your userList so that it will contain companyName
var mappedCustomerList= userList.map(function(user){
var user=userWithCompanyName;
userWithCompanyName['companyName']=
companyList[companyList.findIndex(function(company){return company['id']==user['company_id']})].companyName;
return userWithCompanyName;
});
$scope.customerList=mappedCustomerList;
so then from your view you can access companyName
<td>{{customer.companyName}}</td>

Related

dataTables new row won't add to table

When I add new data it won't add to table
Code
HTML
<thead>
<tr>
<th class="text-center">ID</th>
<th class="text-center">Number</th>
<th class="text-center">Body</th>
<th class="text-center">Heading</th>
<th class="text-center">Book</th>
<th class="text-center">Chapter</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<template v-for="verse in getVerses">
<tr v-bind:key="verse.id">
<td width="30" class="text-center">{{ verse.id }}</td>
<td class="text-center">{{ verse.number }}</td>
<td>{{ verse.body }}</td>
<td>{{ verse.heading }}</td>
<td class="text-center">{{ verse.book.name }}</td>
<td class="text-center">{{ verse.chapter.name }}</td>
<td class="text-center">
<button class="btn btn-sm btn-danger" type="button" #click="deleteVerse(verse.id)">Delete</button>
</td>
</tr>
</template>
</tbody>
</table>
Script
export default {
name: "adminVerses",
data() {
return {
isLoading: true,
type: '',
books: [],
errors: [],
pageTitle: 'Manage Verses',
getChapters: [],
getVerses: [],
isLoadingChapters: true,
isLoadingVerses: true,
verse: {
number: 1,
heading: '',
body: '',
book_id: '',
chapter_id: '',
}
}
},
methods: {
submit: function(e) {
axios.post('/api/saveVerse', this.verse)
.then(res => {
this.isLoading = false;
$('#exampleModal').modal('toggle');
this.getVerses.push( res.data.verse );
this.verse = {
number: parseInt(this.verse.number) + 1,
heading: '',
body: '',
book_id: this.verse.book_id,
chapter_id: this.verse.chapter_id,
};
})
.catch(error => {
// handle authentication and validation errors here
this.errors = error.response.data.errors
this.isLoading = false
})
},
}
}
Any idea?
Update
by adding
if ($.fn.dataTable.isDataTable('#verses')) {
$('#verses').DataTable().clear().destroy(); //This will destroy datatable
};
this.getVerses.push( res.data.verse );
now i can get my newly added data into my table but i also will lose ability of paging and search, etc.
ideas?
You are using this to refer to an object, so you are limited to Axios scope. Instead you can use a variable to store a reference to main object, in your case adminVerses and then use $data to set or fetch the data attributes.
Example :
let vm = this;
axios({
...
}).then((res) => {
vm.$data.getVerses.push( res.data.verse );
});
open your vue devtools and check if getVerses gets the right data
The push on the array isn't responsive, so the vue doesn't know it needs to update the view. The array setter is responsive, so:
this.getVerses = this.getVerses.concat(res.data.verse)
will do the trick.

AngularJS Binding doesn't seem to work

I'm fairly new to Angular, and a simple binding doesn't work for me.
It just shows me {{ trip.name }} and {{trip.created}} just as they're written.
My controller:
(function () {
"use strict";
angular.module("app-trips")
.controller("tripsController", tripsController);
function tripsController() {
var vm = this;
vm.trips = [{
name: "US Trip",
created: new Date()
}, {
name: "World Trip",
created = new Date()
}];
A part of my class:
#section Scripts {
<script src="~/lib/angular/angular.min.js"></script>
<script src="~/js/app-trips.js"></script>
<script src="~/js/tripsController.js"></script>
}
<div class="row" ng-app="app-trips">
<div ng-controller="tripsController as vm" class="col-md-6 col-md-offset-3">
<table class="table table-responsive table-striped">
<tr ng-repeat="trip in vm.trips">
<td>{{ trip.name }}</td>
<td>{{ trip.created }}</td>
</tr>
</table>
</div>
the view of the controller:
(function () {
"use strict";
angular.module("app-trips", ['ngSanitize']);
})();
BTW - supposedly according to the course I'm following, I don't need ['ngSanitize'] yet, but without it it doesn't even show the DIVs.
EDIT: as #jellyraptor noticed , I had a typo with = instead of :
EDIT 2 :
It was the typo + the [ngSanitize] which I really didn't needed. I fixed the typo and passed an empty array and everything works. Thanks all
Either Angular isn't loading or it is encountering an error. Bring up the dev tools with F12 and see if there are errors in the console. Also in the console, you can type 'angular' and hit enter and if it reports that angular is undefined then angular is not loading properly.
Because you are using controllerAs syntax and here the variable is vm. so for access scope object use vm instead of tripsController
<tr ng-repeat="trip in vm.trips">
<td>{{ trip.name }}</td>
<td>{{ trip.created }}</td>
</tr>
(function() {
"use strict";
angular.module("app-trips", []);
})();
angular.module("app-trips")
.controller("tripsController", tripsController);
function tripsController() {
var vm = this;
vm.trips = [{
name: "US Trip",
created: new Date()
}, {
name: "World Trip",
created: new Date()
}];
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.2/angular.min.js"></script>
<div ng-app="app-trips" ng-controller="tripsController as vm" class="col-md-6 col-md-offset-3">
<table class="table table-responsive table-striped">
<tr ng-repeat="trip in vm.trips">
<td>{{ trip.name }}</td>
<td>{{ trip.created }}</td>
</tr>
</table>
</div>
You put an equals sign in your trips array's second object's 'created' attribute.
vm.trips = [{
name: "US Trip",
created: new Date()
}, {
name: "World Trip",
created = new Date()
}];
should be
vm.trips = [{
name: "US Trip",
created: new Date()
}, {
name: "World Trip",
created: new Date()
}];

AngularJs orderby filter not sorting data correctly

I have an array of customer displayed in html using ng-repeat from controller.
Here I am trying to sort the data using orderby filter. But the problem is when the array initialized it is sorted correctly in Ascending order. But when I click on Name heading it chnages the data but does not descends the data as expected. Here is a running plunker:
http://plnkr.co/4aAH08bzVUnws5RRx5RP
AngularJs:
app.controller('MainCtrl', function($scope) {
$scope.sortIt = "name";
$scope.reverse = false;
$scope.customers = [
{
name:'AAA',
city: 'Dublin',
orderTotal: 9.9563,
joined: '1947-10-10'},
{
name:'CCC',
city:'London',
orderTotal: 24.999,
joined: '2011-08-12'},
{
name:'BBB',
city:'Kenya',
orderTotal: 140.4852,
joined: '1981-06-04'},
{
name:'DDD',
city:'Tokyo',
orderTotal: 77.3654,
joined: '2006-10-30'}
]
$scope.doSort = function(propName) {
$scope.sortIt = propName;
//changing the value to opposite if true then false, if false then true
$scope.reverse = !$scope.reverse;
}
HTML:
<table class="table table-striped">
<thead>
<tr>
<th ng-click="doSort(name)" class="btn-arrange">
Name
</th>
<th>
<span>City</span>
</th>
<th>Order Total</th>
<th>Joined</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="c in customers | orderBy: sortIt: reverse">
<td>{{c.name}}</td>
<td>{{c.city}}</td>
<td>{{c.orderTotal}}</td>
<td>{{c.joined}}</td>
</tr>
</tbody>
</table>
Replace $scope.reverse = !scope.reverse; with $scope.reverse = !$scope.reverse;
You missed $ before scope.
Update:
Replace <th ng-click="doSort(name)" class="btn-arrange"> with <th ng-click="doSort('name')" class="btn-arrange">
Working Demo
I am adding this answer to do the sorting for all individual heading values with predicate
<th>Name </th>
<th>City </th>
<th>Order Total</th>
<th>Joined</th>
<tr ng-repeat="c in customers | orderBy:predicate:reverse">
<td>{{c.name}}</td>
<td>{{c.city}}</td>
<td>{{c.orderTotal}}</td>
<td>{{c.joined}}</td>

AngularJS ng-show not firing when used with map

Trying to use a key/value map to determine if an angular controlled element should be displayed :
http://jsfiddle.net/9fR23/181/
But I receive a exception :
angular.js:6173 TypeError: fnPtr is not a function
at Object.elementFns [as get] (angular.js:6802)
at Object.$get.Scope.$digest (angular.js:8563)
at Object.$get.Scope.$apply (angular.js:8771)
at angular.js:986
at Object.invoke (angular.js:2873)
Using scope in this way is not illegal ? How to use a key/value map to determine if element should be displayed ? This map will be updated at runtime, so to ensure this update is reflected on UI I need to include apply() method ?
fiddle code :
<div ng-app="myapp" ng-controller="FirstCtrl">
<table class="table table-striped">
<tr ng-repeat="person in people">
<td ng-show="errorMap('1')">{{ person.first + ' ' + person.last }}</td>
</tr>
</table>
</div>
var myapp = angular.module('myapp', []);
myapp.controller('FirstCtrl', function ($scope) {
var errorMap = new Object()
errorMap['1'] = 'true'
errorMap['2'] = 'false';
$scope.errorMap = errorMap
$scope.people = [
{ id: 1, first: 'John', last: 'Rambo' },
{ id: 2, first: 'Rocky', last: 'Balboa' },
{ id: 3, first: 'John', last: 'Kimble' },
{ id: 4, first: 'Ben', last: 'Richards' }
];
});
errorMap('1') is not a function, its an object. so you can do this:
ng-show="errorMap['1']"
You are trying to call a method on your scope that is called errorMap.
Use square brackets to access it.
<div ng-app="myapp" ng-controller="FirstCtrl">
<table class="table table-striped">
<tr ng-repeat="person in people">
<td ng-show="errorMap['1']">{{ person.first + ' ' + person.last }}</td>
</tr>
</table>
</div>

Adding rows with ng-repeat and nested loop

I'm looking for a way to add rows to a table. My data structure looks like that:
rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
I want to create a table which looks like that:
table
row1
row1.1
row1.2
row2
Is that possible with angular js ng-repeat? If not, what would be a "angular" way of doing that?
Edit:
Flatten the array would be a bad solution because if i can iterate over the sub elements i could use different html tags inside the cells, other css classes, etc.
More than one year later but found a workaround, at least for two levels (fathers->sons).
Just repeat tbody's:
<table>
<tbody ng-repeat="row in rows">
<tr>
<th>{{row.name}}</th>
</tr>
<tr ng-repeat="sub in row.subrows">
<td>{{sub.name}}</td>
</tr>
</tbody>
</table>
As far as I know all browsers support multiple tbody elements inside a table.
More than 3 years later, I have been facing the same issue, and before writing down a directive I tried this out, and it worked well for me :
<table>
<tbody>
<tr ng-repeat-start="row in rows">
<td>
{{ row.name }}
</td>
</tr>
<tr ng-repeat-end ng-repeat="subrow in row.subrows">
<td>
{{ subrow.name }}
</td>
</tr>
</tbody>
</table>
You won't be able to do this with ng-repeat. You can do it with a directive, however.
<my-table rows='rows'></my-table>
Fiddle.
myApp.directive('myTable', function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var html = '<table>';
angular.forEach(scope[attrs.rows], function (row, index) {
html += '<tr><td>' + row.name + '</td></tr>';
if ('subrows' in row) {
angular.forEach(row.subrows, function (subrow, index) {
html += '<tr><td>' + subrow.name + '</td></tr>';
});
}
});
html += '</table>';
element.replaceWith(html)
}
}
});
I'm a bit surprised that so many are advocating custom directives and creating proxy variables being updated by $watch.
Problems like this are the reason that AngularJS filters were made!
From the docs:
A filter formats the value of an expression for display to the user.
We aren't looking to manipulate the data, just format it for display in a different way. So let's make a filter that takes in our rows array, flattens it, and returns the flattened rows.
.filter('flattenRows', function(){
return function(rows) {
var flatten = [];
angular.forEach(rows, function(row){
subrows = row.subrows;
flatten.push(row);
if(subrows){
angular.forEach(subrows, function(subrow){
flatten.push( angular.extend(subrow, {subrow: true}) );
});
}
});
return flatten;
}
})
Now all we need is to add the filter to ngRepeat:
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>Rows with filter</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows | flattenRows">
<td>{{row.name}}</td>
</tr>
</tbody>
</table>
You are now free to combine your table with other filters if desired, like a search.
While the multiple tbody approach is handy, and valid, it will mess up any css that relies on the order or index of child rows, such as a "striped" table and also makes the assumption that you haven't styled your tbody in a way that you don't want repeated.
Here's a plunk: http://embed.plnkr.co/otjeQv7z0rifPusneJ0F/preview
Edit:I added a subrow value and used it in the table to show which rows are subrows, as you indicated a concern for being able to do that.
Yes, it's possible:
Controller:
app.controller('AppController',
[
'$scope',
function($scope) {
$scope.rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
}
]
);
HTML:
<table>
<tr ng-repeat="row in rows">
<td>
{{row.name}}
<table ng-show="row.subrows">
<tr ng-repeat="subrow in row.subrows">
<td>{{subrow.name}}</td>
</tr>
</table>
</td>
</tr>
</table>
Plunker
In case you don't want sub-tables, flatten the rows (while annotating subrows, to be able to differentiate):
Controller:
function($scope) {
$scope.rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
$scope.flatten = [];
var subrows;
$scope.$watch('rows', function(rows){
var flatten = [];
angular.forEach(rows, function(row){
subrows = row.subrows;
delete row.subrows;
flatten.push(row);
if(subrows){
angular.forEach(subrows, function(subrow){
flatten.push( angular.extend(subrow, {subrow: true}) );
});
}
});
$scope.flatten = flatten;
});
}
HTML:
<table>
<tr ng-repeat="row in flatten">
<td>
{{row.name}}
</td>
</tr>
</table>
Plunker
Here is an example. This code prints all names of all the people within the peopeByCity array.
TS:
export class AppComponent {
peopleByCity = [
{
city: 'Miami',
people: [
{
name: 'John', age: 12
}, {
name: 'Angel', age: 22
}
]
}, {
city: 'Sao Paulo',
people: [
{
name: 'Anderson', age: 35
}, {
name: 'Felipe', age: 36
}
]
}
]
}
HTML:
<div *ngFor="let personsByCity of peopleByCity">
<div *ngFor="let person of personsByCity.people">
{{ person.name }}
</div>
</div>

Categories