Skip to content
This repository was archived by the owner on Nov 21, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,41 @@ deferredBootstrapper.bootstrap({
});
```

## Attach constants to specific modules
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```:

```js
window.deferredBootstrapper.bootstrap({
element: document.body,
module: 'myApp',
moduleResolves: [
{
module: 'myApp.settings',
resolve: {
CONSTANT_ONE: ['$http', function ($http) {
return $http.get();
}],
CONSTANT_TWO: ['$http', function ($http) {
return $http.get();
}]
}
},
{
module: 'myApp.moreSettings',
resolve: {
CONSTANT_THREE: ['$http', function ($http) {
return $http.get();
}]
}
}
]
})
```

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```

**Note** that only ```resolve``` or ```moduleResolves``` can be used - using both in the same configuration will throw an exception

## Custom injector modules
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:

Expand Down
75 changes: 75 additions & 0 deletions demo/constant-to-specific-module.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<title>Deferred Bootstrap Demo</title>
<style type="text/css">
#loading {
display: none;
}
.deferred-bootstrap-loading #loading {
display: block !important;
}

#error {
display: none;
}
.deferred-bootstrap-error #error {
display: block !important;
background: red;
}
</style>
</head>
<body>

<div id="loading">
Loading...
</div>

<div id="error">
Could not load configuration!
</div>

<div>
Check the console for output...
</div>

<script src="/bower_components/angular/angular.js"></script>
<script src="/src/deferred-bootstrap.js"></script>
<script>

window.deferredBootstrapper.bootstrap({
element: window.document.body,
module: 'demoApp',
moduleResolves: [{
module: 'demoApp.settings',
resolve: {
STARTUP_CONFIG: ['$http', function ($http) {
return $http.get('/api/demo-config');
}]
}
}]
}).then(function () {
angular.injector(['demoApp.settings']);
});

angular.module('demoApp.settings', [])
.config(function (STARTUP_CONFIG) {
console.log('"demoApp.settings" in config() - STARTUP_CONFIG: ' + JSON.stringify(STARTUP_CONFIG));
})
.run(function (STARTUP_CONFIG) {
console.log('"demoApp.settings" in run() - STARTUP_CONFIG: ' + JSON.stringify(STARTUP_CONFIG));
});

angular.module('demoApp', [])
.config(function ($injector) {
var hasStartupConfig = $injector.has('STARTUP_CONFIG');
console.log('"demoApp" in config() - "$injector.has("STARTUP_CONFIG")": ' + hasStartupConfig);
})
.run(function ($injector) {
var hasStartupConfig = $injector.has('STARTUP_CONFIG');
console.log('"demoApp" in run() - "$injector.has("STARTUP_CONFIG")": ' + hasStartupConfig);
});

</script>
</body>
</html>
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<ul>
<li><a href="simple.html">simple demo</a></li>
<li><a href="multi.html">multi resolve demo</a></li>
<li><a href="constant-to-specific-module.html">constant to specific modules</a></li>
<li><a href="error.html">error in resolve demo</a></li>
<li><a href="error-handler.html">error handler</a></li>
</ul>
Expand Down
5 changes: 4 additions & 1 deletion server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var express = require('express');
var app = express();
var server = require('http').createServer(app),
url = require('url'),
path = require('path'),
fs = require('fs');

var getDemoConfig = function (req, res) {
Expand All @@ -26,9 +27,11 @@ app.configure(function () {
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.errorHandler());
app.use(express.static(__dirname));
app.use(express.static(path.join(__dirname, 'demo')));
app.use(app.router);

console.log(path.join(__dirname, 'demo'));

app.get('/api/demo-config', getDemoConfig);
app.get('/api/demo-config-2', getDemoConfig);
});
Expand Down
51 changes: 44 additions & 7 deletions src/deferred-bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,30 @@ function checkConfig (config) {
if (!isString(config.module)) {
throw new Error('\'config.module\' must be a string.');
}
if (!isObject(config.resolve)) {
throw new Error('\'config.resolve\' must be an object.');
if (config.resolve && config.moduleResolves) {
throw new Error('Bootstrap configuration can contain either \'resolve\' or \'moduleResolves\' but not both');
}
if (config.resolve) {
if (!isObject(config.resolve)) {
throw new Error('\'config.resolve\' must be an object.');
}
}
if (config.moduleResolves) {
if (!isArray(config.moduleResolves)) {
throw new Error('\'config.moduleResolves\' must be an array.');
}
}

forEach(config.moduleResolves, function (moduleResolve) {
if (!moduleResolve.module) {
throw new Error('A \'moduleResolve\' configuration item must contain a \'module\' name.');
}

if (!isObject(moduleResolve.resolve)) {
throw new Error('\'moduleResolve.resolve\' must be an object.');
}
});

if (angular.isDefined(config.onError) && !isFunction(config.onError)) {
throw new Error('\'config.onError\' must be a function.');
}
Expand Down Expand Up @@ -74,18 +95,21 @@ function bootstrap (configParam) {
injectorModules = config.injectorModules || [],
injector,
promises = [],
constantNames = [];
constants = [];

bodyElement = angular.element(document.body);

addLoadingClass();
checkConfig(config);
injector = createInjector(injectorModules);

function callResolveFn (resolveFunction, constantName) {
function callResolveFn (resolveFunction, constantName, moduleName) {
var result;

constantNames.push(constantName);
constants.push({
name: constantName,
moduleName: moduleName || module
});

if (!isFunction(resolveFunction) && !isArray(resolveFunction)) {
throw new Error('Resolve for \'' + constantName + '\' is not a valid dependency injection format.');
Expand All @@ -102,8 +126,11 @@ function bootstrap (configParam) {

function handleResults (results) {
forEach(results, function (value, index) {
var result = value && value.data ? value.data : value;
angular.module(module).constant(constantNames[index], result);
var result = value && value.data ? value.data : value,
moduleName = constants[index].moduleName,
constantName = constants[index].name;

angular.module(moduleName).constant(constantName, result);
});

return doBootstrap(element, module);
Expand All @@ -118,6 +145,16 @@ function bootstrap (configParam) {

forEach(config.resolve, callResolveFn);

if (config.moduleResolves) {
forEach(config.moduleResolves, function (moduleResolve, index) {
forEach(moduleResolve.resolve, function (resolveFunction, constantName) {
callResolveFn(resolveFunction, constantName, config.moduleResolves[index].module);
});
});
} else {
forEach(config.resolve, callResolveFn);
}

return $q.all(promises).then(handleResults, handleError);
}

Expand Down
127 changes: 127 additions & 0 deletions test/deferred-bootstrap.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,75 @@ describe('deferredBootstrapper', function () {
});
});

itAsync('should allow constants to be added to a specified module', function (done) {
var appModule ='app.module',
constantsModuleOne = 'app.constants.one',
constantsModuleTwo = 'app.constants.two';

angular.module(appModule, ['ng'])
.config(function ($injector) {

expect($injector.has('CONSTANTS_ONE_CONFIG')).toBe(false);
expect($injector.has('CONSTANTS_ONE_MORE_CONFIG')).toBe(false);
expect($injector.has('CONSTANTS_TWO_CONFIG')).toBe(false);

done();
});

angular.module(constantsModuleOne, []);
angular.module(constantsModuleTwo, []);

bootstrap({
element: bodyElement,
module: appModule,
moduleResolves: [
{
module: constantsModuleOne,
resolve: {
CONSTANTS_ONE_CONFIG: function ($q) {
var deferred = $q.defer();

deferred.resolve('foo');

return deferred.promise;
},
CONSTANTS_ONE_MORE_CONFIG: function ($q) {
var deferred = $q.defer();

deferred.resolve('foo');

return deferred.promise;
}
}
},
{
module: constantsModuleTwo,
resolve: {
CONSTANTS_TWO_CONFIG: function ($q) {
var deferred = $q.defer();

deferred.resolve('foo');

return deferred.promise;
}
}
}
]
}).then(function () {
var constantsOneInjector = angular.injector([constantsModuleOne]),
constantsTwoInjector = angular.injector([constantsModuleTwo]);

expect(constantsOneInjector.has('CONSTANTS_ONE_CONFIG')).toBe(true);
expect(constantsOneInjector.has('CONSTANTS_ONE_MORE_CONFIG')).toBe(true);
expect(constantsOneInjector.has('CONSTANTS_TWO_CONFIG')).toBe(false);

expect(constantsTwoInjector.has('CONSTANTS_ONE_CONFIG')).toBe(false);
expect(constantsTwoInjector.has('CONSTANTS_ONE_MORE_CONFIG')).toBe(false);
expect(constantsTwoInjector.has('CONSTANTS_TWO_CONFIG')).toBe(true);
done();
});
});

itAsync('should allow custom injector module(s) to be used to create the injector', function (done) {
var customModuleName = 'custom.module';
angular.module(customModuleName, ['ng'])
Expand Down Expand Up @@ -267,6 +336,21 @@ describe('deferredBootstrapper', function () {
}).toThrow('\'config.module\' must be a string.');
});

it('should throw if both resolve and moduleResolves are defined', function () {
var config = {
element: {},
module: 'myModule',
resolve: {
CONST: function () {
}
},
moduleResolves: []
};
expect(function () {
checkConfig(config);
}).toThrow('Bootstrap configuration can contain either \'resolve\' or \'moduleResolves\' but not both');
});

it('should throw if resolve is not an object', function () {
var config = {
element: {},
Expand All @@ -278,6 +362,49 @@ describe('deferredBootstrapper', function () {
}).toThrow('\'config.resolve\' must be an object.');
});

it('should throw if moduleResolves is not an array', function () {
var config = {
element: {},
module: 'myModule',
moduleResolves: 1234
};
expect(function () {
checkConfig(config);
}).toThrow('\'config.moduleResolves\' must be an array.');
});

it('should throw if a moduleResolve does not contain a module name', function () {
var config = {
element: {},
module: 'myModule',
moduleResolves: [{
module: 'A.Test.Module',
resolve: {}
}, {
resolve: {}
}]
};
expect(function () {
checkConfig(config);
}).toThrow('A \'moduleResolve\' configuration item must contain a \'module\' name.');
});

it('should throw if a moduleResolve does not contain a resolve block', function () {
var config = {
element: {},
module: 'myModule',
moduleResolves: [{
module: 'A.Test.Module',
resolve: {}
}, {
module: 'A.Test.Module'
}]
};
expect(function () {
checkConfig(config);
}).toThrow('\'moduleResolve.resolve\' must be an object.');
});

it('should throw if onError is defined but not a function', function () {
var config = {
element: {},
Expand Down