Can't return results from async AngularJS factory to controller

103 Views Asked by At

I've read a dozen blog posts and StackOverflow answers but my factory won't return its results to the controller. In the controller, I first make an object to send data to the factory, then call the factory:

 let videoWordsArrayObject = { // make object to send data to factory
    clipInMovie: $scope.clipInMovie,
    movieTitle: $scope.movieTitle,
    userUID: $scope.user.uid,
    videoWords: $scope.videoWords
  };

videoWordsArrayFactory.toController(videoWordsArrayObject) // call factory
  .then(function(data) {
    console.log(data); // undefined
  })

The data comes back undefined. Here's my factory:

app.factory('videoWordsArrayFactory', function($q) {

    function toController(videoWordsArrayObject) {

      let videoWordsArray = [];

      // get the data from the controller
      var clipInMovie = videoWordsArrayObject.clipInMovie;
      var userUID = videoWordsArrayObject.userUID;
      var videoWords = videoWordsArrayObject.videoWords;
      var movieTitle = videoWordsArrayObject.movieTitle;

      var qPromise = $q.when(firebase.database().ref('users').orderByChild('uid').equalTo(userUID).once('value')) // query Firebase Database by the user's UID to find the user's account
      .then(function(snapshot) { // get a snapshot of the user's data
      snapshot.forEach(function(childSnapshot) { // iterate through the user's data

      switch (true) { 

// cases that don't return data to the controller

      case childSnapshot.val()[movieTitle][movieTitle + "_" + clipInMovie][0].word === videoWords[0]: // array of completed words in Firebase with correct first element
      videoWordsArray = childSnapshot.val()[movieTitle][movieTitle + "_" + clipInMovie];
      console.log(videoWordsArray); // data is here
      return videoWordsArray;
      break;

      default:
      console.log("Error");
    } // close switch-case

  }); // close snapshot forEach loop
}) // close snapshot promise
.catch(function(error) {
  console.error('Error ', error);
}); // close snapshot catch
return qPromise; // no data here
}; // close toController

return {
  toController: toController
};

}); // close factory

The return from the factory happens before the data comes back from the database. I don't understand how to make the factory wait for the promise to resolve before doing the return to the controller.

Also, I don't understand what toController: toController is. I know that either the key or the value is the function that the controller calls, but why is the function both the key and the value? Can I refactor the function to get rid of

return {
  toController: toController
};
2

There are 2 best solutions below

0
Thomas David Kehoe On BEST ANSWER

I had return in the wrong place. Here's my working code for the factory:

app.factory('asyncVideoWordsArrayFactory', function($q) {

  return {
    toController: toController
  };

  function toController(asyncVideoWordsArrayObject) {
    var videoWordsArray = [];
    var clipInMovie = asyncVideoWordsArrayObject.clipInMovie;
    var movieTitle  = asyncVideoWordsArrayObject.movieTitle;
    var userUID     = asyncVideoWordsArrayObject.userUID;
    var videoWords  = asyncVideoWordsArrayObject.videoWords;

      var qPromise = $q.when(firebase.database().ref('users').orderByChild('uid').equalTo(userUID).once('value'))
      .then(function(snapshot) {

      snapshot.forEach(function(childSnapshot) {
        var userData = childSnapshot.val();

        switch (true) { 

        // cases for catching error conditions

        case userData[movieTitle][movieTitle + "_" + clipInMovie][0].word === videoWords[0]: 
        videoWordsArray = userData[movieTitle][movieTitle + "_" + clipInMovie];
        break;

        default:
        console.log("Error");
        } // close switch-case
      }); // close forEach loop
      return videoWordsArray; // return result to toController
    })  // close snapshot promise
    .catch(function(error) {
      console.log('Error: ' + error);
    });
    return qPromise;  // return result to controller
  } // close toController
}); // close factory

The only change was moving the return from inside the case (and inside the forEach loop) to outside the forEach loop. Apparently the old (broken) code failed to return the result out of the promise function, so the toController function didn't have access to the result.

Rephrasing the issue, there are four nested functions:

  1. The factory is a function.
  2. toController is a function.
  3. The second promise (.then) is a function.
  4. The forEach loop is a function.

The factory needs two returns, in the 2nd and 3rd functions. No return is needed in the 4th function because videoWordsArray is on the same scope, i.e., is accessible from the 3rd nested function. The 3rd nested loop has to return to the 2nd nested loop, which returns to the controller.

0
FarukT On

first of all i see that you use in wrong way tha application factory / it is the same thing of application service

in factory you must create your promise and return to the controller after it you will handle it with two functions; first is success function and second is error

try this code and you'll understand it better

i never user firebase but i think it is same think of other dbs

app.factory('videoWordsArrayFactory', function($q) {
return {
    toController: toController
}
    function toController(videoWordsArrayObject) {
            return $q.when(firebase.database().ref('users').orderByChild('uid').equalTo(videoWordsArrayObject.userUID).once('value')) //this return a promise
    }

});


// in your controller
app.controller("myController",function("$scope, videoWordsArrayFactory"){

    var videoWordsArrayObject = { // make object to send data to factory
        clipInMovie: $scope.clipInMovie,
        movieTitle: $scope.movieTitle,
        userUID: $scope.user.uid,
        videoWords: $scope.videoWords
    };

    $scope.callPromise = function(){
        videoWordsArrayFactory.toController(videoWordsArrayObject).then(function(snapshot){
            //handle here youre logic after promise is resolved
            console.log(snapshot);
        },function(error){
            console.log(error);
        })
    }

    $scope.callPromise();
});