AngularJS $location Not Changing the Path

By Eric on February 22, 2014

In my adventures learning AngularJS I am confronted with strange problems all the time which is a really great thing.  I love learning how to do new and intersting things in different programming environments.  Today I was having problems with my URL not forwarding correctly on a callback method.  Once a user was logged in I wanted the URL to forward to another page.  I was using $location.path and setting it to a URL that was defined in my $routeProvider but it wasn't actually going to that page. A quick Google search led me to this Stack Overflow page, AngularJS $location not changing the path, with the exact problem I was having.  The answer, it turns out, is to add $scope.apply() after any changes you've made.  This does a refresh on the scope and applies any changes you've made that may not have been updated.  The answer describes a conflict with another library in his case jQuery.  I am using Firebase to log the user in and I believe the conflict is there. Here is my code and a little bit of explanation with it.

controllers.js

.controller('AuthController', ['$scope', '$rootScope', '$location', 'AuthService', function($scope, $rootScope, $location, AuthService) {
    $scope.login = function(email, password) {
      var ref = new Firebase('https://myfirebaseapp.firebaseio.com');
      var auth = new FirebaseSimpleLogin(ref, function(error, user) {
        if (error) {
          // an error occurred while attempting login
          console.log(error);
        } else if (user) {
          // user authenticated with Firebase
          $rootScope.user = user;
          $location.path('/stores').replace();
          $scope.$apply();
        } else {
          // user is logged out
        }
      });
      AuthService.login(email, password, auth);
    }
    $scope.createUser = function(email, password) {
      var ref = new Firebase('https://myfirebaseapp.firebaseio.com');
      var auth = new FirebaseSimpleLogin(ref, function(error, user) {
        if (error) {
          // an error occurred while attempting login
          console.log(error);
        } else if (user) {
          // user authenticated with Firebase
          $rootScope.user = user;
          $location.path('/stores').replace();
          $scope.$apply();
        } else {
          // user is logged out
        }
      });
      AuthService.createUser(email, password, auth);
    }
    $scope.logout = function() {
      var ref = new Firebase('https://myfirebaseapp.firebaseio.com');
      var auth = new FirebaseSimpleLogin(ref, function(error, user) {
        if (error) {
          // an error occurred while attempting login
          console.log(error);
        } else if (user) {
          // user authenticated with Firebase
        } else {
          // user is logged out
          $rootScope.user = null;
        }
      });
      AuthService.logout(auth);
    }
  }])

services.js

.factory('AuthService', ['$rootScope', '$location', function($rootScope, $location) {
    return {
	    createUser: function(email, password, auth) {
		    auth.logout();
	      auth.createUser(email, password, function(error, user) {
	        if (!error) {
	          $rootScope.user = user;
	        }
	      });
	    },
	    login: function(email, password, auth) {
		    auth.logout();
	      auth.login('password', {
	        email: email,
	        password: password,
	        rememberMe: false
	      });
	    },
	    logout: function(auth) {
	    	auth.logout();
	    }
  	}
  }]);
The controllers.js file contains a simple controller to put my login, logout, and createUser functions in.  I have off loaded the actual Firebase login to a service that I load in.  I had a really hard time getting it all to work and found that putting the Firebase connections into the controller method and passing that to the service was the best route.  I had the Firebase connection in the service but the callbacks were not firing quite in sync with the controller.  This way I was able to get a more consistent timing of events. The FirebaseSimpleLogin has a callback that gets fired whenever an event happens with it.  In our case that is user is created, user logs in, or user logs out.  I then save the user data to the $rootScope to access it throughout my application and change the URL to a path of my choosing.  At this point without the $scope.$apply() the URL would never actually get fired nor would the $scope realize that there was now a user object, though with the Chrome AngularJS plugin I could clearly see the data was in the scope.  Using the $scope.$apply() cleared all of this up and it works great now. If anyone has any comments on this or any bit of my code above I am very open to hearing about it.  I am still learning AngularJS and am still unsure of proper coding standards.