$provide outside config blocks
Asked Answered
S

2

3

I'm certainly missing some fundamental point about the injector, but I fail to understand why exactly this

angular.module('app').config(function ($provide) {
    ...
});

and this

angular.module('app').config(function ($injector) {
    $injector.invoke(function ($provide) { ... });
});

work as intended, while this

app.run(function($provide) {
    ...
});

will throw

Error: [$injector:unpr] Unknown provider: $provideProvider <- $provide

As follows from the above, config has some special relationship with providers, while run deals with instances, yet I'm unsure about the thing that makes config blocks so special.

As a consequence of that, is there no way to get to $provide outside config blocks, e.g. with angular.injector() (though it seems that it gets provider instances also)?

The question, besides mere curiosity, also has some practical considerations. In 1.4 all of $provide functions are exposed to module, but that's not true for 1.3.

Supen answered 24/4, 2015 at 18:22 Comment(0)
S
3

After some Angular injector study I was able to give an exhaustive answer to my own question.

Essentially, $injector in config blocks and provider constructor functions and $injector everywhere else are two different services with the same name, which are defined on internal provider/instance cache explicitly, together with $provide (this one is being defined in provider cache, hence it can be injected in config only).

While generally not recommended because of probable race conditions, it is possible to expose internal services to instance cache and make config-specific $provide and $injector available for injection after config phase has ended:

app.config(function ($provide, $injector) {
  $provide.value('$providerInjector', $injector);
  $provide.value('$provide', $provide);
});

The possible applications are configuring service providers any time (if possible)

app.run(function ($providerInjector) {
  var $compileProvider = $providerInjector.get('$compileProvider');
  ...
});

and defining new components at run-time

app.run(function ($provide) {
  $provide.controller(...);
  ...
});
Supen answered 14/11, 2015 at 2:31 Comment(0)
N
5

The purpose of the config() function is to allow you to perform some global configuration that will affect the entire application - that includes services, directives, controllers, etc. Because of that, the config() block must run before anything else. But, you still need a way to perform the aforementioned configuration and make it available to the rest of the app. And the way to do that is by using providers.

What makes providers "special" is that they have two initialization parts, and one of them is directly related to the config() block. Take a look at the following code:

app.provider('myService', function() {
    var self = {};    
    this.setSomeGlobalProperty = function(value) {
        self.someGlobalProperty = value;
    };

    this.$get = function(someDependency) {
        this.doSomething = function() {
            console.log(self.someGlobalProperty);
        };
    };    
});

app.config(function(myServiceProvider) {
    myServiceProvider.setSomeGlobalProperty('foobar');
});

app.controller('MyCtrl', function(myService) {
    myService.doSomething();
});

When you inject a provider into the config() function, you can access anything but the $get function (technically you can access the $get function, but calling it won't work). That way you can do whatever configuration you might need to do. That's the first initialization part. It's worth mentioning that even though our service is called myService, you need to use the suffix Provider here.

But when you inject the same provider into any other place, Angular calls the $get() function and injects whatever it returns. That's the second initialization part. In this case, the provider behaves just like an ordinary service.

Now about $provide and $injector. Since they are "configuration services", it makes sense to me that you can't access them outside the config() block. If you could, then you would be able to, say, create a factory after it had been used by another service.

Finally, I haven't played with v1.4 yet, so I have no idea why that behavior apparently has changed. If anyone knows why, please let me know and I'll update my answer.

Nicotiana answered 24/4, 2015 at 20:43 Comment(2)
Thank you, Michael, that's quality answer that will serve the community well. Currently I've advanced a bit further then that and can't learn anything new from the answer. I hoped that somebody would share his knowledge on ng internals. I guess I have to go through bootstrap and $injector and study them once more.Supen
In 1.4 module finally exposes the whole API that $provide provides. It wasn't so in previous versions (decorator was missing at least) .Supen
S
3

After some Angular injector study I was able to give an exhaustive answer to my own question.

Essentially, $injector in config blocks and provider constructor functions and $injector everywhere else are two different services with the same name, which are defined on internal provider/instance cache explicitly, together with $provide (this one is being defined in provider cache, hence it can be injected in config only).

While generally not recommended because of probable race conditions, it is possible to expose internal services to instance cache and make config-specific $provide and $injector available for injection after config phase has ended:

app.config(function ($provide, $injector) {
  $provide.value('$providerInjector', $injector);
  $provide.value('$provide', $provide);
});

The possible applications are configuring service providers any time (if possible)

app.run(function ($providerInjector) {
  var $compileProvider = $providerInjector.get('$compileProvider');
  ...
});

and defining new components at run-time

app.run(function ($provide) {
  $provide.controller(...);
  ...
});
Supen answered 14/11, 2015 at 2:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.