Question

Let's say I have a very simple service with a few properties on it. If I use the service in a controller, put the service's properties on the scope so that they are bound to my view, and update them from the controller, they update in the view. This is the behavior I'd expect. However, if the same service's properties are modified from a directive outside of the controller's scope, the view is not updated (unless something triggers a watch to be updated in the controller's scope?). There is obviously something fundamental that I'm missing here, but search search searching has not led me to the answer.

Here is an example on JSFiddle.

app = angular.module('app', []);
// simple service to track application's logon status
app.factory('AuthService', function () {
    var status = {
        isLoggedIn: false
    };

    return {
        status: status,
        login: function () {
            status.isLoggedIn = true;
            console.log('user logged in');
        },
        loggedIn: function () {
            return status.isLoggedIn;
        },
        logout: function () {
            status.isLoggedIn = false;
            console.log('user logged out');
        }
    }
});

app.controller('AuthViewCtrl', function ($scope, AuthService) {
    // bind some service attributes, functions to the scope so that we can use them in our view
    $scope.loggedIn = AuthService.loggedIn;
    $scope.login = AuthService.login;
    $scope.logout = AuthService.logout;
    $scope.stat = AuthService.status;
});

// a simple directive to allow elements to log out of the app on click
app.directive('appLogout', function (AuthService) {
    return function (scope, element) {
        element.bind('click', function () {
            AuthService.logout();
        });
    }
});

// a simple directive to allow elements to log into the app on click
app.directive('appLogin', function (AuthService) {
    return function (scope, element) {
        element.bind('click', function () {
            AuthService.login();
        });
    }
});

And the accompanying html:

<div ng-app="app">
    <div ng-controller="AuthViewCtrl">
        <strong>Are we logged in?</strong>
        <ul>
            <li>service func on scope: <strong>{{ loggedIn() }}</strong></li>
            <li>service prop on scope: <strong>{{ stat.isLoggedIn }}</strong></li>
        </ul>

        <button ng-click="login()">log in from controller scope</button>
        <button ng-click="logout()">log out from controller scope</button>
        <button ng-click="loggedIn()">call AuthService.loggedIn()</button>
    </div>

    <button app-login>log in from directive</button>
    <button app-logout>log out from directive</button>
</div>

The app is logged out when you start. If you "log in[/out] from controller," which calls a service function published to the scope, the watched service values are updated immediately in the view. However, if you hit "log in[/out] from directive," the watched service values are not updated (they will update if you simply call AuthService.loggedIn() from within the scope).

So, I guess my question is, what's the best way to handle this? Where have I gone astray with watching service values?

Thanks, Adam

Was it helpful?

Solution

The problem is that you're calling your service "outside" of angular:

    element.bind('click', function () {
        AuthService.login();
    });

So, you need to wrap the call in an $apply:

    element.bind('click', function () {
        scope.$apply(function() {
            AuthService.login();
        });
    });

Updated Fiddle: http://jsfiddle.net/SAsBa/46/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top