1

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.

Community
  • 1
  • 1
isdn
  • 381
  • 1
  • 3
  • 15
  • Where is `userId` ? Why are you unable to get it ? – Rayon Jan 12 '16 at 07:36
  • ah sorry I should have put the view in there (editing it in now.) No problem getting the userId - it's just passed in through a function and logs correctly to the console every time – isdn Jan 12 '16 at 07:38
  • 1
    If your 'get' method returns the promise then you can do the 'then' in the controller without worrying about any race conditions. – o4ohel Jan 12 '16 at 07:41
  • I don't understand the problem very good. Could you explain why is it a problem that race and could you show us where and how you use the get function. – Hristo Enev Jan 12 '16 at 08:03
  • You may have a look at https://github.com/Yannic92/shoppingList/blob/master/src/main/resources/public/app/lists/service/listService.js. I'm doing this here in the same way – Yannic Bürgmann Jan 12 '16 at 08:32

4 Answers4

1

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.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • You're right this would work. But it's not necessary. If you don't break the reference, everything is fine. (https://github.com/Yannic92/shoppingList/blob/master/src/main/resources/public/app/lists/service/listService.js) I'm doing this here in the same way – Yannic Bürgmann Jan 12 '16 at 08:32
0

You are breaking the reference to the original messages array when you reassign it.

Try:

Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
    messages.concat(docs) ; // keep same array reference   
});

Simple example to explain why it isn't working

var arr = [];
var x   = arr;
arr     = [1,2,3]; // is now a different array reference
console.log(x); // is still empty array. x !== arr now 
charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • Ah you're absolutely right about this - thanks! But unless I'm doing something else wrong I think I'm still stuck with the race condition issue as this is returning the empty array before the call has finished (if I log it I can even see the correct one show up shortly after.) Am I missing something? – isdn Jan 12 '16 at 07:49
  • Yes it would return empty array initially because `Restangular` is asynchronous and the array won't get populated until it completes. `$resource` uses the same concept, returns an empty array immediately. Most people would return the promise from service and update controller model in promise resolve callback – charlietfl Jan 12 '16 at 07:52
  • Oh for sure - I'm finding that after it populates the following page isn't updating. Is there something I should be looking at there instead? – isdn Jan 12 '16 at 07:54
  • `following page`? Not sure what that means – charlietfl Jan 12 '16 at 07:55
  • mh i'm not sure how concat works but what i do at this point is: messages.splice(0, messages.length); messages.push.apply(messages, docs); this should work – Yannic Bürgmann Jan 12 '16 at 07:58
  • @YannicKlem easy enough to look up how it works https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat – charlietfl Jan 12 '16 at 07:59
  • Sorry following page meaning the view on which the get() method is called – isdn Jan 12 '16 at 08:00
  • @charlietfl so this breaks the reference, too. It creates a NEW array .. this is what you don't want to do.. – Yannic Bürgmann Jan 12 '16 at 08:00
  • "The concat() method returns a new array comprised of the array on which it is called joined with the array(s) and/or value(s) provided as arguments." – Yannic Bürgmann Jan 12 '16 at 08:02
  • push.apply adds the content of the second array to the first array – Yannic Bürgmann Jan 12 '16 at 08:03
0

cherlietfl is right.

The problem is that you break the reference to the messages array since you assign a new array to messages inside your get function. But concat is doing this as well.

Try this:

Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
  messages.splice(0, messages.length); // clear the array
  messages.push.apply(messages, docs); //add the new content
});
Yannic Bürgmann
  • 6,301
  • 5
  • 43
  • 77
  • I'm afraid this isn't working for me as the get() method is still returning an empty set. The array does resolve correct in the end but by that time return has already been called and the updates data in the factory doesn't make it to the following view. It's very possible I'm doing something stupid but that's where I'm stuck I'm afraid – isdn Jan 12 '16 at 08:14
  • Could you provide the template which uses the messages array returned by GetMessages.get() and the controller for this template? – Yannic Bürgmann Jan 12 '16 at 08:16
0

Try assigning you function to the scope. Then call that function in the model. Like so:

// controller
$scope.getMessages = GetMessages.get;

View:

<div ng-repeat="message in getMessages()"></div>

This way when the request call finishes and the digest cycle goes through the watchers again, the get function will be called and you will get your messages.

Hristo Enev
  • 2,421
  • 18
  • 29