├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── changelog.md ├── demo ├── constant-to-specific-module.html ├── demo.config.json ├── error-handler.html ├── error.html ├── index.html ├── multi.html └── simple.html ├── karma.conf.js ├── package.json ├── server.js ├── src ├── deferred-bootstrap.js ├── deferred-bootstrap.prefix └── deferred-bootstrap.suffix └── test └── deferred-bootstrap.spec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | bower_components 4 | dist 5 | coverage 6 | npm-debug.log 7 | *.iml 8 | .idea -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": ["describe", "it", "beforeEach", "waitsFor", "expect", "angular", "spyOn", "jasmine", "define"], 3 | "esnext": false, 4 | "bitwise": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "immed": true, 8 | "indent": 2, 9 | "latedef": false, 10 | "newcap": true, 11 | "noarg": true, 12 | "quotmark": "single", 13 | "undef": true, 14 | "unused": true, 15 | "strict": true, 16 | "globalstrict": true, 17 | "trailing": true, 18 | "smarttabs": true, 19 | "browser": true, 20 | "node": true 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | before_install: 6 | - npm install -g grunt-cli karma-cli 7 | 8 | before_script: 9 | - export DISPLAY=:99.0 10 | - sh -e /etc/init.d/xvfb start -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | TBD 4 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | 5 | require('load-grunt-tasks')(grunt); 6 | 7 | grunt.initConfig({ 8 | 9 | pkg: grunt.file.readJSON('package.json'), 10 | 11 | language: grunt.option('lang') || 'en', 12 | 13 | meta: { 14 | banner: '/**\n * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 15 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 16 | ' * <%= pkg.homepage %>\n' + 17 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>\n' + 18 | ' * License: <%= pkg.license %>\n */\n' 19 | }, 20 | 21 | build_dir: 'dist', 22 | 23 | files: { 24 | 25 | core: [ 26 | 'src/deferred-bootstrap.js' 27 | ], 28 | 29 | test: ['test/**/*.js'] 30 | }, 31 | 32 | watch: { 33 | 34 | scripts: { 35 | files: ['Gruntfile.js', '<%= files.core %>', '<%= files.test %>'], 36 | tasks: ['jshint:all', 'karma:unit'] 37 | }, 38 | 39 | livereload: { 40 | options: { 41 | livereload: true 42 | }, 43 | files: ['src/**/*.js'], 44 | tasks: ['jshint', 'karma:unit', 'concat'] 45 | } 46 | }, 47 | 48 | jshint: { 49 | 50 | options: { 51 | jshintrc: true 52 | }, 53 | 54 | all: ['Gruntfile.js', '<%= files.core %>', '<%= files.test %>'], 55 | 56 | core: { 57 | files: { 58 | src: ['<%= files.core %>'] 59 | } 60 | }, 61 | 62 | test: { 63 | files: { 64 | src: ['<%= files.test %>'] 65 | } 66 | } 67 | }, 68 | 69 | express: { 70 | server: { 71 | options: { 72 | port: 3005, 73 | bases: '.', 74 | server: __dirname + '/server.js' 75 | } 76 | } 77 | }, 78 | 79 | concat: { 80 | 81 | banner: { 82 | options: { 83 | banner: '<%= meta.banner %>' 84 | }, 85 | src: '<%= concat.core.dest %>', 86 | dest: '<%= concat.core.dest %>' 87 | }, 88 | 89 | core: { 90 | src: ['src/deferred-bootstrap.prefix', '<%= files.core %>', 'src/deferred-bootstrap.suffix'], 91 | dest: '<%= build_dir %>/angular-deferred-bootstrap.js' 92 | } 93 | 94 | }, 95 | 96 | uglify: { 97 | core: { 98 | files: { 99 | '<%= build_dir %>/angular-deferred-bootstrap.min.js': '<%= concat.core.dest %>' 100 | } 101 | } 102 | }, 103 | 104 | karma: { 105 | 'unit': { 106 | configFile: 'karma.conf.js', 107 | singleRun: true, 108 | browsers: ['PhantomJS'] 109 | }, 110 | 111 | 'chrome-unit': { 112 | configFile: 'karma.conf.js', 113 | singleRun: true, 114 | browsers: ['Chrome'] 115 | }, 116 | 117 | 'firefox-unit': { 118 | configFile: 'karma.conf.js', 119 | singleRun: true, 120 | browsers: ['Firefox'] 121 | }, 122 | 123 | 'debug-unit': { 124 | configFile: 'karma.conf.js', 125 | singleRun: false 126 | } 127 | } 128 | }); 129 | 130 | grunt.registerTask('default', ['jshint:all', 'karma:unit']); 131 | grunt.registerTask('test', ['karma:unit']); 132 | 133 | // Advanced test tasks 134 | grunt.registerTask('test-chrome', ['karma:chrome-unit']); 135 | grunt.registerTask('test-firefox', ['karma:firefox-unit']); 136 | grunt.registerTask('test-all', ['karma']); 137 | 138 | grunt.registerTask('build', [ 139 | 'jshint:all', 140 | 'karma:unit', 141 | 'concat:core', 142 | 'concat:banner', 143 | 'uglify:core' 144 | ]); 145 | 146 | // For development purpose. 147 | grunt.registerTask('dev', ['jshint', 'karma:unit', 'concat', 'watch:livereload']); 148 | grunt.registerTask('server', ['express', 'express-keepalive']); 149 | }; 150 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 philippd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-deferred-bootstrap 2 | 3 | [![Build Status](https://travis-ci.org/philippd/angular-deferred-bootstrap.svg?branch=master)](https://travis-ci.org/philippd/angular-deferred-bootstrap) [![Coverage Status](https://img.shields.io/coveralls/philippd/angular-deferred-bootstrap.svg)](https://coveralls.io/r/philippd/angular-deferred-bootstrap?branch=master)[![NPM version](https://badge.fury.io/js/angular-deferred-bootstrap.svg)](http://badge.fury.io/js/angular-deferred-bootstrap) 4 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/philippd/angular-deferred-bootstrap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | > Initialize your AngularJS app with constants loaded from the back-end. 7 | 8 | This component provides a global resolve function for your app. It works similar to the resolve functions you may know from ngRoute or ui-router: You define what needs to be loaded from the back-end before your application can be started and the deferred bootstrapper takes care of loading the data and bootstrapping the application. 9 | 10 | - [Install](#user-content-install) 11 | - [Usage](#user-content-usage) 12 | - [Loading and error indicators](#user-content-loading-and-error-indicators) 13 | - [Advanced usage](#user-content-advanced-usage) 14 | - [Attach constants to specific modules](#user-content-attach-constants-to-specific-modules) 15 | - [Custom injector modules](#user-content-custom-injector-modules) 16 | - [Bootstrap Config and StrictDi](#user-content-bootstrap-config-and-strictdi) 17 | - [Testing](#user-content-testing) 18 | - [License](#user-content-license) 19 | 20 | ## Install 21 | 22 | #### [Bower](http://bower.io) 23 | 24 | ``` 25 | bower install --save angular-deferred-bootstrap 26 | ``` 27 | 28 | 29 | #### [npm](http://www.npmjs.com) 30 | 31 | ``` 32 | npm install angular-deferred-bootstrap 33 | ``` 34 | 35 | #### [jspm](http://jspm.io) 36 | 37 | ``` 38 | jspm install angular-deferred-bootstrap 39 | ``` 40 | 41 | ## Usage 42 | 43 | Instead of using the ```ng-app``` directive or ```angular.bootstrap()```, use the ```deferredBootstrapper``` to initialize your app: 44 | ```js 45 | deferredBootstrapper.bootstrap({ 46 | element: document.body, 47 | module: 'MyApp', 48 | resolve: { 49 | APP_CONFIG: ['$http', function ($http) { 50 | return $http.get('/api/demo-config'); 51 | }] 52 | } 53 | }); 54 | ``` 55 | 56 | This will make the response of your ```$http``` call available as a constant to your AngularJS app. This means you can now use dependency injection to access the ```APP_CONFIG``` wherever you need it in your app (even in ```config()``` blocks!). 57 | ```js 58 | angular.module('MyApp', []) 59 | .config(function (APP_CONFIG) { 60 | console.log('APP_CONFIG is: ' + JSON.stringify(APP_CONFIG)); 61 | }) 62 | ``` 63 | 64 | ## Loading and error indicators 65 | To make it possible to conditionally show a loading indicator or an error message when the initialization fails, the following CSS classes are set on the HTML body: 66 | 67 | * **deferred-bootstrap-loading** while the data is loading 68 | * **deferred-bootstrap-error** if an error occurs in a resolve function and the app can not be bootstrapped 69 | 70 | Have a look at the demo pages to see this in action. 71 | 72 | ## Advanced usage 73 | You can have multiple constants resolved for your app and you can do in the resolve function whatever is necessary before the app is started. The only constraint is, that the function has to return a promise. 74 | 75 | To handle exceptions when the promises are resolved, you can add an ```onError``` function to the configuration object. 76 | 77 | Example: 78 | ```js 79 | deferredBootstrapper.bootstrap({ 80 | element: document.body, 81 | module: 'MyApp', 82 | resolve: { 83 | APP_CONFIG: ['$http', function ($http) { 84 | return $http.get('/api/demo-config'); 85 | }], 86 | OTHER_CONSTANT: ['$http', '$q', '$timeout', function ($http, $q, $timeout) { 87 | var deferred = $q.defer(); 88 | $timeout(function () { 89 | deferred.resolve('MyConstant'); 90 | }, 2000); 91 | return deferred.promise; 92 | }] 93 | }, 94 | onError: function (error) { 95 | alert('Could not bootstrap, error: ' + error); 96 | } 97 | }); 98 | ``` 99 | 100 | ## Attach constants to specific modules 101 | By default, any constants specified in the ```resolve``` object will be attached to the module specified in the ```module``` option. If you have a need to attach constants to different modules then this can be achieved by using ```moduleResolves```: 102 | 103 | ```js 104 | window.deferredBootstrapper.bootstrap({ 105 | element: document.body, 106 | module: 'myApp', 107 | moduleResolves: [ 108 | { 109 | module: 'myApp.settings', 110 | resolve: { 111 | CONSTANT_ONE: ['$http', function ($http) { 112 | return $http.get(); 113 | }], 114 | CONSTANT_TWO: ['$http', function ($http) { 115 | return $http.get(); 116 | }] 117 | } 118 | }, 119 | { 120 | module: 'myApp.moreSettings', 121 | resolve: { 122 | CONSTANT_THREE: ['$http', function ($http) { 123 | return $http.get(); 124 | }] 125 | } 126 | } 127 | ] 128 | }) 129 | ``` 130 | 131 | In the above example, ```CONSTANT_ONE``` and ```CONSTANT_TWO``` will be added to the ```'myApp.settings'``` module and ```CONSTANT_THREE``` will be added to the ```'myApp.moreSettings'``` module. There are no limits on how many ```moduleResolve``` objects you create and also no limit on the number of constants per ```moduleResolve``` 132 | 133 | **Note** that only ```resolve``` or ```moduleResolves``` can be used - using both in the same configuration will throw an exception 134 | 135 | ## Custom injector modules 136 | By default, the injector that calls your resolve functions only provides the services from the AngularJS core module ```ng```. If you have a use case where you want to use one of your existing services to get configuration at bootstrap time, you can specify which modules should be made available and inject services from those modules in the resolve function. An example is below: 137 | 138 | ```js 139 | deferredBootstrapper.bootstrap({ 140 | element: document.body, 141 | module: 'myApp', 142 | injectorModules: 'myApp.settings', 143 | resolve: { 144 | SETTINGS: ['SettingsService', function (SettingsService) { 145 | return SettingsService.get('/settings'); 146 | }] 147 | } 148 | }); 149 | ``` 150 | 151 | The ```injectorModules``` option can also take an array of modules. If you have multiple services spread across different modules you can also inject them: 152 | 153 | ```js 154 | deferredBootstrapper.bootstrap({ 155 | element: document.body, 156 | module: 'myApp', 157 | injectorModules: ['myApp.settings', 'myApp.foo'] 158 | resolve: { 159 | SETTINGS: ['SettingsService', function (SettingsService) { 160 | return SettingsService.get('/settings'); 161 | }], 162 | FOO: ['FooService', function (FooService) { 163 | return FooService.get('/foo'); 164 | }] 165 | } 166 | }); 167 | ``` 168 | 169 | **Note** that the services which are injected in your resolve functions will be instantiated again when the actual app starts. This means you can not save any state in your services in the resolve functions. 170 | 171 | 172 | ## Bootstrap Config and StrictDi 173 | To set the AngularJS ```strictDi``` mode, or any future ```angular.boostrap``` config parameters, pass in an optional config object called ```bootstrapConfig```: 174 | ```js 175 | deferredBootstrapper.bootstrap({ 176 | element: document.body, 177 | module: 'MyApp', 178 | bootstrapConfig: { 179 | strictDi: true 180 | }, 181 | resolve: { 182 | APP_CONFIG: ['$http', function ($http) { 183 | return $http.get('/api/demo-config'); 184 | }] 185 | } 186 | }); 187 | ``` 188 | 189 | ## Testing 190 | Since the constants that deferredBootstrapper adds to your applications module are not available in your unit tests, it makes sense to provide them in a global beforeEach(): 191 | ```js 192 | beforeEach(function () { 193 | module(function ($provide) { 194 | $provide.constant('APP_CONFIG', { someUrl: '/dummyValue' }); 195 | }); 196 | }); 197 | ``` 198 | 199 | ## License 200 | 201 | [MIT](http://opensource.org/licenses/MIT) © Philipp Denzler 202 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-deferred-bootstrap", 3 | "version": "0.1.9", 4 | "homepage": "https://github.com/philippd/angular-deferred-bootstrap", 5 | "authors": [ 6 | "Philipp Denzler " 7 | ], 8 | "main": "angular-deferred-bootstrap.js", 9 | "license": "MIT", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "devDependencies": { 18 | "angular": "1.2.21", 19 | "angular-mocks": "1.2.21" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.1.9 - 2015-12-17 4 | - Fixed broken support for AMD modules 5 | 6 | ## 0.1.8 - 2015-12-15 7 | - Add support for CommonJS and AMD modules 8 | 9 | ## 0.1.7 - 2015-05-09 10 | - Add JSPM support 11 | 12 | ## 0.1.6 - 2015-04-09 13 | - Remove error class after successful bootstrap, fixes [#32](https://github.com/philippd/angular-deferred-bootstrap/issues/32) 14 | 15 | ## 0.1.5 - 2014-10-08 16 | - Fixed call of resolve function to not fail if angular.forEach passes the object as third argument in each iteration (Angular > 1.3.0-rc.1), see [#20](https://github.com/philippd/angular-deferred-bootstrap/issues/20) 17 | - Pass additional configuration to bootstrap the AngularJS app (for example ```strictDi``` mode), implemented by [@jeffsheets](https://github.com/jeffsheets), see [#23](https://github.com/philippd/angular-deferred-bootstrap/pull/23) 18 | 19 | ## 0.1.4 - 2014-10-03 20 | - Improved injector creation to allow injecting services depending on the $rootElement, implemented by [@B8li](https://github.com/B8li), see [#22](https://github.com/philippd/angular-deferred-bootstrap/pull/22) 21 | 22 | ## 0.1.3 - 2014-08-08 23 | - Fixed bug that the resolve function was called twice during bootstrapping, see aea0e2058e3c5bb881a92a1b3c277e4f0aed6dc5 24 | 25 | ## 0.1.2 - 2014-08-04 26 | - Added ability to specify which module a constant is added to, implemented by [@Shepless](https://github.com/Shepless), see [#16](https://github.com/philippd/angular-deferred-bootstrap/pull/16) 27 | 28 | ## 0.1.1 - 2014-07-04 29 | - always add ```ng``` to the initial injector which is used during bootstrapping, see [#15](https://github.com/philippd/angular-deferred-bootstrap/issues/15) 30 | 31 | ## 0.1.0 - 2014-06-10 32 | - the resolve functions can now use dependency injection to access services from the AngularJS core 33 | - BREAKING: the resolve functions can NOT anymore directly get access to the 'injector' (which is also not needed anymore) -> check the updated demos and the docs 34 | - the bootstrap configuration takes a new argument 'injectorModules' where the modules which should be made available to the resolve functions can be specified, thanks [@Shepless](https://github.com/Shepless), see: [#11](https://github.com/philippd/angular-deferred-bootstrap/pull/11) 35 | 36 | ## 0.0.5 - 2014-04-22 37 | - bootstrap() now returns a promise, thanks [@harriha](https://github.com/harriha) 38 | - fixed [#9](https://github.com/philippd/angular-deferred-bootstrap/issues/9): Loading/error CSS classes didn't work if the script was loaded in , thanks [@harriha](https://github.com/harriha) 39 | 40 | ## 0.0.4 - 2014-04-16 41 | - add onError to allow custom error handling, closes [#1](https://github.com/philippd/angular-deferred-bootstrap/issues/1) 42 | 43 | ## 0.0.3 - 2014-04-15 44 | - remove loading class after angular app is bootstrapped, fixes [#7](https://github.com/philippd/angular-deferred-bootstrap/issues/7) 45 | 46 | ## 0.0.2 - 2014-04-15 47 | - add support for IE8 ([#8](https://github.com/philippd/angular-deferred-bootstrap/pull/6)), fixes [#5](https://github.com/philippd/angular-deferred-bootstrap/issues/5) 48 | -------------------------------------------------------------------------------- /demo/constant-to-specific-module.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Deferred Bootstrap Demo 5 | 21 | 22 | 23 | 24 |
25 | Loading... 26 |
27 | 28 |
29 | Could not load configuration! 30 |
31 | 32 |
33 | Check the console for output... 34 |
35 | 36 | 37 | 38 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /demo/demo.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Application Name" 3 | } 4 | -------------------------------------------------------------------------------- /demo/error-handler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Deferred Bootstrap Error Demo 5 | 21 | 22 | 23 | 24 |
25 | Loading... 26 |
27 | 28 |
29 | Could not load configuration! 30 |
31 | 32 | 33 | 34 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /demo/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Deferred Bootstrap Error Demo 5 | 21 | 22 | 23 | 24 |
25 | Loading... 26 |
27 | 28 |
29 | Could not load configuration! 30 |
31 | 32 | 33 | 34 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Deferred Bootstrap Demo 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/multi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Deferred Bootstrap Demo 5 | 21 | 22 | 23 | 24 |
25 | Loading... 26 |
27 | 28 |
29 | Could not load configuration! 30 |
31 | 32 |
33 | Value: {{ value }} 34 |
35 | 36 | 37 | 38 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /demo/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Deferred Bootstrap Demo 5 | 21 | 22 | 23 | 24 |
25 | Loading... 26 |
27 | 28 |
29 | Could not load configuration! 30 |
31 | 32 |
33 | Value: {{ value }} 34 |
35 | 36 | 37 | 38 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | basePath: '', 7 | 8 | frameworks: ['jasmine'], 9 | 10 | files: [ 11 | 'bower_components/angular/angular.js', 12 | 'bower_components/angular-mocks/angular-mocks.js', 13 | 'src/**/*.js', 14 | 'test/**/*.spec.js' 15 | ], 16 | 17 | exclude: [], 18 | 19 | reporters: ['progress', 'coverage', 'coveralls'], 20 | 21 | port: 9876, 22 | 23 | colors: true, 24 | 25 | // LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 26 | logLevel: config.LOG_INFO, 27 | 28 | // Start these browsers, currently available: 29 | // - Chrome 30 | // - ChromeCanary 31 | // - Firefox 32 | // - Opera 33 | // - Safari (only Mac) 34 | // - PhantomJS 35 | // - IE (only Windows) 36 | browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'], 37 | 38 | captureTimeout: 60000, 39 | 40 | singleRun: false, 41 | 42 | preprocessors: { 43 | 'src/**/!(*.spec)+(.js)': ['coverage'] 44 | }, 45 | 46 | coverageReporter: { 47 | type: 'lcov', 48 | dir: 'coverage/' 49 | }, 50 | 51 | plugins: [ 52 | 'karma-jasmine', 53 | 'karma-chrome-launcher', 54 | 'karma-firefox-launcher', 55 | 'karma-phantomjs-launcher', 56 | 'karma-coverage', 57 | 'karma-coveralls' 58 | ] 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-deferred-bootstrap", 3 | "version": "0.1.9", 4 | "description": "Initialize AngularJS apps with constants loaded from the back-end", 5 | "homepage": "https://github.com/philippd/angular-deferred-bootstrap", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/philippd/angular-deferred-bootstrap" 9 | }, 10 | "scripts": { 11 | "postinstall": "./node_modules/bower/bin/bower install", 12 | "test": "./node_modules/karma/bin/karma start karma.conf.js --single-run" 13 | }, 14 | "author": { 15 | "name": "Philipp Denzler" 16 | }, 17 | "main": "angular-deferred-bootstrap.js", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "bower": "~1.3.8", 21 | "karma": "~0.12.19", 22 | "karma-firefox-launcher": "0.1.3", 23 | "karma-phantomjs-launcher": "0.1.4", 24 | "karma-jasmine": "0.1.5", 25 | "karma-coverage": "~0.2.5", 26 | "karma-coveralls": "~0.1.4", 27 | "grunt-karma": "0.8.3", 28 | "grunt": "~0.4.5", 29 | "grunt-contrib-watch": "0.6.1", 30 | "grunt-contrib-concat": "0.5.0", 31 | "grunt-contrib-uglify": "0.5.1", 32 | "grunt-contrib-jshint": "0.10.0", 33 | "grunt-contrib-clean": "0.6.0", 34 | "load-grunt-tasks": "0.6.0", 35 | "express": "~3.4.8", 36 | "grunt-express": "~1.2.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | var app = express(); 4 | var server = require('http').createServer(app), 5 | url = require('url'), 6 | path = require('path'), 7 | fs = require('fs'); 8 | 9 | var getDemoConfig = function (req, res) { 10 | function answer(code, data) { 11 | res.writeHead(code,{ 12 | 'Content-Type':'application/json;charset=utf-8', 13 | 'Access-Control-Allow-Origin':'*', 14 | 'Access-Control-Allow-Headers':'X-Requested-With' 15 | }); 16 | res.end(data); 17 | } 18 | 19 | fs.readFile('./demo/demo.config.json', function(err, data) { 20 | if (err) answer(404, ''); 21 | else answer(200, data); 22 | }); 23 | }; 24 | 25 | app.configure(function () { 26 | app.use(express.logger('dev')); 27 | app.use(express.bodyParser()); 28 | app.use(express.methodOverride()); 29 | app.use(express.errorHandler()); 30 | app.use(express.static(__dirname)); 31 | app.use(app.router); 32 | 33 | app.get('/api/demo-config', getDemoConfig); 34 | app.get('/api/demo-config-2', getDemoConfig); 35 | }); 36 | 37 | module.exports = server; 38 | 39 | // Override: Provide an "use" used by grunt-express. 40 | module.exports.use = function () { 41 | app.use.apply(app, arguments); 42 | }; -------------------------------------------------------------------------------- /src/deferred-bootstrap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isObject = angular.isObject, 4 | isFunction = angular.isFunction, 5 | isArray = angular.isArray, 6 | isString = angular.isString, 7 | forEach = angular.forEach, 8 | loadingClass = 'deferred-bootstrap-loading', 9 | errorClass = 'deferred-bootstrap-error', 10 | bodyElement, 11 | $q; 12 | 13 | function addLoadingClass () { 14 | bodyElement.addClass(loadingClass); 15 | } 16 | 17 | function removeBodyClasses () { 18 | bodyElement.removeClass(loadingClass); 19 | bodyElement.removeClass(errorClass); 20 | } 21 | 22 | function addErrorClass () { 23 | removeBodyClasses(); 24 | bodyElement.addClass(errorClass); 25 | } 26 | 27 | function isPromise (value) { 28 | return isObject(value) && isFunction(value.then); 29 | } 30 | 31 | function checkConfig (config) { 32 | if (!isObject(config)) { 33 | throw new Error('Bootstrap configuration must be an object.'); 34 | } 35 | if (!isString(config.module)) { 36 | throw new Error('\'config.module\' must be a string.'); 37 | } 38 | if (config.resolve && config.moduleResolves) { 39 | throw new Error('Bootstrap configuration can contain either \'resolve\' or \'moduleResolves\' but not both'); 40 | } 41 | if (config.resolve) { 42 | if (!isObject(config.resolve)) { 43 | throw new Error('\'config.resolve\' must be an object.'); 44 | } 45 | } 46 | if (config.bootstrapConfig) { 47 | if (!isObject(config.bootstrapConfig)) { 48 | throw new Error('\'config.bootstrapConfig\' must be an object.'); 49 | } 50 | } 51 | if (config.moduleResolves) { 52 | if (!isArray(config.moduleResolves)) { 53 | throw new Error('\'config.moduleResolves\' must be an array.'); 54 | } 55 | } 56 | 57 | forEach(config.moduleResolves, function (moduleResolve) { 58 | if (!moduleResolve.module) { 59 | throw new Error('A \'moduleResolve\' configuration item must contain a \'module\' name.'); 60 | } 61 | 62 | if (!isObject(moduleResolve.resolve)) { 63 | throw new Error('\'moduleResolve.resolve\' must be an object.'); 64 | } 65 | }); 66 | 67 | if (angular.isDefined(config.onError) && !isFunction(config.onError)) { 68 | throw new Error('\'config.onError\' must be a function.'); 69 | } 70 | } 71 | function provideRootElement (modules, element) { 72 | element = angular.element(element); 73 | modules.unshift(['$provide', function($provide) { 74 | $provide.value('$rootElement', element); 75 | }]); 76 | } 77 | 78 | function createInjector (injectorModules, element) { 79 | var modules = ['ng']; 80 | if (isString(injectorModules)) { 81 | modules.push(injectorModules); 82 | } else if (isArray(injectorModules)) { 83 | modules = modules.concat(injectorModules); 84 | } 85 | provideRootElement(modules, element); 86 | return angular.injector(modules, element); 87 | } 88 | 89 | function doBootstrap (element, module, bootstrapConfig) { 90 | var deferred = $q.defer(); 91 | 92 | angular.element(document).ready(function () { 93 | angular.bootstrap(element, [module], bootstrapConfig); 94 | removeBodyClasses(); 95 | 96 | deferred.resolve(true); 97 | }); 98 | 99 | return deferred.promise; 100 | } 101 | 102 | function bootstrap (configParam) { 103 | var config = configParam || {}, 104 | element = config.element, 105 | module = config.module, 106 | injectorModules = config.injectorModules || [], 107 | injector, 108 | promises = [], 109 | constants = [], 110 | bootstrapConfig = config.bootstrapConfig; 111 | 112 | bodyElement = angular.element(document.body); 113 | 114 | addLoadingClass(); 115 | checkConfig(config); 116 | injector = createInjector(injectorModules, element); 117 | $q = injector.get('$q'); 118 | 119 | function callResolveFn (resolveFunction, constantName, moduleName) { 120 | var result; 121 | 122 | constants.push({ 123 | name: constantName, 124 | moduleName: moduleName || module 125 | }); 126 | 127 | if (!isFunction(resolveFunction) && !isArray(resolveFunction)) { 128 | throw new Error('Resolve for \'' + constantName + '\' is not a valid dependency injection format.'); 129 | } 130 | 131 | result = injector.instantiate(resolveFunction); 132 | 133 | if (isPromise(result)) { 134 | promises.push(result); 135 | } else { 136 | throw new Error('Resolve function for \'' + constantName + '\' must return a promise.'); 137 | } 138 | } 139 | 140 | function handleResults (results) { 141 | forEach(results, function (value, index) { 142 | var result = value && value.data ? value.data : value, 143 | moduleName = constants[index].moduleName, 144 | constantName = constants[index].name; 145 | 146 | angular.module(moduleName).constant(constantName, result); 147 | }); 148 | 149 | return doBootstrap(element, module, bootstrapConfig); 150 | } 151 | 152 | function handleError (error) { 153 | addErrorClass(); 154 | if (isFunction(config.onError)) { 155 | config.onError(error); 156 | } 157 | } 158 | 159 | if (config.moduleResolves) { 160 | forEach(config.moduleResolves, function (moduleResolve, index) { 161 | forEach(moduleResolve.resolve, function (resolveFunction, constantName) { 162 | callResolveFn(resolveFunction, constantName, config.moduleResolves[index].module); 163 | }); 164 | }); 165 | } else { 166 | forEach(config.resolve, function (resolveFunction, constantName) { 167 | callResolveFn(resolveFunction, constantName); 168 | }); 169 | } 170 | 171 | return $q.all(promises).then(handleResults, handleError); 172 | } 173 | 174 | var deferredBootstrapper = { 175 | bootstrap: bootstrap 176 | }; 177 | 178 | if(typeof define === 'function' && define.amd) { 179 | define([], deferredBootstrapper); 180 | } else if(typeof module === 'object' && module.exports) { 181 | module.exports = deferredBootstrapper; 182 | } else { 183 | window.deferredBootstrapper = deferredBootstrapper; 184 | } 185 | -------------------------------------------------------------------------------- /src/deferred-bootstrap.prefix: -------------------------------------------------------------------------------- 1 | (function (window, document) { -------------------------------------------------------------------------------- /src/deferred-bootstrap.suffix: -------------------------------------------------------------------------------- 1 | })(window, document); -------------------------------------------------------------------------------- /test/deferred-bootstrap.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // A jasmine 2.0 -like interface for async tests 4 | function itAsync(title, func) { 5 | it(title, function () { 6 | var finished = false; 7 | 8 | function done() { 9 | finished = true; 10 | } 11 | 12 | func(done); 13 | 14 | waitsFor(function () { 15 | return finished === true; 16 | }); 17 | }); 18 | } 19 | 20 | /* global checkConfig, createInjector, isPromise, loadingClass, errorClass */ 21 | describe('deferredBootstrapper', function () { 22 | 23 | it('should provide bootstrap function', function () { 24 | expect(typeof window.deferredBootstrapper.bootstrap).toBe('function'); 25 | }); 26 | 27 | describe('bootstrap', function () { 28 | 29 | var bootstrap, 30 | bodyElement; 31 | var APP_NAME = 'testApp'; 32 | 33 | beforeEach(function () { 34 | bootstrap = window.deferredBootstrapper.bootstrap; 35 | bodyElement = window.document.body; 36 | }); 37 | 38 | itAsync('should inject $location', function (done) { 39 | bootstrap({ 40 | element: bodyElement, 41 | module: APP_NAME, 42 | resolve: { 43 | LOCATION: function ($http, $q, $location) { 44 | var deferred = $q.defer(); 45 | 46 | deferred.resolve($location); 47 | 48 | return deferred.promise; 49 | } 50 | } 51 | }); 52 | 53 | angular.module(APP_NAME, []) 54 | .config(function (LOCATION) { 55 | expect(LOCATION).toBeDefined(); 56 | 57 | done(); 58 | }); 59 | }); 60 | 61 | itAsync('should resolve with the value returned from the defined constant', function (done) { 62 | bootstrap({ 63 | element: bodyElement, 64 | module: APP_NAME, 65 | resolve: { 66 | CONFIG: function ($http, $q) { 67 | var deferred = $q.defer(); 68 | 69 | deferred.resolve('foo'); 70 | 71 | return deferred.promise; 72 | } 73 | } 74 | }); 75 | 76 | angular.module(APP_NAME, []) 77 | .config(function (CONFIG) { 78 | expect(CONFIG).toBe('foo'); 79 | 80 | done(); 81 | }); 82 | }); 83 | 84 | itAsync('should return a Promise which resolves with `true` in success case', function (done) { 85 | var promise = bootstrap({ 86 | element: bodyElement, 87 | module: APP_NAME, 88 | resolve: { 89 | CONFIG: function ($http, $q) { 90 | var deferred = $q.defer(); 91 | 92 | deferred.resolve('foo'); 93 | 94 | return deferred.promise; 95 | } 96 | } 97 | }); 98 | 99 | expect(isPromise(promise)).toBe(true); 100 | 101 | promise.then(function (result) { 102 | expect(result).toBe(true); 103 | 104 | done(); 105 | }); 106 | }); 107 | 108 | itAsync('should allow constants to be added to a specified module', function (done) { 109 | var appModule ='app.module', 110 | constantsModuleOne = 'app.constants.one', 111 | constantsModuleTwo = 'app.constants.two'; 112 | 113 | angular.module(appModule, ['ng']) 114 | .config(function ($injector) { 115 | 116 | expect($injector.has('CONSTANTS_ONE_CONFIG')).toBe(false); 117 | expect($injector.has('CONSTANTS_ONE_MORE_CONFIG')).toBe(false); 118 | expect($injector.has('CONSTANTS_TWO_CONFIG')).toBe(false); 119 | 120 | done(); 121 | }); 122 | 123 | angular.module(constantsModuleOne, []); 124 | angular.module(constantsModuleTwo, []); 125 | 126 | bootstrap({ 127 | element: bodyElement, 128 | module: appModule, 129 | moduleResolves: [ 130 | { 131 | module: constantsModuleOne, 132 | resolve: { 133 | CONSTANTS_ONE_CONFIG: function ($q) { 134 | var deferred = $q.defer(); 135 | 136 | deferred.resolve('foo'); 137 | 138 | return deferred.promise; 139 | }, 140 | CONSTANTS_ONE_MORE_CONFIG: function ($q) { 141 | var deferred = $q.defer(); 142 | 143 | deferred.resolve('foo'); 144 | 145 | return deferred.promise; 146 | } 147 | } 148 | }, 149 | { 150 | module: constantsModuleTwo, 151 | resolve: { 152 | CONSTANTS_TWO_CONFIG: function ($q) { 153 | var deferred = $q.defer(); 154 | 155 | deferred.resolve('foo'); 156 | 157 | return deferred.promise; 158 | } 159 | } 160 | } 161 | ] 162 | }).then(function () { 163 | var constantsOneInjector = angular.injector([constantsModuleOne]), 164 | constantsTwoInjector = angular.injector([constantsModuleTwo]); 165 | 166 | expect(constantsOneInjector.has('CONSTANTS_ONE_CONFIG')).toBe(true); 167 | expect(constantsOneInjector.has('CONSTANTS_ONE_MORE_CONFIG')).toBe(true); 168 | expect(constantsOneInjector.has('CONSTANTS_TWO_CONFIG')).toBe(false); 169 | 170 | expect(constantsTwoInjector.has('CONSTANTS_ONE_CONFIG')).toBe(false); 171 | expect(constantsTwoInjector.has('CONSTANTS_ONE_MORE_CONFIG')).toBe(false); 172 | expect(constantsTwoInjector.has('CONSTANTS_TWO_CONFIG')).toBe(true); 173 | done(); 174 | }); 175 | }); 176 | 177 | itAsync('should allow custom injector module(s) to be used to create the injector', function (done) { 178 | var customModuleName = 'custom.module'; 179 | angular.module(customModuleName, ['ng']) 180 | .service('CustomService', ['$q', function ($q) { 181 | this.get = function () { 182 | var deferred = $q.defer(); 183 | 184 | deferred.resolve('foo'); 185 | 186 | return deferred.promise; 187 | }; 188 | }]); 189 | 190 | var promise = bootstrap({ 191 | element: bodyElement, 192 | module: APP_NAME, 193 | injectorModules: customModuleName, 194 | resolve: { 195 | CONFIG: ['CustomService', function (CustomService) { 196 | return CustomService.get(); 197 | }] 198 | } 199 | }); 200 | 201 | expect(isPromise(promise)).toBe(true); 202 | 203 | promise.then(function (result) { 204 | expect(result).toBe(true); 205 | 206 | done(); 207 | }); 208 | }); 209 | 210 | itAsync('should allow to inject services from ng module even if custom modules dont list it as dependency', function (done) { 211 | var customModuleName = 'custom.module'; 212 | angular.module(customModuleName, []) 213 | .service('CustomService', ['$q', function ($q) { 214 | this.get = function () { 215 | var deferred = $q.defer(); 216 | 217 | deferred.resolve('foo'); 218 | 219 | return deferred.promise; 220 | }; 221 | }]); 222 | 223 | var promise = bootstrap({ 224 | element: bodyElement, 225 | module: APP_NAME, 226 | injectorModules: customModuleName, 227 | resolve: { 228 | CONFIG: ['CustomService', '$timeout', function (CustomService, $timeout) { 229 | return $timeout(CustomService.get, 1); 230 | }] 231 | } 232 | }); 233 | 234 | expect(isPromise(promise)).toBe(true); 235 | 236 | promise.then(function (result) { 237 | expect(result).toBe(true); 238 | 239 | done(); 240 | }); 241 | }); 242 | 243 | itAsync('should use the default ngInjector if "ng" is specified as the injectorModules config option', function (done) { 244 | var promise = bootstrap({ 245 | element: bodyElement, 246 | module: APP_NAME, 247 | injectorModules: ['ng'], 248 | resolve: { 249 | CONFIG: function ($http, $q) { 250 | var deferred = $q.defer(); 251 | 252 | deferred.resolve('foo'); 253 | 254 | return deferred.promise; 255 | } 256 | } 257 | }); 258 | 259 | expect(isPromise(promise)).toBe(true); 260 | 261 | promise.then(function (result) { 262 | expect(result).toBe(true); 263 | 264 | done(); 265 | }); 266 | }); 267 | 268 | describe('CSS class handling', function () { 269 | 270 | itAsync('should add loading class immediately and remove it when resolved', function (done) { 271 | var promise = bootstrap({ 272 | element: window.document.body, 273 | module: APP_NAME, 274 | resolve: { 275 | CONFIG: function ($http, $q) { 276 | expect(bodyElement.classList.contains(loadingClass)).toBe(true); 277 | 278 | var deferred = $q.defer(); 279 | 280 | deferred.resolve('foo'); 281 | 282 | return deferred.promise; 283 | } 284 | } 285 | }); 286 | 287 | angular.module(APP_NAME, []) 288 | .config(function () { 289 | // Yep, it's still there at this point 290 | expect(bodyElement.classList.contains(loadingClass)).toBe(true); 291 | }); 292 | 293 | promise.then(function () { 294 | expect(bodyElement.classList.contains(loadingClass)).toBe(false); 295 | 296 | done(); 297 | }); 298 | }); 299 | 300 | itAsync('should add error class in case Promise resolves to an error', function (done) { 301 | bootstrap({ 302 | element: window.document.body, 303 | module: APP_NAME, 304 | resolve: { 305 | CONFIG: function ($http, $q) { 306 | expect(bodyElement.classList.contains(errorClass)).toBe(false); 307 | 308 | var deferred = $q.defer(); 309 | 310 | deferred.reject(new Error('bar')); 311 | 312 | return deferred.promise; 313 | } 314 | }, 315 | onError: function (/* err */) { 316 | expect(bodyElement.classList.contains(errorClass)).toBe(true); 317 | 318 | done(); 319 | } 320 | }); 321 | }); 322 | 323 | }); 324 | 325 | }); 326 | 327 | describe('checkConfig()', function () { 328 | 329 | it('should accept valid deferred bootstrap config', function () { 330 | var config = { 331 | element: {}, 332 | module: 'myModule', 333 | resolve: { 334 | CONST: function () { 335 | } 336 | } 337 | }; 338 | checkConfig(config); 339 | }); 340 | 341 | it('should throw if config is not an object', function () { 342 | var config = 12; 343 | expect(function () { 344 | checkConfig(config); 345 | }).toThrow('Bootstrap configuration must be an object.'); 346 | }); 347 | 348 | it('should throw if module is not a string', function () { 349 | var config = { 350 | element: {}, 351 | module: [], 352 | resolve: { 353 | CONST: function () { 354 | } 355 | } 356 | }; 357 | expect(function () { 358 | checkConfig(config); 359 | }).toThrow('\'config.module\' must be a string.'); 360 | }); 361 | 362 | it('should throw if both resolve and moduleResolves are defined', function () { 363 | var config = { 364 | element: {}, 365 | module: 'myModule', 366 | resolve: { 367 | CONST: function () { 368 | } 369 | }, 370 | moduleResolves: [] 371 | }; 372 | expect(function () { 373 | checkConfig(config); 374 | }).toThrow('Bootstrap configuration can contain either \'resolve\' or \'moduleResolves\' but not both'); 375 | }); 376 | 377 | it('should throw if resolve is not an object', function () { 378 | var config = { 379 | element: {}, 380 | module: 'myModule', 381 | resolve: 123 382 | }; 383 | expect(function () { 384 | checkConfig(config); 385 | }).toThrow('\'config.resolve\' must be an object.'); 386 | }); 387 | 388 | it('should throw if bootstrapConfig is not an object', function () { 389 | var config = { 390 | element: {}, 391 | module: 'myModule', 392 | resolve: { 393 | CONST: function () { 394 | } 395 | }, 396 | bootstrapConfig: 123 397 | }; 398 | expect(function () { 399 | checkConfig(config); 400 | }).toThrow('\'config.bootstrapConfig\' must be an object.'); 401 | }); 402 | 403 | it('should accept valid deferred bootstrap config with bootstrapConfig option', function () { 404 | var config = { 405 | element: {}, 406 | module: 'myModule', 407 | resolve: { 408 | CONST: function () { 409 | } 410 | }, 411 | bootstrapConfig: { 412 | strictDi: true 413 | } 414 | }; 415 | checkConfig(config); 416 | }); 417 | 418 | it('should throw if moduleResolves is not an array', function () { 419 | var config = { 420 | element: {}, 421 | module: 'myModule', 422 | moduleResolves: 1234 423 | }; 424 | expect(function () { 425 | checkConfig(config); 426 | }).toThrow('\'config.moduleResolves\' must be an array.'); 427 | }); 428 | 429 | it('should throw if a moduleResolve does not contain a module name', function () { 430 | var config = { 431 | element: {}, 432 | module: 'myModule', 433 | moduleResolves: [{ 434 | module: 'A.Test.Module', 435 | resolve: {} 436 | }, { 437 | resolve: {} 438 | }] 439 | }; 440 | expect(function () { 441 | checkConfig(config); 442 | }).toThrow('A \'moduleResolve\' configuration item must contain a \'module\' name.'); 443 | }); 444 | 445 | it('should throw if a moduleResolve does not contain a resolve block', function () { 446 | var config = { 447 | element: {}, 448 | module: 'myModule', 449 | moduleResolves: [{ 450 | module: 'A.Test.Module', 451 | resolve: {} 452 | }, { 453 | module: 'A.Test.Module' 454 | }] 455 | }; 456 | expect(function () { 457 | checkConfig(config); 458 | }).toThrow('\'moduleResolve.resolve\' must be an object.'); 459 | }); 460 | 461 | it('should throw if onError is defined but not a function', function () { 462 | var config = { 463 | element: {}, 464 | module: 'myModule', 465 | resolve: { 466 | CONST: function () { 467 | } 468 | }, 469 | onError: 'bla' 470 | }; 471 | expect(function () { 472 | checkConfig(config); 473 | }).toThrow('\'config.onError\' must be a function.'); 474 | }); 475 | 476 | }); 477 | 478 | describe('createInjector()', function () { 479 | var element; 480 | 481 | beforeEach(function () { 482 | angular.module('module1', []); 483 | angular.module('module2', []); 484 | element = angular.element('
'); 485 | }); 486 | 487 | it('should create injector with given module as string', function () { 488 | // given 489 | var modules = 'module1'; 490 | spyOn(angular, 'injector').andCallThrough(); 491 | 492 | // when 493 | createInjector(modules, element); 494 | var injectorModules = angular.injector.mostRecentCall.args[0]; 495 | 496 | // then 497 | expect(injectorModules.indexOf('ng')).not.toBe(-1); 498 | expect(injectorModules.indexOf('module1')).not.toBe(-1); 499 | }); 500 | 501 | it('should create injector with given modules as array', function () { 502 | // given 503 | var modules = ['module1', 'module2']; 504 | spyOn(angular, 'injector').andCallThrough(); 505 | 506 | // when 507 | createInjector(modules, element); 508 | var injectorModules = angular.injector.mostRecentCall.args[0]; 509 | 510 | // then 511 | expect(injectorModules.indexOf('ng')).not.toBe(-1); 512 | expect(injectorModules.indexOf('module1')).not.toBe(-1); 513 | expect(injectorModules.indexOf('module2')).not.toBe(-1); 514 | }); 515 | 516 | it('should create injector with given element', function () { 517 | // given 518 | var modules = 'module1'; 519 | spyOn(angular, 'injector').andCallThrough(); 520 | 521 | // when 522 | createInjector(modules, element); 523 | 524 | // then 525 | expect(angular.injector).toHaveBeenCalledWith(jasmine.any(Object), element); 526 | }); 527 | }); 528 | 529 | describe('isPromise()', function () { 530 | 531 | it('should check if object is a promise', function () { 532 | var promise = { 533 | then: function () { 534 | } 535 | }; 536 | expect(isPromise(promise)).toBe(true); 537 | }); 538 | 539 | it('should detect if object is not a promise', function () { 540 | expect(isPromise({})).toBe(false); 541 | expect(isPromise(undefined)).toBe(false); 542 | expect(isPromise(null)).toBe(false); 543 | }); 544 | }); 545 | 546 | }); 547 | --------------------------------------------------------------------------------