├── app ├── css │ ├── .gitignore │ └── app.css ├── img │ └── .gitignore ├── js │ ├── filters.js │ ├── directives.js │ ├── routingConfig.js │ ├── services.js │ ├── controllers.js │ └── app.js ├── partials │ ├── .gitignore │ ├── 404.jade │ ├── admin.jade │ ├── home.jade │ ├── private.jade │ ├── register.jade │ └── login.jade └── index.jade ├── Procfile ├── README.md ├── package.json ├── .gitattributes ├── server.js ├── routes.js ├── models └── User.js └── .gitignore /app/css/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/js/filters.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/js/directives.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/partials/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node server.js -------------------------------------------------------------------------------- /app/partials/404.jade: -------------------------------------------------------------------------------- 1 | h1 404 2 | p Ain't nothing here -------------------------------------------------------------------------------- /app/partials/admin.jade: -------------------------------------------------------------------------------- 1 | h1 Admin 2 | p This view is only visible to administrators. -------------------------------------------------------------------------------- /app/partials/home.jade: -------------------------------------------------------------------------------- 1 | h1 Hello 2 | p This page is only accessible to logged in users -------------------------------------------------------------------------------- /app/partials/private.jade: -------------------------------------------------------------------------------- 1 | h1 Private view 2 | p This page is only accessible to logged in users -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-client-side-auth 2 | ======================== 3 | 4 | One way to implement authentication/authorization in Angular applications. 5 | 6 | * Blogpost: http://www.frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/ 7 | * Live version: http://angular-client-side-auth.herokuapp.com/ 8 | -------------------------------------------------------------------------------- /app/js/routingConfig.js: -------------------------------------------------------------------------------- 1 | (function(exports){ 2 | 3 | exports.userRoles = { 4 | public: 1, // 001 5 | user: 2, // 010 6 | admin: 4 // 100 7 | }; 8 | 9 | exports.accessLevels = { 10 | public: 7, // 111 11 | anon: 1, // 001 12 | user: 6, // 110 13 | admin: 4 // 100 14 | }; 15 | 16 | })(typeof exports === 'undefined'? this['routingConfig']={}: exports); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-client-side-auth", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "express": "*", 6 | "jade": "*", 7 | "passport": "*", 8 | "passport-local": "*", 9 | "underscore": "*" 10 | }, 11 | "subdomain": "angular-client-side-auth", 12 | "scripts": { 13 | "start": "server.js" 14 | }, 15 | "engines": { 16 | "node": "0.8.x" 17 | } 18 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /app/js/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Services */ 4 | 5 | angular.module('myApp.services', []).factory('Auth', function($http){ 6 | return { 7 | register: function(user, success, error) { 8 | $http.post('/register', user).success(success).error(error); 9 | }, 10 | login: function(user, success, error) { 11 | $http.post('/login', user).success(success).error(error); 12 | }, 13 | logout: function(success, error) { 14 | $http.post('/logout').success(success).error(error); 15 | } 16 | }; 17 | }); -------------------------------------------------------------------------------- /app/css/app.css: -------------------------------------------------------------------------------- 1 | #cover { 2 | position:absolute; 3 | height:100%; 4 | width:100%; 5 | background:white; 6 | } 7 | 8 | #userInfo { 9 | float:right;padding:8px; 10 | } 11 | 12 | .fade-hide-setup, .fade-show-setup { 13 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 14 | -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 15 | -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 16 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 17 | } 18 | 19 | .fade-hide-setup { 20 | opacity:1; 21 | } 22 | .fade-hide-setup.fade-hide-start { 23 | opacity:0; 24 | } 25 | 26 | .fade-show-setup { 27 | opacity:0; 28 | } 29 | .fade-show-setup.fade-show-start { 30 | opacity:1; 31 | } -------------------------------------------------------------------------------- /app/partials/register.jade: -------------------------------------------------------------------------------- 1 | h1 Register 2 | form.form-horizontal(ng-submit="register()", name="registerForm") 3 | .control-group 4 | label.control-label(for="username") Username 5 | .controls 6 | input(type="text", ng-model="username", placeholder="Username", name="username", required) 7 | .control-group 8 | label.control-label(for="password") Password 9 | .controls 10 | input(type="password", ng-model="password", placeholder="Password", name="password", required) 11 | .control-group 12 | label.radio.inline 13 | input(type="radio", name="role", ng-model="role", id="adminRole", ng-value="{{ userRoles.admin }}") 14 | | Administrator 15 | label.radio.inline 16 | input(type="radio", name="role", ng-model="role", id="adminRole", ng-value="{{ userRoles.user }}") 17 | | Normal user 18 | .control-group 19 | .controls 20 | button.btn(type="submit") Submit 21 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , http = require('http') 3 | , passport = require('passport') 4 | , path = require('path') 5 | , User = require('./models/User.js'); 6 | 7 | var app = express(); 8 | 9 | app.set('views', __dirname + '/app'); 10 | app.set('view engine', 'jade'); 11 | app.use(express.logger('dev')) 12 | app.use(express.cookieParser()); 13 | app.use(express.bodyParser()); 14 | app.use(express.methodOverride()); 15 | app.use(express.static(path.join(__dirname, 'app'))); 16 | app.use(express.session( 17 | { 18 | secret: "Superdupersecret", 19 | cookie: { maxAge: 3600000 * 24 * 7 } 20 | })); 21 | app.use(passport.initialize()); 22 | app.use(passport.session()); 23 | 24 | passport.use(User.localStrategy); 25 | passport.serializeUser(User.serializeUser); 26 | passport.deserializeUser(User.deserializeUser); 27 | 28 | require('./routes.js')(app); 29 | 30 | app.set('port', process.env.PORT || 8000); 31 | http.createServer(app).listen(app.get('port'), function(){ 32 | console.log("Express server listening on port " + app.get('port')); 33 | }); -------------------------------------------------------------------------------- /app/partials/login.jade: -------------------------------------------------------------------------------- 1 | .hero-unit 2 | h1 Log in 3 | p This site is an example of how one can implement role based authentication in Angular applications as outlined in 4 | a(href="http://www.frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/") this blogpost 5 | | . All the code can be found in 6 | a(href="https://github.com/fnakstad/angular-client-side-auth") this GitHub repository 7 | | . You can either register a new user, or log in with one of the two predefined users: 8 | ul 9 | li admin/123 10 | li user/123 11 | form.form-horizontal(ng-submit="login()", name="loginForm") 12 | .control-group 13 | label.control-label(for="username") Username 14 | .controls 15 | input(type="text", ng-model="username", placeholder="Username", name="username", required) 16 | .control-group 17 | label.control-label(for="password") Password 18 | .controls 19 | input(type="password", ng-model="password", placeholder="Password", name="password", required) 20 | .control-group 21 | .controls 22 | button.btn(type="submit") Submit 23 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , passport = require('passport') 3 | , User = require('./models/User.js') 4 | , userRoles = require('./app/js/routingConfig').userRoles; 5 | 6 | module.exports = function(app) { 7 | 8 | // Views 9 | app.get('/partials/*', function (req, res) { 10 | var requestedView = path.join('./', req.url); 11 | res.render(requestedView); 12 | }); 13 | 14 | // Auth stuff 15 | app.post('/login', function(req, res, next) { 16 | passport.authenticate('local', 17 | function(err, user) { 18 | if(err) { return next(err); } 19 | if(!user) { return res.send(400); } 20 | 21 | req.logIn(user, function(err) { 22 | if(err) { 23 | return next(err); 24 | } 25 | res.json(200, { "role": user.role, "username": user.username }); 26 | }); 27 | })(req, res, next); 28 | }); 29 | 30 | app.post('/logout', function(req, res) { 31 | req.logout(); 32 | res.send(200); 33 | }); 34 | 35 | app.post('/register', function(req, res, next) { 36 | User.addUser(req.body.username, req.body.password, req.body.role, function(err, user) { 37 | if(err === 'UserAlreadyExists') return res.send(403, "User already exists"); 38 | else if(err) return res.send(500); 39 | 40 | req.logIn(user, function(err) { 41 | if(err) { next(err); } 42 | else { res.json(200, { "role": user.role, "username": user.username }); } 43 | }); 44 | }); 45 | }); 46 | 47 | // All other get requests should be handled by AngularJS's client-side routing system 48 | app.get('/*', function(req, res){ 49 | var role = userRoles.public, username = ''; 50 | if(req.user) { 51 | role = req.user.role; 52 | username = req.user.username; 53 | } 54 | res.cookie('user', JSON.stringify({ 55 | 'username': username, 56 | 'role': role 57 | })); 58 | res.render('index'); 59 | }); 60 | } -------------------------------------------------------------------------------- /app/js/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Controllers */ 4 | 5 | function AppCtrl($rootScope, $scope, $location, Auth) { 6 | 7 | $rootScope.accessLevels = routingConfig.accessLevels; 8 | $rootScope.userRoles = routingConfig.userRoles; 9 | 10 | $scope.logout = function() { 11 | Auth.logout(function() { 12 | $rootScope.user.role = routingConfig.userRoles.public; 13 | $location.path('/login'); 14 | }, function() { 15 | $rootScope.error = "Failed to logout"; 16 | }); 17 | }; 18 | } 19 | 20 | function LoginCtrl($rootScope, $scope, $location, Auth) { 21 | $rootScope.activeNavItem = 'login'; 22 | 23 | $scope.login = function() { 24 | Auth.login({ 25 | username: $scope.username, 26 | password: $scope.password 27 | }, 28 | function(res) { 29 | console.log(res); 30 | $rootScope.user = res; 31 | $location.path('/'); 32 | }, 33 | function(err) { 34 | $rootScope.error = "Failed to login"; 35 | }); 36 | }; 37 | } 38 | 39 | function MenuCtrl($rootScope, $scope) { 40 | $scope.showMenuItem = function(accessLevel) { 41 | return !!($rootScope.user.role & accessLevel); 42 | } 43 | } 44 | 45 | function HomeCtrl($rootScope) { 46 | $rootScope.activeNavItem = 'home'; 47 | } 48 | 49 | function RegisterCtrl($rootScope, $scope, $location, Auth) { 50 | $rootScope.activeNavItem = 'register'; 51 | $scope.role = routingConfig.userRoles.user; 52 | 53 | $scope.register = function() { 54 | Auth.register({ 55 | username: $scope.username, 56 | password: $scope.password, 57 | role: $scope.role 58 | }, 59 | function(res) { 60 | $rootScope.user = res; 61 | $location.path('/'); 62 | }, 63 | function(err) { 64 | console.log(err); 65 | $rootScope.error = err; 66 | }); 67 | }; 68 | } 69 | 70 | function PrivateCtrl($rootScope) { 71 | $rootScope.activeNavItem = 'private'; 72 | } 73 | 74 | function AdminCtrl($rootScope) { 75 | $rootScope.activeNavItem = 'admin'; 76 | } 77 | -------------------------------------------------------------------------------- /app/index.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | html(lang='en', data-ng-app='myApp') 3 | head 4 | meta(charset='utf-8') 5 | title Angular Auth Example 6 | link(rel='stylesheet', href='css/app.css') 7 | link(href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.2/css/bootstrap-combined.min.css", rel="stylesheet") 8 | body(data-ng-controller="AppCtrl") 9 | 10 | #cover(data-ng-hide="appInitialized", data-ng-animate="{ hide: 'fade-hide' }") 11 | 12 | ul.nav.nav-tabs(data-ng-controller="MenuCtrl") 13 | li(data-ng-show="showMenuItem(accessLevels.anon)", data-ng-class='{active: activeNavItem == "login"}') 14 | a(href='/login') Log in 15 | li(data-ng-show="showMenuItem(accessLevels.anon)", data-ng-class='{active: activeNavItem == "register"}') 16 | a(href='/register') Register 17 | li(data-ng-show="showMenuItem(accessLevels.user)", data-ng-class='{active: activeNavItem == "home"}') 18 | a(href='/') Home 19 | li(data-ng-show="showMenuItem(accessLevels.user)", data-ng-class='{active: activeNavItem == "private"}') 20 | a(href='/private') Private 21 | li(data-ng-show="showMenuItem(accessLevels.admin)", data-ng-class='{active: activeNavItem == "admin"}') 22 | a(href='/admin') Admin 23 | li(data-ng-show="showMenuItem(accessLevels.user)") 24 | button.btn-link.logout-button(ng-click="logout()", style="padding:8px;border:0px;") 25 | | Log out 26 | li#userInfo(data-ng-show="showMenuItem(accessLevels.user)") 27 | | Welcome  28 | span.label(data-ng-class='{"label-info": user.role == userRoles.user, "label-success": user.role == userRoles.admin}') {{ user.username }} 29 | 30 | .container 31 | div(data-ng-view='ng-view') 32 | .alert.alert-error(data-ng-bind="error", data-ng-show="error") 33 | 34 | 35 | script(src='https://ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular.js') 36 | script(src='https://ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular-cookies.js') 37 | script(src='/js/routingConfig.js') 38 | script(src='/js/app.js') 39 | script(src='/js/services.js') 40 | script(src='/js/controllers.js') 41 | script(src='/js/filters.js') 42 | script(src='/js/directives.js') 43 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | var User 2 | , _ = require('underscore') 3 | , passport = require('passport') 4 | , LocalStrategy = require('passport-local').Strategy 5 | , userRoles = require('../app/js/routingConfig').userRoles; 6 | 7 | var users = [ 8 | { 9 | id: 1, 10 | username: "user", 11 | password: "123", 12 | role: userRoles.user 13 | }, 14 | { 15 | id: 2, 16 | username: "admin", 17 | password: "123", 18 | role: userRoles.admin 19 | } 20 | ]; 21 | 22 | function addUser(username, password, role, callback) { 23 | if(findByUsername(username) !== undefined) return callback("UserAlreadyExists"); 24 | 25 | // Clean up when 500 users reached 26 | if(users.length > 500) { 27 | users = users.slice(0, 2); 28 | } 29 | 30 | var user = { 31 | id: _.max(users, function(user) { return user.id; }).id + 1, 32 | username: username, 33 | password: password, 34 | role: role 35 | }; 36 | users.push(user); 37 | callback(null, user); 38 | }; 39 | 40 | function findById (id) { 41 | return _.find(users, function(user) { return user.id === id }); 42 | }; 43 | 44 | function findByUsername(username) { 45 | return _.find(users, function(user) { return user.username === username; }); 46 | }; 47 | 48 | var localStrategy = new LocalStrategy( 49 | function(username, password, done) { 50 | 51 | var user = findByUsername(username); 52 | 53 | if(!user) { 54 | done(null, false, { message: 'Incorrect username.' }); 55 | } 56 | else if(user.password != password) { 57 | done(null, false, { message: 'Incorrect username.' }); 58 | } 59 | else { 60 | return done(null, user); 61 | } 62 | 63 | } 64 | ) 65 | 66 | function serializeUser(user, done) { 67 | done(null, user.id); 68 | } 69 | 70 | function deserializeUser(id, done) { 71 | var user = findById(id); 72 | if(user) { done(null, user); } 73 | else { done({ message: 'User not found' }, null); } 74 | } 75 | 76 | module.exports = { 77 | addUser: addUser, 78 | findById: findById, 79 | findByUsername: findByUsername, 80 | localStrategy: localStrategy, 81 | serializeUser: serializeUser, 82 | deserializeUser: deserializeUser 83 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | .idea/ 19 | node_modules/ 20 | 21 | # External tool builders 22 | .externalToolBuilders/ 23 | 24 | # Locally stored "Eclipse launch configurations" 25 | *.launch 26 | 27 | # CDT-specific 28 | .cproject 29 | 30 | # PDT-specific 31 | .buildpath 32 | 33 | 34 | ################# 35 | ## Visual Studio 36 | ################# 37 | 38 | ## Ignore Visual Studio temporary files, build results, and 39 | ## files generated by popular Visual Studio add-ons. 40 | 41 | # User-specific files 42 | *.suo 43 | *.user 44 | *.sln.docstates 45 | 46 | # Build results 47 | [Dd]ebug/ 48 | [Rr]elease/ 49 | *_i.c 50 | *_p.c 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.vspscc 65 | .builds 66 | *.dotCover 67 | 68 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 69 | #packages/ 70 | 71 | # Visual C++ cache files 72 | ipch/ 73 | *.aps 74 | *.ncb 75 | *.opensdf 76 | *.sdf 77 | 78 | # Visual Studio profiler 79 | *.psess 80 | *.vsp 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper* 84 | 85 | # Installshield output folder 86 | [Ee]xpress 87 | 88 | # DocProject is a documentation generator add-in 89 | DocProject/buildhelp/ 90 | DocProject/Help/*.HxT 91 | DocProject/Help/*.HxC 92 | DocProject/Help/*.hhc 93 | DocProject/Help/*.hhk 94 | DocProject/Help/*.hhp 95 | DocProject/Help/Html2 96 | DocProject/Help/html 97 | 98 | # Click-Once directory 99 | publish 100 | 101 | # Others 102 | [Bb]in 103 | [Oo]bj 104 | sql 105 | TestResults 106 | *.Cache 107 | ClientBin 108 | stylecop.* 109 | ~$* 110 | *.dbmdl 111 | Generated_Code #added for RIA/Silverlight projects 112 | 113 | # Backup & report files from converting an old project file to a newer 114 | # Visual Studio version. Backup files are not needed, because we have git ;-) 115 | _UpgradeReport_Files/ 116 | Backup*/ 117 | UpgradeLog*.XML 118 | 119 | 120 | 121 | ############ 122 | ## Windows 123 | ############ 124 | 125 | # Windows image file caches 126 | Thumbs.db 127 | 128 | # Folder config file 129 | Desktop.ini 130 | 131 | 132 | ############# 133 | ## Python 134 | ############# 135 | 136 | *.py[co] 137 | 138 | # Packages 139 | *.egg 140 | *.egg-info 141 | dist 142 | build 143 | eggs 144 | parts 145 | bin 146 | var 147 | sdist 148 | develop-eggs 149 | .installed.cfg 150 | 151 | # Installer logs 152 | pip-log.txt 153 | 154 | # Unit test / coverage reports 155 | .coverage 156 | .tox 157 | 158 | #Translations 159 | *.mo 160 | 161 | #Mr Developer 162 | .mr.developer.cfg 163 | 164 | # Mac crap 165 | .DS_Store 166 | -------------------------------------------------------------------------------- /app/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp', ['myApp.services', 'ngCookies']) 4 | 5 | .config(['$routeProvider', '$locationProvider', '$httpProvider', function ($routeProvider, $locationProvider, $httpProvider) { 6 | 7 | var access = routingConfig.accessLevels; 8 | 9 | $routeProvider.when('/', 10 | { 11 | templateUrl: '/partials/home', 12 | controller: HomeCtrl, 13 | access: access.user 14 | }); 15 | $routeProvider.when('/login', 16 | { 17 | templateUrl: '/partials/login', 18 | controller: LoginCtrl, 19 | access: access.anon 20 | }); 21 | $routeProvider.when('/register', 22 | { 23 | templateUrl: '/partials/register', 24 | controller: RegisterCtrl, 25 | access: access.anon 26 | }); 27 | $routeProvider.when('/private', 28 | { 29 | templateUrl: '/partials/private', 30 | controller: PrivateCtrl, 31 | access: access.user 32 | }); 33 | $routeProvider.when('/admin', 34 | { 35 | templateUrl: '/partials/admin', 36 | controller: AdminCtrl, 37 | access: access.admin 38 | }); 39 | $routeProvider.when('/404', 40 | { 41 | templateUrl: '/partials/404', 42 | access: access.public 43 | }); 44 | $routeProvider.otherwise({redirectTo:'/404'}); 45 | 46 | $locationProvider.html5Mode(true); 47 | 48 | var interceptor = ['$location', '$q', function($location, $q) { 49 | function success(response) { 50 | return response; 51 | } 52 | 53 | function error(response) { 54 | 55 | if(response.status === 401) { 56 | $location.path('/login'); 57 | return $q.reject(response); 58 | } 59 | else { 60 | return $q.reject(response); 61 | } 62 | } 63 | 64 | return function(promise) { 65 | return promise.then(success, error); 66 | } 67 | }]; 68 | 69 | $httpProvider.responseInterceptors.push(interceptor); 70 | 71 | }]) 72 | 73 | .run(['$rootScope', '$location', '$cookieStore', function ($rootScope, $location, $cookieStore) { 74 | 75 | $rootScope.user = $cookieStore.get('user') || { username: '', role: routingConfig.userRoles.public }; 76 | $cookieStore.remove('user'); 77 | 78 | $rootScope.$on("$routeChangeStart", function (event, next, current) { 79 | $rootScope.error = null; 80 | if (!(next.access & $rootScope.user.role)) { 81 | if($rootScope.user.role === routingConfig.userRoles.user || $rootScope.user.role === routingConfig.userRoles.admin) { 82 | $location.path('/'); 83 | } 84 | else { 85 | $location.path('/login'); 86 | } 87 | } 88 | }); 89 | 90 | $rootScope.appInitialized = true; 91 | }]); --------------------------------------------------------------------------------