Anadi Misra Anadi Misra - 3 months ago 9
AngularJS Question

working with deferred results in angularjs

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) {

Answer

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').