3
votes

The use case is to change login button to text "logged in as xxx" after authentication.

I have devided my page to 3 views: header, content, footer. The login button is in the header view. When I click login, it transits to "app.login" state, and the content view changes to allow user input username and password.

Here's the routing code:

app.config(['$stateProvider', '$urlRouterProvider',
    function($stateProvider, $urlRouterProvider) {
    $stateProvider
    .state('app', {
        url: '/',
        views: {
            'header': {
                templateUrl: 'static/templates/header.html',
                controller: 'AppController'
            },
            'content': {
                templateUrl: 'static/templates/home.html',
                controller: 'HomeController'
            },
            'footer': {
                templateUrl: 'static/templates/footer.html',
            }
        }
    })
    .state('app.login', {
        url: 'login',
        views: {
            'content@': {
                templateUrl : 'static/templates/login.html',
                controller  : 'LoginController'
           }
        }
    })

The html template has code like this:

<li><span ng-if='loggedIn' class="navbar-text">
    Signed in as {{currentUser.username}}</span>
</li>

LoginController set a $scope.loggedIn flag to true once authentication succeeded, but how can I populate that flag to the header view?

As I understand it I can't just use $scope.loggedIn in the html template as above because the $scope is different in two controllers. I know if LoginController is a child of AppController, then I can call $scope.$emit in LoginController with an event and call $scope.$on in AppController to capture it. But in this case the two controllers are for different views, how can I make them parent-child?

I know I can use $rootScope but as I'm told polluting $rootScope is the last resort so I'm trying to find a best practise. This must be a very common use cases so I must be missing something obvious.

3

3 Answers

1
votes

You can use a factory to handle authentication:

app.factory( 'AuthService', function() {
  var currentUser;

  return {
    login: function() {
      // logic 
    },
    logout: function() {
      // logic 
    },
    isLoggedIn: function() {
      // logic 
    },
    currentUser: function() { 
      return currentUser; 
    }
  };
});

Than can inject the AuthService in your controllers. The following code watches for changes in a value from the service (by calling the function specified) and then syncs the changed values:

app.controller( 'AppController', function( $scope, AuthService ) {
  $scope.$watch( AuthService.isLoggedIn, function ( isLoggedIn ) {
    $scope.isLoggedIn = isLoggedIn;
    $scope.currentUser = AuthService.currentUser();
  });
});
0
votes

In such cases I typically opt to use a service to coordinate things. Service's are instantiated using new and then cached, so you effectively get a singleton. You can then put in a simple sub/pub pattern and you're good to go. A basic skeleton is as follows

angular.module('some-module').service('myCoordinationService', function() {
    var callbacks = [];
    this.register = function(cb) {
      callbacks.push(cb);
    };

    this.send(message) {
      callbacks.forEach(function(cb) {
        cb(message);
      });
    };
}).controller('controller1', ['myCoordinationService', function(myCoordinationService) {
  myCoordinationService.register(function(message) {
     console.log('I was called with ' + message);
  });
}).controller('controller2', ['myCoordinationService', function(myCoordinationService) {
  myCoordinationService.send(123);
});
0
votes

Do you use any serivce to keep logged user data? Basically serivces are singletons so they are good for solving that kind of problem without polluting $rootScope.

app.controller('LoginController', ['authService', '$scope', function (authService, $scope) {
  $scope.login = function(username, password) {
    //Some validation
    authService.login(username, password);
  }
}]);

app.controller('HeaderController', ['authService', '$scope', function (authService, $scope) {
    $scope.authService = authService;
}]);

In your header html file:

<span ng-if="authService.isAuthenticated()">
    {{ authService.getCurrentUser().userName }}
</span>