Firebase, AngularJS and GeoFire - wait for response from factory

1.8k views Asked by At

I'm starting to learn a lot of things related to the hybrid apps coding. I decided to go on and use Ionic framework, based on AngularJS (this gives me the occasion to learn it), and use Firebase as my backend solution.

I want to setup a map in my app, so I would locate the user and some points of interests around him. I found out GeoFire existed, so I started working with it.

My problem : I fail in displaying a user's location saved into Firebase. I query it using GeoFire, and get the response (console.log it) but it won't update the scope variable.

Here is the JS code :

myApp.factory("MyLoc", function(){
    var firebaseRef = new Firebase("https://myfirebase.firebaseio.com/geofire/");
    var geoFire = new GeoFire(firebaseRef);

    /*    geoFire.set("user_loc", [37.785326, -122.405696]).then(function() {
     console.log("Provided key has been added to GeoFire");
     }, function(error) {
     console.log("Error: " + error);
     });*/

     return geoFire.get("user_loc").then(function(location) {
        if (location === null) {
            console.log("Provided key is not in GeoFire");
            return 0;
        }
        else {
            console.log("Provided key has a location of " + location);
            return location;
        }
    }, function(error) {
        console.log("Error: " + error);
        return 0;
    });
})

myApp.controller("firstCtrl", function($scope, MyLoc) {
    $scope.data = {};

    $scope.data.myLoc = MyLoc;
});

The HTML just displays it :

{{data.myLoc}}

Since I'm an Angular N00b, I guess I'm missing something obvious. I guess I should use a promise or something similar, just can't figure out how ! Can someone help me, please ? :)

Thank you very much bros !

[UPDATE]

Okay so, here is the answer to the first question !

MyLoc.then(function(data){
  $scope.data.myLoc = data;

  $scope.$apply();
});

However I had to use

$scope.$apply();

for the HTML to display it, apparently it doesn't updates right away. Do you have any idea why ? I understand Angular has to be notified of a change for it to display, as explained very nicely here : http://jimhoskins.com/2012/12/17/angularjs-and-apply.html

What I don't understand, is why it's the case here ? Why is Angular unaware of the change ?

Thanks again !

1

There are 1 answers

6
Frank van Puffelen On BEST ANSWER

You're being bitten here by the fact that Firebase's API is asynchronous. You cannot return value from an asynchronous call in the way you're trying:

This is the pertinent fragment of your code:

myApp.factory("MyLoc", function(){
    var firebaseRef = new Firebase("https://myfirebase.firebaseio.com/geofire/");
    var geoFire = new GeoFire(firebaseRef);

     return geoFire.get("user_loc").then(function(location) {
        if (location === null) {
            console.log("Provided key is not in GeoFire");
            return 0;
        }
        else {
            console.log("Provided key has a location of " + location);
            return location;
        }
    }, function(error) {
        console.log("Error: " + error);
        return 0;
    });
})

Notice those functions that you're passing into then? Those are called callback functions and are key to Firebase's (and the modern web's in general) asynchronous operation. It's easier to see why your construct won't work if you extract those callbacks into regular, global, named functions:

myApp.factory("MyLoc", function(){
    var firebaseRef = new Firebase("https://myfirebase.firebaseio.com/geofire/");
    var geoFire = new GeoFire(firebaseRef);

     return geoFire.get("user_loc").then(onGeoFireResult, onGeoFireError);
})

function onGeoFireResult(location) {
    if (location === null) {
        console.log("Provided key is not in GeoFire");
        return 0;
    }
    else {
        console.log("Provided key has a location of " + location);
        return location;
    }
}

function onGeoFireError(error) {
    console.log("Error: " + error);
    return 0;
}

When you call geoFire.get("user_loc") it performs a request to the server. That request may take some time and we don't want the browser to be blocked. So instead of waiting for the request to complete, we pass in a function to be called when the server responds with the requested data. Since things may go wrong, we also pass in a function to be called in case of an error.

So it is important to realize that the onGeoFireResult and onGeoFireError run at a different time than when you call geoFire.get("user_loc"). So you cannot return their results from the encapsulating function. Instead you have to deal with the concept of a promise: a structure that at some point will have the data you requested.

If you want to continue using Firebase and Angular without AngularFire, I highly recommend that you follow this video tutorial: https://www.youtube.com/watch?v=3YVlcFyxBr4 . Spending an hour or so on that will save you dozens of question here.

Alternatively set two breakpoints: one on onGeoFireResult and one on the line after geoFire.get("user_loc") and see which breakpoint triggers first.