Skip to content
This repository was archived by the owner on Nov 21, 2025. It is now read-only.

Conversation

@Shepless
Copy link
Contributor

I have made a few alterations that allow the config to take an optional injectorModules member. This will allow users to decide which module the deferred bootstrap should use and allows the injection of custom services.

I personally have a use case where I want to use one of my existing services to get configuration at bootstrap time, however this service is also used in other areas of the app, saving me from duplicating code. An example is below:

deferredBootstrapper.bootstrap({
        element: document.body,
        module: 'myApp',
        injectorModules: 'myApp.settings',
        resolve: {
            SETTINGS: ['SettingsService', function (SettingsService) {
                return SettingsService.get('/settings');
            }]
        }
    });

The injectorModules option can also take an array of modules so if you have multiple services spread across different modules you can also inject them:

deferredBootstrapper.bootstrap({
        element: document.body,
        module: 'myApp',
        injectorModules: ['myApp.settings', 'myApp.foo']
        resolve: {
            SETTINGS: ['SettingsService', function (SettingsService) {
                return SettingsService.get('/settings');
            }],
            FOO: ['FooService', function (FooService) {
                return FooService.get('/foo');
            }]
        }
    });

As I mentioned previously this is a completely optional config member and if not included does not impact existing functionality.

You may also notice that this change uses the injector to instantiate the resolveFunction. This gives the added benefit of being able to specify dependency injection in the various ways angular itself allows, for example:

resolve: function($http) {}
resolve: ['$http', function($http) {}]

Which in turn allows this bootstrap code to also be minified, something that is not currently available.

I have included two new units tests to cover this new functionality. I hope it meets the requirements.

Cheers.

@Shepless Shepless changed the title Added the ability to specific a custom module to create the injector Added the ability to specify a custom module to create the injector May 19, 2014
@philippd
Copy link
Owner

Hey thanks a lot! This seems like a very cool feature. I will merge this soon and release a new version in the next days.

philippd added a commit that referenced this pull request Jun 9, 2014
Added the ability to specify a custom module to create the injector
@philippd philippd merged commit 4c92057 into philippd:master Jun 9, 2014
@philippd
Copy link
Owner

philippd commented Jun 9, 2014

Thanks again, i like this feature very much and can also use this in my current project! :) I'll update the docs and publish a new release. Sorry for the delay...

@philippd
Copy link
Owner

philippd commented Jun 9, 2014

now available via bower in version 0.1.0: https://github.com/philippd/bower-angular-deferred-bootstrap/releases/tag/v.0.1.0

@Shepless
Copy link
Contributor Author

Awesome! Thanks for accepting, I'll be updating my project to use the new bower version and not my fork :)

@marksyzm
Copy link

marksyzm commented Jul 3, 2014

First of all @Shepless, thanks very much for putting the time into this feature - it really did seem like a necessity to have the injector format built into the resolve items.
However, I seem to be getting an error that is stopping me from including services - any idea what could be causing this?

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>

    <script src="components/angular/angular.js"></script>
    <script src="components/angular-deferred-bootstrap/angular-deferred-bootstrap.js"></script>
    <script>
        var app = angular.module('app', ['app.services']);
        angular.module('app.services', []);


        app.factory('TestService', function () {
            return {
                log: function (message) {
                    console.log(message);
                }
            }
        });

        deferredBootstrapper.bootstrap({
            element: document,
            module: 'app',
            injectorModules: 'app.services',
            resolve: {
                LOAD_CONFIG: [
                    'TestService',
                    '$timeout',
                    function (TestService, $timeout) {
                        return $timeout(function () {
                            TestService.log("this happens")
                        }, 1000)
                    }
                ]
            }
        })
    </script>
</body>
</html>

The error is:

Uncaught Error: [$injector:unpr] Unknown provider: TestServiceProvider <- TestService
http://errors.angularjs.org/1.2.19/$injector/unpr?p0=TestServiceProvider%20%3C-%20TestService angular.js:78
VALIDITY_STATE_PROPERTY angular.js:78
(anonymous function) angular.js:3753
getService angular.js:3881
(anonymous function) angular.js:3758
getService angular.js:3881
invoke angular.js:3908
instantiate angular.js:3928
callResolveFn angular-deferred-bootstrap.js:98
forEach angular.js:332
bootstrap angular-deferred-bootstrap.js:123
(anonymous function)

The version of angular I am using is 1.2.19 and the version of this script is the current (0.1.0)

@philippd
Copy link
Owner

philippd commented Jul 3, 2014

Hi @marksyzm
You registered the TestService in the 'app' module instead of 'app.services'. Try

var app = angular.module('app', ['app.services']),
  servicesModule = angular.module('app.services', []);

  servicesModule .factory('TestService', function () { ... }

@marksyzm
Copy link

marksyzm commented Jul 3, 2014

Ah, simple mistake... okay, it wasn't actually that error now that I look at it. With this:

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>

    <script src="components/angular/angular.js"></script>
    <script src="components/angular-deferred-bootstrap/angular-deferred-bootstrap.js"></script>
    <script>
        angular.module('app', ['app.services']);
        var app = angular.module('app.services', []);


        app.provider('TestService', function () {
            return {
                log: function (message) {
                    console.log(message);
                }
            }
        });

        deferredBootstrapper.bootstrap({
            element: document,
            module: 'app',
            injectorModules: 'app.services',
            resolve: {
                LOAD_CONFIG: [
                    'TestService',
                    '$timeout',
                    function (TestService, $timeout) {
                        return $timeout(function () {
                            TestService.log("this happens")
                        }, 1000)
                    }
                ]
            }
        })
    </script>
</body>
</html>

I get this:

Uncaught object angular.js:78
VALIDITY_STATE_PROPERTY angular.js:78
(anonymous function) angular.js:3857
forEach angular.js:325
loadModules angular.js:3823
createInjector angular.js:3763
createInjector angular-deferred-bootstrap.js:57
callResolveFn angular-deferred-bootstrap.js:97
forEach angular.js:332
bootstrap angular-deferred-bootstrap.js:123
(anonymous function) test.html:23

Which is actually the dreaded "missing ngLocale" error you get when a module can't be found and returns an uncaught object error in minError:

"[$injector:nomod] Module 'ngLocale' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.2.19/$injector/nomod?p0=ngLocale"

Any ideas? I've been trying to solve this with no joy.

@philippd
Copy link
Owner

philippd commented Jul 3, 2014

I created a working plunker with your example here: http://plnkr.co/edit/CrMY8uGu7eKOIW6ApVNz?p=preview

The problem was that you injected $timeout but did not specify the angular core module ng as injector module.
We should probably add this as a default module if injectorModules is specified. I'll create an issue to fix this.

@marksyzm
Copy link

marksyzm commented Jul 3, 2014

I've got it... I made an error just then again and should have put service instead. It seems that you need to provide ng as a module as well if you supply the injectorModules property. This now seems to work as not providing ng tells it that it wasn't provided as an injector modules

Edit: Ha! I just worked it out when you did! Yeah, I would specify that it is either required or a default module.

@Shepless
Copy link
Contributor Author

Shepless commented Jul 3, 2014

Hi @marksyzm

The error generated is because your provide does not implement a $get function:

app.provider('TestService', function () {
        this.$get = function () {
            return {
                log: function (message) {
                    console.log(message);
                }
            }
        };
    });

Once this is fixed it will fall over because you aren't explicitly requiring the ng module as a dependency for your app.services module:

var app = angular.module('app.services', ['ng']);

Full working source:

    angular.module('app', ['app.services']);
    var app = angular.module('app.services', ['ng']);

    app.provider('TestService', function () {
        this.$get = function () {
            return {
                log: function (message) {
                    console.log(message);
                }
            }
        };
    });

    deferredBootstrapper.bootstrap({
        element: document,
        module: 'app',
        injectorModules: 'app.services',
        resolve: {
            LOAD_CONFIG: [
                'TestService',
                '$timeout',
                function (TestService, $timeout) {
                    return $timeout(function () {
                        TestService.log("this happens")
                    }, 1000)
                }
            ]
        }
    });

I'm 50/50 on whether we should make the deferred bootstrap inject ng is if it's not explicitly included. Mainly because I think users module definitions should be handling this and we should only inject what the user requests.

@marksyzm
Copy link

marksyzm commented Jul 3, 2014

In which case I would specify that information about the injectorModules property then

@Shepless
Copy link
Contributor Author

Shepless commented Jul 3, 2014

Yeah definitely - what are your thoughts @philippd? Do you think we should auto inject ng?

@marksyzm
Copy link

marksyzm commented Jul 3, 2014

Ignore that provider user error... I was just trying out other stuff and accidentally left it in

@philippd
Copy link
Owner

philippd commented Jul 3, 2014

@Shepless yes, I'd suggest to do this. I usually do not list ng as a dependency in my apps... (but probably I should?)

I think it's too easy to make a mistake like this and don't see a problem with always adding the module. Is there a good reason not to do it?

@Shepless
Copy link
Contributor Author

Shepless commented Jul 3, 2014

My personal view on this is that modules are supposed to be small, re-usable and isolated areas of code and should be self sufficient e.g. if I ran angular.inject() on them then they would always instantiate correctly. Therefore any dependencies a module has should be handled explicitly by the users code to allow a module to run in isolation.

I'd prefer to have someway of checking if ng is loaded in the checkConfig() function and make the user aware that way. What do you think?

@marksyzm
Copy link

marksyzm commented Jul 3, 2014

Maybe throw an error to suggest including the module rather than auto including it?

@Shepless
Copy link
Contributor Author

Shepless commented Jul 3, 2014

@marksyzm Yeah thats the approach I'm thinking, however it might not actually be an error. The user might not want to include ng in their module dependencies. Consider:

angular.module('myMod', [])
    .factory('myService', [function () {
        return {
           isValid: function (foo) {
            return (!!foo);
            }
        }
    }])

Theres no need for any ng dependency here.

@marksyzm
Copy link

marksyzm commented Jul 3, 2014

I was just thinking if the service they were injecting was from ng but wasn't included then throw that, but then I guess there's no reliable way of detecting that...

@Shepless
Copy link
Contributor Author

Shepless commented Jul 3, 2014

It's a tricky one! We don't want the module to be too restrictive but we also want it to be transparent as to why the injector fails!

@marksyzm
Copy link

marksyzm commented Jul 3, 2014

Another thing I spotted is that is that, unlike with native angular, you can't just put the deferred bootstrapper in and expect it to assume custom modules will be loaded before bootstrapping starts. This is obviously solvable with the standard use of checking for the document ready event. Either that or check the order.

@marksyzm
Copy link

marksyzm commented Jul 3, 2014

Another "gotcha" probably worth mentioning is that the service you instantiate is initialised separately to when it is used in the app, so any changes you make to it in the deferring section will be a separate object to the app.

@philippd
Copy link
Owner

philippd commented Jul 3, 2014

After having a look at the angular.js doBootstrap()function I think we should auto-inject ng to avoid the issue @marksyzm had. I don't see a problem with that as angular does the same in their bootstrapping function.

@philippd
Copy link
Owner

philippd commented Jul 3, 2014

Another "gotcha" probably worth mentioning is that the service you instantiate is initialised separately to when it is used in the app, so any changes you make to it in the deferring section will be a separate object to the app.

Yes, this is how it works because a new injector will be created as soon as all promises in the resolve object are resolved. I'll update the docs and add a note for that.

@philippd philippd mentioned this pull request Jul 3, 2014
@philippd
Copy link
Owner

philippd commented Jul 3, 2014

@Shepless please have a look at fbbb83d, if you agree with this i'll release 0.1.1 with this improvement.

@Shepless
Copy link
Contributor Author

Shepless commented Jul 4, 2014

@philippd I've added comments to #15

@marksyzm
Copy link

marksyzm commented Jul 4, 2014

@philippd To get around that issue with new injectors and whatnot, I just passed the services with their values into an object and updated the newly instantiated singletons in run.

@philippd
Copy link
Owner

philippd commented Jul 4, 2014

@marksyzm the ng module is now automatically added to the initial injector in v0.1.1

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants