92

I have an AuthService, which logs in a user, it returns back a user json object. What I want to do is set that object and have all the changes reflected across the application (logged in/logged out state) without having to refresh the page.

How would I accomplish this with AngularJS?

Mazzy
  • 1,901
  • 2
  • 16
  • 36
Art
  • 5,864
  • 3
  • 30
  • 32

2 Answers2

180

The easiest way to accomplish this is by using a service. For example:

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

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

You can then reference this in any of 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 to the scope.

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

And then, of course, you can use that information however you see fit; e.g. in directives, in templates, etc. You can repeat this (customized to what you need to do) in your menu controllers, etc. It will all be updated automatically when you change the state on the service.

Anything more specific depends on your implementation.

starball
  • 20,030
  • 7
  • 43
  • 238
Josh David Miller
  • 120,525
  • 16
  • 127
  • 95
  • 1
    That is what I was thinking too, but I'm curious how the factory works. Won't we get a different currentUser context each time a controller is created and asks for an AuthService? Perhaps I'm just not clear on how Angular hooks things up. Ideally we would have one context. As an alternative I was thinking of using the ngCookies store (not my favorite idea) or browser storage (not that well supported yet). – Chris Nicola Feb 13 '13 at 18:34
  • 28
    @ChrisNicola Actually, in AngularJS all services are singletons. So the service is created the very first time it's requested (i.e. by a controller or another service) and all subsequent requests for it return the exact same instance. – Josh David Miller Feb 13 '13 at 18:46
  • @JoshDavidMiller You have the `isLoggedIn` property as a function? Wouldn't that be a boolean? – nicholas Apr 02 '13 at 22:21
  • 2
    It could be, but as a function we can remove the internals of how we store that information from the public API and into the private API. This makes refactoring later much easier. But the function would still *return* a boolean. – Josh David Miller Apr 02 '13 at 22:37
  • 7
    This might be a stupid question... but what happens if the user refreshes the page - is the login information lost? – Tomba May 09 '13 at 11:05
  • 10
    @Tomba That's a good question. :-) Indeed the info is lost on refresh. Usually, you'll want to store *some* session information in a cookie. That session info can also be checked when setting up the `AuthService`. This helps not only for page refreshes but for someone opening a link in a new tab. – Josh David Miller May 09 '13 at 15:43
  • Great point about opening a new tab, I hadn't considered that – Tomba May 15 '13 at 14:07
  • @JoshDavidMiller what would isLoggedIn look like? I'm assuming it needs to be updated from login and logout to trigger the watch? – turbo2oh Aug 16 '13 at 02:23
  • 1
    @turbo2oh Yeah, in most cases it probably just returns a local variable that's set on login and logout. For apps with offline support or other trickiness, this gets more complicated. – Josh David Miller Aug 16 '13 at 07:26
  • @JoshDavidMiller Any idea for this situation, I am very similar things that you were discussing here but not sure to plug all the things together to work in my specific scenario. Can you see this and help please: http://stackoverflow.com/questions/28381426/getting-logged-in-user-information-in-resolve-for-ui-router – Harry Joy Feb 08 '15 at 10:49
  • This is awesome. I've been looking everywhere for this. I feel like angular has a steep curve because if there's gaps in your understanding it's really hard to search for the answers. I'm wondering, is the best way to have an authenticated area with a menu and general interface that contains many different pages to implement this, and then have a general ApplicationCtrl that controls what you see and then have page controllers inside of that? Thanks again! – Z2VvZ3Vp May 01 '15 at 21:38
  • 2
    @PixMach You're 100% right about the learning curve. Your question is going to depend a lot of the specific case, but here are some general patterns. Keep separation of concerns: the UI related to initiating a login is separate from the auth itself, which is separate from the state of the auth, which is separate from any menu that may depend on said state. Nav/menu are often best handled by a single controller, and nested states (a la `ui-router`) and route resolves are a good way to keep auth controls DRY. What you wrote sounds about right. – Josh David Miller May 01 '15 at 22:43
5

I would amend the good response of Josh by adding that, as an AuthService is typically of interest of anyone (say, anyone but the login view should disappear if nobody is logged), maybe a simpler alternative would be to notify interested parties using $rootScope.$broadcast('loginStatusChanged', isLoggedIn); (1) (2), while interested parties (such as controllers) would listen using $scope.$on('loginStatusChanged', function (event, isLoggedIn) { $scope.isLoggedIn = isLoggedIn; }.

(1) $rootScope being injected as an argument of the service

(2) Note that, in the likely case of a asynchronous login operation, you'll want to notify Angular that the broadcast will change things, by including it in a $rootScope.$apply() function.

Now, speaking of keeping the user context in every/many controllers, you might not be happy listening for login changes in everyone of them, and might prefer to listen only in a topmost login controller, then adding other login-aware controllers as children/embedded controllers of this one. This way, the children controller will be able to see the inherited parent $scope properties such as your user context.

Jérôme Beau
  • 10,608
  • 5
  • 48
  • 52
  • 4
    Downvoted for incorrect explanation of the factory function. This misunderstanding was already addressed in [this comment](http://stackoverflow.com/questions/14206492/how-do-i-store-a-current-user-context-in-angular/14206567#comment20831205_14206567) months before you posted your answer. – Rhys van der Waerden Feb 27 '14 at 02:06