Karl Campbell
Karl Campbell

Reputation: 325

Service variable not updating in controller

I have a NavbarCtrl that is outside of ng-view. I have a login controller that talks to a service to get a user logged in. Once the user is logged in, I want the Navbar to update with the user's email address. However for the life of me, I can't seem to get the Navbar scope to update with the data that is loaded in to my "Auth" service once the user is logged in.

This is my main index.html:

<div class="navbar navbar-inverse navbar-fixed-top">

        <div class="navbar-inner">
            <div class="container">
                <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </a>
                <a class="brand" href="#">Brim</a>

                <div class="pull-right"  ng-controller="NavbarCtrl">
                    <div ng-click="refresh()">hello</div>
                    {{ user.email }}
                </div>
            </div>
        </div>

    </div>

    <div class="container" ng-view>

And my service:

.factory('Auth', function($resource) {
    var authenticated = false;
    var user = {};
    return {
        isAuthenticated: function () {
            return authenticated;
        },
        getUser: function() {
            return user;
        },
        login: function(loginUser, callback) {
            user =  {email:'[email protected]'}
            authenticated = true;
            callback(true);
            //actual code for logging in taken out for brevity
        }
    }
})

And my Login and Navbar controllers:

function LoginCtrl($scope, $location, Auth) {

$scope.login = function() {
    Auth.login($scope.user, function(success) {
        if(success) $location.path('/dashboard');
        //console.log(Auth.getUser())
    });
}

}

function NavbarCtrl($scope, Auth)  {

//thought this should work
$scope.user = Auth.getUser();

//experimenting unsuccessfully with $watch
$scope.$watch(Auth.isAuthenticated(),function () {
    $scope.user = Auth.getUser()
})

//clicking on a refresh button is the only way I can get this to work
$scope.refresh = function() {
    $scope.user = Auth.getUser()
}
}

From my research I would have thought that $scope.user = Auth.getUser(); would work, but it's not and I'm at a complete loss as to how how I can get my Navbar updated when a user is logged in. Thanks in advance for any help.

Upvotes: 24

Views: 21515

Answers (3)

BRogers
BRogers

Reputation: 3594

Using user as your variable name is generally not a good idea. I once I changed it ti currentUser everything worked for me.

Upvotes: 0

Mark Rajcok
Mark Rajcok

Reputation: 364677

Update: well, you learn something new every day... just remove the () to watch the results of a function:

$scope.$watch(Auth.isAuthenticated, function() { ... });

Updated fiddle

In this fiddle, notice how 'watch1' and 'watch3' both trigger a second time when the value of $scope.isAuthenticated changes.

So, this is the general technique to watch for changes to a primitive value that is defined on a service:

  • define an API/method that returns (the value of) the primitive
  • $watch that method

To watch for changes to an object or array that is defined on a service:

  • define an API/method that returns (a reference to) the object/array
  • In the service, be careful to only modify the object/array, don't reassign it.
    E.g., don't do this: user = ...;
    Rather, do this: angular.copy(newInfo, user) or this: user.email = ...
  • normally you'll assign a local $scope property the result of that method, hence the $scope property will be a reference to the actual object/array
  • $watch the scope property

Example:

$scope.user = Auth.getUser();
// Because user is a reference to an object, if you change the object
// in the service (i.e., you do not reassign it), $scope.user will
// likewise change.
$scope.$watch('user', function(newValue) { ... }, true);
// Above, the 3rd parameter to $watch is set to 'true' to compare
// equality rather than reference.  If you are using Angular 1.2+
// use $watchCollection() instead:
$scope.$watchCollection('user', function(newValue) { ... });

Original answer:

To watch the results of a function, you need to pass $watch a function that wraps that function:

$scope.$watch( function() { return Auth.isAuthenticated() }, function() { ... });

Fiddle. In the fiddle, notice how only 'watch3' triggers a second time when the value of $scope.isAuthenticated changes. (They all trigger initially, as part of $watch initialization.)

Upvotes: 54

remigio
remigio

Reputation: 4211

Try to change

$scope.$watch(Auth.isAuthenticated(),function () { $scope.user = Auth.getUser() })

to

$scope.$watch(Auth.isAuthenticated(),function () { $scope.user = Auth.getUser() }, true)

Look at the AungularJs $watch documentation for a detailed explanation, the default value is false which means that Angular detects changes in the first parameter values by reference, while passing true means the check is done by equality. Being the parameter a function, the check returns always false, since the reference to the function is always the same.

Upvotes: 0

Related Questions