Hugh Hou
Hugh Hou

Reputation: 2384

Using $loaded with $firebaseObject to update value VS firebase SDK update. Pros and Cons

Let's say I have a service deal with Firebase operation:

angular.module('myApp').factory('taskOfferService', ['FURL', '$firebaseArray', '$firebaseObject', '$q', 'taskService', taskOfferService]);

function taskOfferService(FURL, $firebaseArray, $firebaseObject, $q, taskService) {

    var ref = new Firebase(FURL);

    var Offer = {
        acceptOffer: function(taskId, offerId, runnerId) {
            var offerRef = ref.child('taskOffers').child(taskId).child(offerId);
            var taskUpdate = $q.defer();
            offerRef.update({accepted: true}, function(error) {
                if (error) {
                    console.log('Update offer accepted value failed!');
                } else {
                    var taskRef = ref.child('tasks').child(taskId);

                    taskRef.update({status: "assigned", runner: runnerId}, function(error) {
                        if (error) {
                            console.log('Update task status failed!');
                            taskUpdate.reject(false);
                        } else {
                            taskUpdate.resolve(true);
                        }
                    });

                }
            });
            return taskUpdate.promise;
        },  
    };
    return Offer;
}
    })();

And I have a controller need to call this service and need to wait for a promise when update successful - to call the toaster.pop:

$scope.acceptOffer = function(offerId, runnerId) {
       taskOfferService.acceptOffer($scope.selectedTask.$id, offerId, runnerId).then(function(){
         toaster.pop('success', 'Offer is accepted.');
       }); 
    };

This code work and it is follow the suggestion from Firebase docs. But as you can see in my service, in order to get a promise, I need to use the update callback inside an update callback...My point is, Firebase SDK do not return a promise when done and I need to create this promise and it is A LOT OF CODES...

If I use firebaseObject (angularFire 1.0), it will run into issues listed here: saving new property overwrites firebase object

And using ANTIPATTERN according to @sinan, the code can be way clearer:

acceptOffer: function(taskId, offerId, runnerId) {
            var o = $firebaseObject(ref.child('taskOffers').child(taskId).child(offerId));
            o.$loaded().then(function(){
                o.accepted = true;
                o.$save().then(function(){
                    var oTask = $firebaseObject(ref.child('tasks').child(taskId));
                    oTask.$loaded().then(function(){
                        oTask.status = "assigned";
                        oTask.runner = runnerId;
                        oTask.$save();
                    });
                })       
        },

The point is, using "ANTIPATTERN", I can utilize $save() - which return an promise so no $q service is need inside my firebase service. It looks a lot clearer in my opinion. Both method works.

BUT, according to doc:

"The $loaded() method should be used with care as it's only called once after initial load. Using it for anything but debugging is usually a poor practice."

I just find myself using $loaded A LOT! Please advice the best way to go about this.

Upvotes: 0

Views: 814

Answers (1)

Frank van Puffelen
Frank van Puffelen

Reputation: 598847

Firebase has a JavaScript SDK that exposes the platform's functionality to JavaScript environments. AngularFire is a library on top of that SDK, that makes it easier to bind Firebase data to an AngularJS web interface.

Your example here is a plain data manipulation operation. You're not binding the data to the screen, so you should not need AngularFire. I also see no need for using promises.

As far as I can tell, this does the exact same thing as your last script:

acceptOffer: function(taskId, offerId, runnerId) {
  var offer = ref.child('taskOffers').child(taskId).child(offerId);
  offer.update({ accepted: true }, function() {
    var task = ref.child('tasks').child(taskId);
    task.update({ status: "unassigned", runner: runnerId });
  });
}

Not only is this shorter, it also prevents downloading the data, just to then update it.

And the best part? Since AngularFire is built on top of the regular Firebase JavaScript SDK, they interoperate perfectly. So if in another somewhere you actually display the task or offer through an AngularJS view that watches to a $firebaseObject, it will show the updated value straight away.

Update

If you need to do something when the acceptOffer method is done saving, you can pass in a callback:

acceptOffer: function(taskId, offerId, runnerId, callback) {
  var offer = ref.child('taskOffers').child(taskId).child(offerId);
  offer.update({ accepted: true }, function(error) {
    if (!error) {
      var task = ref.child('tasks').child(taskId);
      task.update({ status: "unassigned", runner: runnerId }, callback);
    }
    else {
      callback(error)
    }
  });
}

You then invoke it like:

taskOfferService.acceptOffer($scope.selectedTask.$id, offerId, runnerId, function(error) {
  if (!error) {
    toaster.pop('success', 'Offer is accepted.');
  }
  else {
    console.error('Something went wrong: '+error);
  }
}); 

You could definitely also promisify the acceptOffer method. But this is not necessary.

Upvotes: 2

Related Questions