I have been reading different posts with similar questions but I cannot get this figured out. I have a Job class that is linked to different tables such as Customer and Employee. The Data is coming back from the Database but I cannot get the Angular Table to display the linked classes. The suggestions have been to use different mods, restangular, angular-activerecord, ModelCore and this method
angular js model relationships
I am not sure the best route to take and what would be the simplest way of doing it.
app.factory('Job', function ($resource) {
return $resource('/api/apiJob/:id',
{ id: '#id' },
{ 'save': { method: 'POST' } },
{ 'update': { method: 'PUT' } },
{ 'query': { method: 'GET', isArray: false } });
});
app.factory('jobFactory', function ($http) {
return {
updateJob: function (job) {
return $http.put('/api/apiJob/' + job.JobId, job);
}
};
});
app.factory('Customer', function ($resource) {
return $resource('/api/apiCustomer/:id',
{ id: '#id' },
{ 'save': { method: 'POST' } },
{ 'update': { method: 'PUT' } },
{ 'query': { method: 'GET', isArray: false } });
});
app.factory('customerFactory', function ($http) {
return {
updateCustomer: function (customer) {
return $http.put('/api/apiCustomer/' + customer.CustomerId, customer);
}
};
});
'use strict';
app.controller('JobCtrl', function ($scope, Job, $resource, $route, jobFactory, notificationFactory, $http) {
////GET Jobs
$scope.jobArray = Job.query()
//New Job Modal
$scope.NewJobModal = function () {
$('#NewJobModal').modal();
}
$scope.submitJob = function () {
var data = {
JobId: $scope.JobId,
JobNumber: $scope.JobNumber,
JobName: $scope.JobName,
CustomerName: $scope.CustomerName,
JobDescription: $scope.JobDescription,
OriginalContract: $scope.OriginalContract,
BillingDate: $scope.BillingDate,
}
$http.post('/api/apiJob/PostNewJob', data).success(function (data, status, headers) {
console.log(data);
});
window.top.location.reload();
};
//End New Job Post
//Delete Job
$scope.deleteJob = function (job) {
Job.delete({ id: job.JobId }, function () {
if ($scope.jobArray.length === 1) {
$scope.jobArray.splice(-1, 2);
} else {
$scope.jobArray.splice(job.JobId - 1, 2);
}
window.top.location.reload();
});
};
//End Delete Job
$scope.updateJob = function (job) {
jobFactory.updateJob(job).success(successCallback).error(errorCallback);
};
$scope.job = [];
var successCallback = function (data, status, headers, config) {
notificationFactory.success();
return $('#editJobModal').modal('hide');
};
var errorCallback = function (data, status, headers, config) {
notificationFactory.error(data.ExceptionMessage);
};
//End Edit Job
}); //End Job Controller
What It looks like in the Browser Console
0: {$id:1, JobId:1, JobNumber:2534, JobName:St.Lukes, JobDescription:Rebuilding Cafeteria,…}
$id: "1"
BalanceDue: 89654123
BalanceToBill: 541256
BillingDate: "2014-08-12T14:43:22.507"
BillingForm: "Invoice"
Budget: 854523658
CertPayroll: true
ChangeOrders: [{$id:4, ChangeOrderId:1, ChangeOrderNumber:7854, ChangeOrderDate:2014-08-12T14:43:22.673,…}]
ContractDate: "2014-08-12T14:43:22.507"
Customers: [{$id:2, CustomerId:2, CustomerName:Grove at Wilcrest, CustomerPhoneNumber:8327899667,…}]
0: {$id:2, CustomerId:2, CustomerName:Grove at Wilcrest, CustomerPhoneNumber:8327899667,…}
Employees: [{$id:3, EmployeeId:2, AccountName:Ham Sandwich, EmployeeFirstName:Scott, EmployeeLastName:Willis,…}]
JobAddress: "1234 Mason Rd"
JobCity: "Katy"
JobCost: 784556124
Updated Factory
app.factory('JobGet', function ($http, $q) {
var data = $http({
method: 'GET',
url: '/api/apiJob'
})
return {
query: function () {
var deferred = $q.defer();
setTimeout(function () {
deferred.resolve(data)
}, 2000);
return deferred.promise;
}
};
});
Network Response Tab
[{"$id":"1","JobId":1,"JobNumber":2534,"JobName":"St.Lukes","JobDescription":"Rebuilding Cafeteria","OriginalContract":1250210,"ContractDate":"2014-08-12T14:43:22.507","BillingDate":"2014-08-12T14:43:22.507","TotalCO":12502105,"RevisedContract":452136852,"Budget":854523658,"BillingForm":"Invoice","TESPM":"Frank Harvel","TESSuperintendent":"Rudy Sanchez","Status":"Active","MoreShit":"More shit goes here","TaxExempt":true,"CertPayroll":true,"JobCost":784556124,"RemainingBudget":96523145,"Profit":854125,"Percentage":45,"TotalBilled":45236554,"BalanceToBill":541256,"PaidToDate":0,"BalanceDue":89654123,"JobAddress":"1234 Mason Rd","JobCity":"Katy","JobState":"TX","JobZipcode":77493,"JobCounty":"Harris","JobPhone":2814569654,"JobFax":2814563251,"JobHidden":false,"Customers":[{"$id":"2","CustomerId":2,"CustomerName":"Grove at Wilcrest","CustomerPhoneNumber":8327899667,"CustomerFaxNumber":7134568547,"CustomerAddress":"56328 Richmond Ave","CustomerCity":"Houston","CustomerState":"TX","CustomerZipcode":77042,"CustomerWebsite":"grovewilcrest.com","CustomerOtherShit":"Other Shit Goes here","CustomerHidden":false,"CustomerPM":null,"CustomerAdmin":"Jane Miller","CustomerAccountant":"Betsy Sue","TESSuperintendent":null,"JobId":1,"Job":{"$ref":"1"}}],"Employees":[{"$id":"3","EmployeeId":2,"AccountName":"Ham Sandwich","EmployeeFirstName":"Scott","EmployeeLastName":"Willis","EmployeeTitle":"Admin","EmployeeCellPhone":2818567854,"EmployeeOfficePhone":7134527854,"EmployeeEmail":"testing#gmail.com","CompanyEmployeeId":12521,"EmployeeHidden":false,"EmployeeIsSuper":true,"EmployeeIsPM":true,"JobId":1,"Job":{"$ref":"1"}}],"ChangeOrders":[{"$id":"4","ChangeOrderId":1,"ChangeOrderNumber":7854,"ChangeOrderDate":"2014-08-12T14:43:22.673","ChangeOrderName":"Insert Name Here","ChangeOrderDescription":"It changed","ChangeOrderAmount":4000,"ChangeOrderApprovedDate":"2014-08-12T14:43:22.673","ChangeOrderApprovedAmount":3000,"ChangeOrderApprovedNumber":1,"ChangeOrderAttn":"Frank Harvel","ChangeOrderHidden":false,"JobId":1,"Job":{"$ref":"1"}}]},{"$id":"5","JobId":2,"JobNumber":12343,"JobName":"Katy Mills","JobDescription":"New Mall Bathrooms","OriginalContract":32623212,"ContractDate":"2014-08-12T14:43:22.507","BillingDate":"2014-08-12T14:43:22.507","TotalCO":12502105,"RevisedContract":452136852,"Budget":854523658,"BillingForm":"Invoice","TESPM":"Frank Harvel","TESSuperintendent":"Mike Hall","Status":"Active","MoreShit":"More shit goes here","TaxExempt":true,"CertPayroll":true,"JobCost":784556124,"RemainingBudget":96523145,"Profit":854125,"Percentage":45,"TotalBilled":45236554,"BalanceToBill":541256,"PaidToDate":0,"BalanceDue":89654123,"JobAddress":"1234 Mason Rd","JobCity":"Katy","JobState":"TX","JobZipcode":77493,"JobCounty":"Harris","JobPhone":2814456965,"JobFax":2814563225,"JobHidden":false,"Customers":[{"$id":"6","CustomerId":1,"CustomerName":"City Center","CustomerPhoneNumber":8327899667,"CustomerFaxNumber":7134568547,"CustomerAddress":"123453 HWY 6","CustomerCity":"Katy","CustomerState":"TX","CustomerZipcode":77493,"CustomerWebsite":"citycenter.com","CustomerOtherShit":"Other Shit Goes here","CustomerHidden":false,"CustomerPM":null,"CustomerAdmin":"Jane Miller","CustomerAccountant":"Betsy Sue","TESSuperintendent":null,"JobId":2,"Job":{"$ref":"5"}}],"Employees":[{"$id":"7","EmployeeId":3,"AccountName":"Ice Cream","EmployeeFirstName":"Aaron","EmployeeLastName":"Horstmann","EmployeeTitle":"Office Bitch","EmployeeCellPhone":2818567854,"EmployeeOfficePhone":7134527854,"EmployeeEmail":"aaron#gmail.com","CompanyEmployeeId":12521,"EmployeeHidden":false,"EmployeeIsSuper":true,"EmployeeIsPM":true,"JobId":2,"Job":{"$ref":"5"}}],"ChangeOrders":[{"$id":"8","ChangeOrderId":2,"ChangeOrderNumber":1823,"ChangeOrderDate":"2014-08-12T14:43:22.673","ChangeOrderName":"Insert Name Here","ChangeOrderDescription":"Work Orders","ChangeOrderAmount":4000,"ChangeOrderApprovedDate":"2014-08-12T14:43:22.673","ChangeOrderApprovedAmount":3000,"ChangeOrderApprovedNumber":2,"ChangeOrderAttn":"Rosie Sanchez","ChangeOrderHidden":false,"JobId":2,"Job":{"$ref":"5"}}]},{"$id":"9","JobId":3,"JobNumber":12398,"JobName":"City Center","JobDescription":"Remodeling Yard House","OriginalContract":56325412,"ContractDate":"2014-08-12T14:43:22.507","BillingDate":"2014-08-12T14:43:22.507","TotalCO":12502105,"RevisedContract":452136852,"Budget":854523658,"BillingForm":"Invoice","TESPM":"Frank Harvel","TESSuperintendent":"Shawn Saulnier","Status":"Active","MoreShit":"More shit goes here","TaxExempt":true,"CertPayroll":true,"JobCost":784556124,"RemainingBudget":96523145,"Profit":854125,"Percentage":45,"TotalBilled":45236554,"BalanceToBill":541256,"PaidToDate":0,"BalanceDue":89654123,"JobAddress":"1234 Mason Rd","JobCity":"Katy","JobState":"TX","JobZipcode":77493,"JobCounty":"Harris","JobPhone":2814256965,"JobFax":2814565325,"JobHidden":false,"Customers":[{"$id":"10","CustomerId":3,"CustomerName":"Twin Peaks","CustomerPhoneNumber":8327899667,"CustomerFaxNumber":7134568547,"CustomerAddress":"8473 Katy Fwy","CustomerCity":"Houston","CustomerState":"TX","CustomerZipcode":77043,"CustomerWebsite":"citycenter.com","CustomerOtherShit":"Other Shit Goes here","CustomerHidden":false,"CustomerPM":null,"CustomerAdmin":"Jane Miller","CustomerAccountant":"Betsy Sue","TESSuperintendent":null,"JobId":3,"Job":{"$ref":"9"}}],"Employees":[{"$id":"11","EmployeeId":1,"AccountName":"Not Sure","EmployeeFirstName":"Frank","EmployeeLastName":"Harvel","EmployeeTitle":"Manager","EmployeeCellPhone":2818567854,"EmployeeOfficePhone":7134527854,"EmployeeEmail":"texas45#gmail.com","CompanyEmployeeId":12521,"EmployeeHidden":false,"EmployeeIsSuper":true,"EmployeeIsPM":false,"JobId":3,"Job":{"$ref":"9"}}],"ChangeOrders":[{"$id":"12","ChangeOrderId":3,"ChangeOrderNumber":45324,"ChangeOrderDate":"2014-08-12T14:43:22.673","ChangeOrderName":"Insert Name Here","ChangeOrderDescription":"It changed again","ChangeOrderAmount":4000,"ChangeOrderApprovedDate":"2014-08-12T14:43:22.673","ChangeOrderApprovedAmount":3000,"ChangeOrderApprovedNumber":3,"ChangeOrderAttn":"Travis Dillard","ChangeOrderHidden":false,"JobId":3,"Job":{"$ref":"9"}}]}]
First of all, this is a work in progress. Your code is very large and to solve this I removed a lot o noise from your plunkr, I left there only what's essential to make it work and only the code involved in it.
Working Demo
First thing to notice, on your JobController you are doing:
////GET Jobs
$scope.jobArray = Job.query();
This worked on previous versions of angular, on the current version there is no automatic promise unwrapping anymore, so you must use the then function:
////GET Jobs
Job.query().then(function(retrievedData){
$scope.jobArray = retrievedData;
});
Notice that on the plunkr I "mocked" the returning of data using $q and a timeout, I actually cleaned the json leaving only the properties that are used on the table, just to make it simpler, you don't need to do that on your code ok? Just keep that part as it is.
Maybe you'll have to modify it just to resolve the promise with the returned data, but this is up to you, the important thing is to return a promise from your query() method and resolve it.
On the HTML I noticed that you are binding the customer name like this:
<td>{{job.Customers.CustomerName}}</td>
But on your Json, the Customers property is an array of customers, so you either return one customer from your api or bind it like this:
<td>{{job.Customers[0].CustomerName}}</td>
I also noticed that there are a lot of jQuery references on your controllers like this:
$('#editJobModal').modal();
This is not recommended and I don't think it will work at all, when dealing with the DOM, always use directives and comunicate with them using bindings, thats the angular way.
Well after all this, my plunkr is showing the table with the 2 fake customers from the Json. Now I suggest you to study that code and apply those principles to your app.
I hope I could help or at least point you at the right direction.
Let's say my current route is /books and I make an $http call to get all of the books we want to show a user. Normally, the call would resolve quickly and the books would be ng-repeated into the DOM. When we have an error, though (such as a timeout or there are no books returned), we update a common, global view that will overlay the content view and display a message like, "There are no books available." The common view is handled via a service with methods like CommonView.showLoading(), CommonView.showError("There are no books available."), and CommonView.hide(), etc.
Recently, I discovered that if the $http is not resolved quickly, the user may leave and go to another route (maybe /dinosaurs). Eventually, when the $http ends up resolving or being rejected, the promise call to display that common, global view will happen, resulting in an error view being displayed when there shouldn't be one, and the error will make no sense to the user (ie, user is at /dinosaurs and the error screen pops up with "There are no books available.").
I've seen that you can cancel an $http with a timeout promise, but this still seems like it could lead to race conditions (maybe you call cancel after processing of the resolve() or reject() has begun). I think it would be messy to have to check that the current route matches the route the $http was initiated from.
It seems like there should be some standard way to destroy $http calls on a route change or from a controller's $destroy method. I'd really like to avoid adding a lot of conditionals all over my gigantic app.
I can't find a great way to stop the processing of my callback if it's already started, but here's the $http wrapper I made to try and stop delayed callbacks from getting called after route changes. It doesn't replicate all of the $http methods, just the ones I needed. I haven't fully tested it, either. I've only verified that it will work in normal conditions (normal bandwidth with standard calls, ie httpWrapper.get(url).success(cb).error(err)). Your mileage may vary.
angular.module('httpWrapper', []).provider('httpWrapper', function() {
this.$get = ['$rootScope','$http','$q', function($rootScope, $http, $q) {
var $httpWrapper = function(config) {
var deferred = $q.defer();
var hasChangedRoute = false;
var canceler = $q.defer();
var http = null;
var evListener = null;
var promise = deferred.promise;
if ((config || {}).timeout && typeof config.timeout === 'Object') {
// timeout promise already exists
canceler.promise = config.timeout;
} else {
angular.extend(config || {}, {
timeout: canceler.promise
});
}
http = $http(config)
.success(function(data, status, headers, config) {
// only call back if we haven't changed routes
if (!hasChangedRoute) {
deferred.resolve({data:data, status:status, headers:headers, config:config});
}
})
.error(function(data, status, headers, config) {
// only call back if we haven't changed routes
if (!hasChangedRoute) {
deferred.reject({data:data, status:status, headers:headers, config:config});
}
});
evListener = $rootScope.$on('$locationChangeStart', function(scope, next, current) {
hasChangedRoute = true;
canceler.resolve('killing http');
evListener(); // should unregister listener
})
promise.success = function(fn) {
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
}
return promise;
};
angular.forEach(['get', 'delete', 'head', 'jsonp'], function(method) {
$httpWrapper[method] = function(url, config) {
return $httpWrapper(
angular.extend(config || {}, {
method: method,
url: url
})
);
};
});
angular.forEach(['post', 'put'], function(method) {
$httpWrapper[method] = function(url, data, config) {
return $httpWrapper(
angular.extend(config || {}, {
method: method,
url: url,
data: data
})
);
};
});
return $httpWrapper;
}];
});