Can't seem to get my head over the concept of 'promise' in AngularJS, I am using the restangular library to fetch a resource over REST, however I always get null results. Here's the code
.service('CareersService', [ 'Restangular', '$sce', function(Restangular, $sce){
var vacancies = [];
var result;
this.getVacancies = function() {
Restangular.all('job_posts').getList({active: 'true'}).then(function(job_posts){
job_posts.forEach(function(job_post){
vacancies.push(_.pick(job_post,['id','title','min_experience','max_experience','location']));
})
})
return vacancies;
}
this.getVacancy = function(job_id){
Restangular.one('job_posts',job_id).get().then(function(job_post){
result = _.pick(job_post, 'title','min_experience','max_experience','location','employment_type','description');
var safe_description = $sce.trustAsHtml(result.description);
var emp_type = _.capitalize(result.employment_type);
_.set(result, 'description', safe_description);
_.set(result, 'employment_type', emp_type);
});
return result;
}
}]).controller('DetailsCtrl', ['$scope' ,'$stateParams', 'CareersService' ,function($scope, $stateParams, CareersService) {
$scope.data.vacancy = { title: 'Loading ...', contents: '' };
$scope.data.vacancy = CareersService.getVacancy($stateParams.job_id);
}])
and then in view
<div class="container">
<a ui-sref="careers" class="btn btn-primary">Show All</a>
<div class="row">
<h2>{{ data.vacancy.title }}</h2>
<p>{{ data.vacancy.min_experience }}</p>
<p>{{ data.vacancy.max_experience }}</p>
<p>{{ data.vacancy.location }}</p>
<p>{{ data.vacancy.employment_type }}</p>
<p ng-bind-html="data.vacancy.description"></p>
</div>
</div>
Am I missing something in the way to use promises?
Update
here's the updated code thanks to all the help I got here,
this.getVacancies = function() {
Restangular.all('job_posts').getList({active: 'true'}).then(function(job_posts){
job_posts.forEach(function(job_post){
vacancies.push(_.pick(job_post,['id','title','min_experience','max_experience','location']));
})
return vacancies;
})
}
this.getVacancy = function(job_id){
Restangular.one('job_posts',job_id).get().then(function(job_post){
vacancy = _.pick(job_post, 'title','min_experience','max_experience','location','employment_type','description');
...
return vacancy;
});
}
}])
And in controllers
CareersService.getVacancy($stateParams.job_id).then(function (vacancy){
$scope.data.vacancy = vacancy;
});
and
CareersService.getVacancies().then(function (vacancies){
$scope.data.vacancies = vacancies;
});
I now get the error
Cannot read property 'then' of undefined
At the line
CareersService.getVacancies().then(function(vacancies) {
Restangular makes an API call over a http, and once it make a call it returns underlying promise object. And inside .then
function of it you can get the data responded by API.
So here you are making an async call and considering it to happen it in synchronous way like you can see you had returned result
/vacancies
array from Restangular
call, in that way result
/vacancies
is always going to be empty.
In such you should return a promise from a service method. And return appropriate formatted data from promise so that you can chain
that promise in controller as well(by retrieving a data).
Service
this.getVacancies = function() {
//returned Restangular promise
return Restangular.all('job_posts').getList({
active: 'true'
}).then(function(job_posts) {
job_posts.forEach(function(job_post) {
vacancies.push(_.pick(job_post, ['id', 'title', 'min_experience', 'max_experience', 'location']));
});
//return calculated result
return vacancies;
})
}
this.getVacancy = function(job_id) {
//returned Restangular promise
return Restangular.one('job_posts', job_id).get().then(function(job_post) {
result = _.pick(job_post, 'title', 'min_experience', 'max_experience', 'location', 'employment_type', 'description');
var safe_description = $sce.trustAsHtml(result.description);
var emp_type = _.capitalize(result.employment_type);
_.set(result, 'description', safe_description);
_.set(result, 'employment_type', emp_type);
//returned result to chain promise
return result;
});
}
As I said now you can easily chain promise inside controller by having .then
function over service method call.
CareersService.getVacancy($stateParams.job_id).then(function(result){
$scope.data.vacancy = result;
});
Update
The syntax without .then
would work, but you need to make small change in it by adding .$object
after a method call.
$scope.data.vacancy = CareersService.getVacancy($stateParams.job_id).$object;
$object
is property which added inside promise object by Restangular
. While making an API call, at that time it makes $scope.data.vacancy
value as a blank array ([]
) and once server respond with response, it fills that object with response received by server. Behind the scene it only updates the value of $object
property which automatically update $scope.data.vacancy
value.
Same behaviour is there in $resource
of ngResource
.
I wanted to also put down that when you're chaining promise, that time you have to explicitly handle error case. Whereas in current code you haven't handle such failure condition. So I'd suggest you to go for that as well by adding error
function inside Restangular
REST API call. and do use $q.reject('My error data, this can be object as well')
.