How can I create an Angular factory with a getter and setter method without running into a race condition

Issue

I’m creating a factory to take a userId from one page, make a call to a REST API, and return the results on the following view. My initial attempts were largely taken from this answer but – unsurprisingly – I keep getting caught in a situation where the doesn’t respond in time and the get() method returns an empty array.

Here’s the factory itself

app.factory('GetMessages', function() {
 var messages = []

 function set(userId) {

   Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
    messages = docs   
    })
 }

 function get() {
    return messages;
 }

 return {
  set: set,
  get: get
 }

});

For what it’s worth I’m having no trouble getting the userId into the factory as it’s just passed in on a function like this

view:

<a ng-click='passToFactory(message.user.id)' href='/home/inbox/reply'>Reply</a>

controller:

$scope.passToFactory = function(id) {
    GetMessages.set(id);
};

and the controller for the following view is just
$scope.messages = GetMessages.get()

The issue I’m having is that after the factory returns the empty set no further changes from the factory are recognized (even though after time elapses it does get the proper response from the API) and $scope.messages remains empty.

I’ve attempted to move the API call to the get method (this hasn’t worked as the get method often does not get the userId in time) and I can’t find a way to use a promise to force get() to wait on set() completing.

I’d prefer to keep using Restangular in the eventual solution but this is a small thing that has taken too much time so any fix works.

I’m fairly new to Angular so I’m sure there’s something totally obvious but right now I’m just lost. Thanks.

Solution

The race condition that you have is that the function inside the .then method is executed asynchronously after the call to the set function. If the get function executes before the $q service fulfills the promise, the get function returns an empty array.

The solution is to save the promise and chain from the promise.

app.factory('GetMessages', function() {

 var promise;

 function set(userId) {

     promise = Restangular.all('/api/messages/').getList({'_id': userId});
 }

 function get() {
    return promise;
 }

 return {
  set: set,
  get: get
 }

});

In your controller, chain from the promise.

GetMessages.get.then( function (docs) {
    $scope.messages = docs;
}) .catch ( function (error) {
    //log error
};

For more information on chaining promises, see the AngularJS $q Service API Reference — chaining promises.

Answered By – georgeawg

Answer Checked By – Clifford M. (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.