19

In order to determine if a user's session is authenticated, I need to make a $http request to the server before the first route is loaded. Before each route is loaded, an authentication service checks the status of the user and the access level required by the route, and if the user isn't authenticated for that route, it redirects to a login page. When the app is first loaded however, it has no knowledge of the user, so even if they have an authenticated session it will always redirect to the login page. So to fix this I'm trying to make a request to the server for the users status as a part of the app initialisation. The issue is that obviously $http calls are asynchronous, so how would I stop the app running until the request has finished?

I'm very new to Angular and front-end development in general, so my issue maybe a misunderstanding of javascript rather than of Angular.

Ben Davis
  • 617
  • 2
  • 11
  • 19

2 Answers2

19

You could accomplish that by using resolve in your routingProvider.

This allows you to wait for some promises to be resolved before the controller will be initiated.

Quote from the docs:

resolve - {Object.=} - An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated. If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired.

Simple example

    app.config(['$routeProvider', function($routeProvider) {
    $routeProvider.
        when('/', {templateUrl: 'home.html', controller: 'MyCtrl',resolve: {
            myVar: function($q,$http){
                var deffered = $q.defer();

                    // make your http request here and resolve its promise

                     $http.get('http://example.com/foobar')
                         .then(function(result){
                             deffered.resolve(result);
                          })

                return deffered.promise;
            }
        }}).
        otherwise({redirectTo: '/'});
}]);

myVar will then be injected to your controller, containing the promise data.

Avoiding additional DI parameter

You could also avoid the additional DI parameter by returning a service you were going to inject anyways:

app.config(['$routeProvider', function($routeProvider) {
        $routeProvider.
            when('/', {templateUrl: 'home.html', controller: 'MyCtrl',resolve: {
                myService: function($q,$http,myService){
                  var deffered = $q.defer();

                      /*  make your http request here
                      *   then, resolve the deffered's promise with your service.
                      */

                  deffered.resolve(myService),

                  return deffered.promise;
                }
            }}).
            otherwise({redirectTo: '/'});
    }]);

Obviously, you will have to store the result from your request anywhere in a shared service when doing things like that.


Have a look at Angular Docs / routeProvider

I have learned most of that stuff from that guy at egghead.io

Wottensprels
  • 3,307
  • 2
  • 29
  • 38
  • This is the way to go. Note that you can globally listen for routing events through: `$rootScope.$on('$routeChangeStart', callback)`, also works for `$routeChangeSuccess`, '$routeChangeError'. Useful is you want to display a loader, progress bar and handle errors. – Arnaud Rinquin Oct 29 '13 at 10:02
  • Would I have to do this on every controller? Like Arnaud's suggestion, at the moment I'm using `$rootScope.$on("routeChangeStart", callback)` to check authentication before every route change. Is there a way to use something similar to resolve using $on()? Ideally, before each route change it would check if the user is authenticated, and if it doesn't know who the user is, it asks the server. Would this be possible? – Ben Davis Oct 29 '13 at 13:56
  • I do not fully understand what you are trying to accomplish. You'll have to do that for every controller that depends on some data, yes. What is bad for you about the way you currently handle this? (with $on()) – Wottensprels Oct 29 '13 at 15:13
  • I'm sorry I'm not being clear, I appreciate the help. Using $on works fine if the app is aware that their session is authenticated, but how does the app know if this is the case when it's first loaded? My idea was to have the app query the server to get information about the user's session before the first page load. Essentially I need to run a synchronous http request during the app startup. – Ben Davis Oct 29 '13 at 15:39
  • I think i got it now :) A call to $http will return a promise that you could resolve within your routing configuration. When your application starts work, the Routeprovider will detect something like the root path and try to resolve your $http request. Based on that result, you could handle things. It's hard for me to say that in english, though, excuse me. I'll try to provide a simple snippet later this evening when i'm finally at home. Hopefully, this will clear things up – Wottensprels Oct 29 '13 at 15:51
  • Oh dear, it hadn't dawned on me that the root path is the only route (potentially confusing homophone) where I would need to query the server! So yeah, sorry it took me a while to realise your answer was the solution! The only caveat I can think of is that if a user was already in the app and went back to the root of the site, the check could be redundant. I suppose the promise used in the resolve could come from another service which would only make the $http request if it doesn't have any knowledge of the user. – Ben Davis Oct 29 '13 at 16:04
  • Sorry but I have another question: do you know of any better ways of passing this sort of initial data to the app when it's first loaded? It seems inefficient to make lots of different requests just to do the initial loading. Thanks a lot. – Ben Davis Oct 29 '13 at 16:07
  • I would assume that few applications would be in the need to fetch such a lot of data before their initialization to make this inefficent thinking of the time-to-load aspect. But on the other hand, i am quite to inexperienced to say so safely. I am unaware of another, commonly used method, though. – Wottensprels Oct 29 '13 at 20:21
  • you can add this $rootScope.$on('$routeChangeStart', callback) to angularJS run block and check there if the user session is valid or not. – Jideobi Benedine Ofomah Apr 03 '14 at 09:48
  • To send init. data to your application, the easiest way is to edit HTML on server (with PHP/Ruby...) and create a Javascript global object. – Thomas Decaux Oct 06 '14 at 16:10
  • Why do you need to use $q service? Can't you just return result of $http.get() directly (which is already a promise)? – Slava Fomin II Nov 20 '14 at 23:10
2

Encapsulate all your resources with a sessionCreator and return a promise. After resolve then to your controller so you can keep it free of specific promise code.

app.factory('sessionCreator', ['$http', '$q', 'urlApi',

  function ($http, $q, urlApi) {
    var deferred = $q.defer();

    $http.get(urlApi + '/startPoint').success(function(data) {
      // Do what you have to do for initialization.
      deferred.resolve();
    });

    return deferred.promise;
  }

]);

app.factory('Product', ['$resource', 'urlApi', 'sessionCreator',

  function($resource, urlApi, sessionCreator) {
    // encapsulate all yours services with `sessionCreator`
    return sessionCreator.then(function() {
      return $resource(urlApi + '/products', {}, {
        query: {method:'GET', params:{}, isArray: true}
      });
    });
  }

]);

app.config(['$routeProvider', function ($routeProvider) {

  var access = routingConfig.accessLevels;

  $routeProvider
    .when('/product', {
      templateUrl: 'views/products.html', controller: 'ProductCtrl',
      // resolve then in router configuration so you don't have to call `then()` inside your controllers
      resolve: { Product: ['Product', function(Product) { return Product; }] }
    })
    .otherwise({ redirectTo: '/' });
}]);
armoucar
  • 408
  • 1
  • 4
  • 15