angularjsangular-promiseangularjs-factory

Can't return results from async AngularJS factory to controller


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
};

Solution

  • 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.