Alexandros Spyropoulos
Alexandros Spyropoulos

Reputation: 974

Firebase - Angular $on listener returns data undefined value on callback

I'm Trying to fetch some data from a firebase , I'm using angular firebase plugin. I double checked debugging in inspector, the url is the correct one. It responds back, that means that the url is correct but the callback's arguments is undefined.

I'm using loaded because I need it to fire once. I tried value but shame thing.

I think I exhausted all my energy on this for today so a second opinion would be great.

P.S. I really wonder why they are not using a promise instead of a callback.

fragment from angular + firebase factory

     var seekMatch = function(player) {
        var deferred = $q.defer();
        
        angular.forEach(matches.$getIndex(), function(matchId) {
            var matchRef = firebaseRef('matches/' + matchId);  // <-- double checked, the url sends me to the correct firebase record
            var matchDB = $firebase(matchRef);
            
            matchDB.$on('loaded', function(data) {
                console.log(data);   // <----- return's undefined
                if (matchMakingFormula(data.playerA, player)) {
                    if (!match) {
                        match = data;
                        deferred.resolve(match);
                    }
                }
            });
        });
        
        return deferred.promise;
    };

I'm adding all the code here to give you a better idea of what I'm trying to do.

Full code of my fb.match.service

'use strict';

angular.module('angularfireApp')
.factory('FBmatchService', ['$rootScope' , '$q', '$firebase', 'firebaseRef',
function ($rootScope, $q, $firebase, firebaseRef) {

  // Service logic
  var matchesRef = firebaseRef( '/matches/' );
  var matches = $firebase(matchesRef);
  var match = null;

  var matchMakingFormula = function (playerA , playerB) {
    return playerA.type !== playerB.type
    && distanceFormula( playerA.location.lat , playerA.location.long, playerB.location.lat , playerB.location.long ) < 1
  };

  var distanceFormula = function (lat1 , lon1 , lat2, lon2) {
    var R = 6371; // km
    var dLat = (lat2-lat1).toRad();
    var dLon = (lon2-lon1).toRad();
    var lat1 = lat1.toRad();
    var lat2 = lat2.toRad();

    var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var d = R * c;
    return d;
  };

  var getMatch = function (matchId) {
    match = matches.$getIndex(matchId);
    return match;
  };

  var seekMatch = function ( player ) {
    var deferred =  $q.defer();

    angular.forEach(matches.$getIndex() , function (matchId){
      var matchRef = firebaseRef( 'matches/'+matchId );
      var matchDB = $firebase( matchRef );

      matchDB.$on('loaded',function (data) {
        console.log(data);
        if (matchMakingFormula(data.playerA , player)) {
          if (!match) {
            match = data;
            deferred.resolve(match);
          }
        }
      });
    });

    return deferred.promise;
  };


  // Public API here
  return {
    get: function (matchId) {
      return getMatch(matchId);
    },
    seek: function (points) {
      return seekMatch(points);
      //return match.promise;
    },
    new: function (points) {
      //return match.promise;
    },
    join: function (match) {
      //return match;
    }
  };
}]);

Thanks in advance. Cheers and have fun!

Upvotes: 0

Views: 554

Answers (3)

Alexandros Spyropoulos
Alexandros Spyropoulos

Reputation: 974

OK, finally "found" the solution. Thanks to kato that remind me to check my version.

Current version 0.7.2 preview works for me. Thing is that is not on bower yet and I assumed that I had the latest version while updating from bower. Which was wrong.

      collection.$child( matchId ).$on('loaded' , function(match){  //<---- match now returns the proper object but also null or {} empty object sometimes if empty.
        if (match) {
          if (valid(match)){ //<-- so you need to validate the output not just !match
            deferred.resolve(match); 
          }
          else
          {
            deferred.reject('invalid');
          }
        }
        else
        {
          deferred.reject('no match');
        }
      });

Either way is always a good idea to validate your endpoints before consuming them for recovery and error catching reasons.

Better update from github because the project seems to advance much quicker than it's bower registry.

Cheers and have fun.

Upvotes: 1

Alexandros Spyropoulos
Alexandros Spyropoulos

Reputation: 974

Another think to bear in mind is that this

            matchDB.$on('loaded', function(data) {
                console.log(matchDB);  //  <--- notice what is going on here
                if (matchMakingFormula(data.playerA, player)) {
                    if (!match) {
                        match = data;
                        deferred.resolve(match);
                    }
                }
            });

return's this

matchDB: Object
 $add: function (item) {
 $auth: function (token) {
 $bind: function (scope, name) {
 $child: function (key) {
 $getIndex: function () {
 $off: function (type, callback) {
 $on: function (type, callback) {
 $remove: function (key) {
 $save: function (key) {
 $set: function (newValue) {
 $transaction: function (updateFn, applyLocally) {
 playerA: Object  //  <----  notice this. it was the content of the object in firebase
 __proto__: Object

Which is completely crazy... It is actually merging the matchDB which is the DB reference of match with the object that I'm expecting from the firebase.

 "13131314141"{
    "playerA" : {
       "location" : {
           "lat" : 51.6021821,
           "long" : "-02582276"
        },
        "type" : "monster"
    }
 }

You can actually make a solution out of this. but how you can take the result in the callback to use it as a promise deferred.resolve?

I can understand that they did this to be able to do

$scope.match = $matchDB.$on('loaded' , function(){});

but this doesn't serve my purpose that is decoupling firebase from my controllers and I don't really think it is actually a neat solution.

Please don't accept this as a solution because it is not a real one. You can hack your way to make it one but there is probably a better way to do it or at least the project is too young and soon a proper solution will be available.

Upvotes: 0

Alexandros Spyropoulos
Alexandros Spyropoulos

Reputation: 974

i fixed that with a small hack in angularfire.js

line 336ish in the $on handler

336. callback();

change with

336. callback(self._snapshot);

line 587ish in the end of _wrapTimeout function add

  587. //hack to preserve the snapshot from the timeout wipeouts
  if ( evt === "loaded" ) {
    self._snapshot = param;
  }

I hope this will help you for now. I will try to find a proper solution for this.

Upvotes: 0

Related Questions