Stephen
Stephen

Reputation: 5460

In an Angular, how would I get bootstrap data from the server before launch?

A bit of info. I'm working on a single page app, but am attempting to make it just an HTML file, rather than an actual dynamic page that contains all the bootstrap information in it. I'm also hoping to, when the app boots (or perhaps prior to), check to see if the current session is 'logged in', and if not then direct the hash to the 'login'.

I'm new to Angular, and am having a difficult time figuring out how to program out this flow. So, in essence..

  1. HTML page loaded with 'deferred' bootstrap
  2. Hit URL to get login status
  3. If status is 'not logged in', direct to #/login
  4. Start app

Any pointers on where #2 and #3 would live? In my 'easy world' I'd just use jquery to grab that data, and then call the angular.resumeBootstrap([appname]). But, as I'm trying to actually learn Angular rather than just hack around the parts I don't understand, I'd like to know what would be used in this place. I was looking at providers, but I'm not sure that's what I need.

Thanks!

EDIT

Based on @Mik378's answer, I've updated my code to the following as a test. It works to a point, but as the 'get' is async, it allows the application to continue loading whatever it was before then shooting off the status results..

var app = angular.module('ping', [
    'ngRoute',
    'ping.controllers'
]).provider('security', function() {
    this.$get = ['$http', function($http) {
            var service = {
                getLoginStatus: function () {
                    if (service.isAuthenticated())
                        return $q.when(service.currentUser);
                    else
                        return $http.get('/login/status').then(function (response) {
                            console.log(response);
                            service.loggedIn = response.data.loggedIn;
                            console.log(service);
                            return service.currentUser;
                        });
                },
                isAuthenticated: function () {
                    return !!service.loggedIn;
                }
            };
            return service;
        }];
}).run(['security', function(security) {
        return security.getLoginStatus().then(function () {
            if(!security.isAuthenticated()) {
                console.log("BADNESS");
            } else {
                console.log("GOODNESS");
            }
        });
}]);

My hope was that this could somehow be completed prior to the first controller booting up so that it wasn't loading (or attempting to load) things that weren't even cleared for access yet.

EDIT #2

I started looking into the 'resolve' property in the router, and @Mik378 verified what I was looking at. My final code that is (currently) working how I want it is as follows (appologies about the super long code block)

angular.module('ping.controllers', [])
        .controller('Dashboard', ['$scope', function($scope) {
            console.log('dashboard')
        }])
        .controller('Login', ['$scope', function($scope) {
            console.log('login')
        }]);

var app = angular.module('ping', [
    'ngRoute',
    'ping.controllers'
]).run(['$rootScope', '$location', function($root, $location) {
    $root.$on("$routeChangeError", function (event, current, previous, rejection) {
        switch(rejection) {
            case "not logged in":
                $location.path("/login"); //<-- NOTE #1
                break;
        }
    });
}]);

app.provider('loginSecurity', function() {
    this.$get = ['$http', '$q', function($http, $q) {
            var service = {
                defer: $q.defer, //<-- NOTE #2
                requireAuth: function() { //<-- NOTE #3
                    var deferred = service.defer();
                    service.getLoginStatus().then(function() {
                        if (!service.isAuthenticated()) {
                            deferred.reject("not logged in")
                        } else {
                            deferred.resolve("Auth OK")
                        }
                    });
                    return deferred.promise;
                },
                getLoginStatus: function() {
                    if (service.isAuthenticated()) {
                        return $q.when(service.currentUser);
                    } else {
                        return $http.get('/login/status').then(function(response) {
                            console.log(response);
                            service.loggedIn = response.data.loggedIn;
                            console.log(service);
                            return service.currentUser;
                        });
                    }
                },
                isAuthenticated: function() {
                    return !!service.loggedIn;
                }
            };
            return service;
        }
    ];
});

app.config(['$routeProvider', function($routeProvider) {
        console.log('Routing loading');
        $routeProvider.when('/', {
            templateUrl: 'static/scripts/dashboard/template.html',
            controller: 'Dashboard',
            resolve: {'loginSecurity': function (loginSecurity) {
                return loginSecurity.requireAuth(); //<- NOTE #4
            }}
        });
        $routeProvider.when('/login', {
            templateUrl: 'static/scripts/login/template.html',
            controller: 'Login'
        });
        $routeProvider.otherwise({redirectTo: '/404'});
}]);

Notes:

  1. This section hooks into routing failures. In the case of a "no login", I wanted to catch the failure and pop the person over to the login page.
  2. I can't get access to the $q inside of the requireAuth function, so I grabbed a reference to it. Perhaps a better way of doing this exists?
  3. This function wraps up the other two - it uses the promise returned from getLoginStatus, but returns its own promise that will be rejected if the end result from the getLoginStatus winds up with the user not being logged in. Sort of a round-about way of doing it.
  4. This returns #3's promise, which is used by the $routeProvider.. so if it fails, the routing fails and you end up catching it at #1.

Whew. I think that's enough for a day. Time for a beer.

Upvotes: 4

Views: 413

Answers (1)

Mik378
Mik378

Reputation: 22191

No need to use a deferred bootstrap for your case:

angular.module('app').run(['security', '$location', function(security) {
  // Get the current user when the application starts
  // (in case they are still logged in from a previous session)
  security.requestCurrentUser().then(function(){
       if(!security.isAuthenticated())
       $location.path('yourPathToLoginPage')
     };   //service returning the current user, if already logged in
}]);

this method requestCurrentUser would be the following:

requestCurrentUser: function () {
                    if (service.isAuthenticated())
                        return $q.when(service.currentUser);
                    else
                        return $http.get('/api/current-user').then(function (response) {
                            service.currentUser = response.data.user;
                            return service.currentUser;
                        });
                }

and inside security service again:

isAuthenticated: function () {
                    return !!service.currentUser;
                }

Note the run method of the module => As soon as the application runs, this service is called.

-- UPDATE --

To prevent any controller to be initialized before the promise provided by requestCurrentUser is resolved, a better solution, as evoked in the comments below, is to use the resolve route property .

Upvotes: 2

Related Questions