Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angularfire - $scope wont update after async call, and $apply throws error

I am currently banging my head trying to understand a little bit better how Angularfire works in this case. I have 2 controllers on my "webapp/test", one for the user to login, and the other to show some profile info. After the user login with Facebook, i use the "uid" to check the firebase DB if the user exist, and retrieve some of the info thats saved on the database.

The problem right now if since the database read call is async, i cant get the info to update on the view, and using $scope.$apply() only works as long as i dont change pages, once i click on a page thats using another controller, i get the following:

Error: [$rootScope:inprog] http://errors.angularjs.org/1.3.15/$rootScope/inprog?p0=%24digest

Controller

app.controller("profile", function($scope, loginService, Auth, userManagement) {
    var authData = Auth.$getAuth();
    if (authData != null) {
        $scope.authData = authData;
        userManagement.saveLastLogin(authData);
        userManagement.userDatabaseRead(authData).done(function(userDatabase) {
                $scope.userDatabase = userDatabase;
                $scope.$apply();
        });
    };
    $scope.logoutFB = function() {
        loginService.logoutUser();
        Auth.$onAuth(function(authData) {
            $scope.authData = authData;
        });
    };
})

Factory

app.factory("userManagement",function(FirebaseUrl) {
    return {
        saveLastLogin: function(authData) {
            var userId = authData.uid;
                var ref = new Firebase(FirebaseUrl);
                var users = ref.child("users");
                var timestamp = Date.now();
                users.child(authData.uid).update({
                    last_activity: timestamp,
                });
                console.log('user ' + userId + ' last login was on' + timestamp);
        },
        userDatabaseRead: function(authData) {
            var ref = new Firebase(FirebaseUrl+"users/"+authData.uid);
            var data, def = $.Deferred();
            ref.on("value", function(snapshot) {
                var userDatabase = snapshot.val();
                def.resolve(userDatabase);
            }, function (errorObject) {
              console.log("The read failed: " + errorObject.code);
            });
            return def.promise();
        },
    }
});

Update 1: View

This is my view, what i am trying to do is once the user is logged in, show some information from the firebase structure that belongs to this user uid

<div class="jumbotron">
  <h1>Profile View</h1>
  <p ng-show="authData">Welcome {{ authData.facebook.cachedUserProfile.first_name }}</p>
  <p ng-show="authData">{{userDatabase.last_activity | date:'yyyy-MM-dd HH:mm:ss Z'}}</p>
  <button ng-click="logoutFB()" ng-show="authData" class="btn btn-danger facebook-login">Logout</button>
</div>
like image 655
Nicolas Ferro Avatar asked Oct 18 '22 19:10

Nicolas Ferro


1 Answers

You're not actually using AngularFire, just the Firebase SDK.

Angular is not aware of the Firebase SDK, so you need to use AngularFire so it triggers the $digest loop for you.

To includle AngularFire, add the JavaScript file and then add it to the dependency array as well.

angular.module('app', ['firebase'])
  .constant('FirebaseUrl', '<my-firebase-app>')
  .service('rootRef', ['FirebaseUrl', Firebase])
  .factory('userManagement', UserManagement)
  .controller('MyCtrl', MyController);

function UserManagement($firebaseObject, rootRef) {
  return {
    saveLastLogin: saveLastLogin,
    userDataseRead: userDatabaseRead
  };

  function userDataseRead(authData) {
    var userRef = rootRef.child('users').child(authData.uid);
     // return a $firebaseObject with the ref, don't create a listener
    return $firebaseObject(userRef);
  }

  function saveLasLogin(authData) {
     // this code is good because it doesn't do any listening,
     // just updates to the server
  }
}

function MyController($scope, userManagement) {
  var authData = Auth.$getAuth();
  $scope.userDatabase = userManagement(authData);
}

Some things to note in your original code.

You're using a Promise is a real time listener, which is an anti-pattern.

Promises fire off only once, whereas Firebase database listeners can fire off multiple times.

ref.on("value", function(snapshot) {
  var userDatabase = snapshot.val();
  def.resolve(userDatabase); // don't do this
 }, function (errorObject) {
  console.log("The read failed: " + errorObject.code);
});

AngularFire takes care of this for you when you use either a $firebaseObject or $firebaseArray.

Use resolve in the router to inject users

Rather than grab the user in the controller, you can ensure that if the user is authenticated they will be injected in the controller:

app.config(["$routeProvider", function($routeProvider) {
$routeProvider.when("/home", {
  controller: "HomeCtrl",
  templateUrl: "views/home.html",
  resolve: {
    currentAuth: ["Auth", function(Auth) {
      return Auth.$requreAuth();
    }]
  }
})
like image 122
David East Avatar answered Nov 11 '22 14:11

David East