AngularJS two different $injectors
Asked Answered
C

3

6

Today I found, that $injector injected to config or provider is different from $injector injected to service, factory or controller.

And get() function from this $injectors works differently.

$injector from config or provider, can't get() any service! $injector.get('myService') throws Error: [$injector:unpr] Unknown provider: myService, but $injector.has('myService') return true. That's very very strange.

$injector from service or controller works normally.

Here is a code sample for better understanding:

angular.module('app', [])

        .provider('myProvider', function ($injector) {
            this.$get = ['$injector', function (serviceInjector) {
                return {
                    providerInjector: $injector,
                    serviceInjector: serviceInjector
                };
            }];
        })

        .service('myService', function () {})

        .controller('myCtrl', function ($scope, myProvider) {
            var providerInjector = myProvider.providerInjector;
            var serviceInjector = myProvider.serviceInjector;

            console.log(providerInjector === serviceInjector); // -> false

            console.log(serviceInjector.has('myService')); // `serviceInjector` has `myService`
            console.log(getMyService(serviceInjector)); // `serviceInjector` can get `myService`

            console.log(providerInjector.has('myService')); // `providerInjector` has `myService` too!
            console.log(getMyService(providerInjector)); // but `providerInjector` can't get `myService`! =(

            function getMyService(injector) {
                try {
                    injector.get('myService');
                    return "OK";
                } catch (e) {
                    return e.toString();
                }
            }

        });

Here is a plunker to play

Can anybody explain why there is two different injectors?

And how can I use $injector from provider/config to inject service(after service was initialized, of course)?

P.S. I use angular 1.3.13

Choate answered 28/4, 2015 at 6:11 Comment(2)
Your question is a little unclear. What do you want to achieve in provider/config? Can you give a use case?Forayer
Use case? For example: get service through $injector in config section. Or provider. After service initialization, of course. In my case(it is very complex to show) I add callbacks to provider in config section. And in callback I want to use my service. DI can't help me with that, so I use $injector. This callbacks will be called from controller, when service was initialized already.Choate
C
8

I found this issue on github: https://github.com/angular/angular.js/issues/5559

In the config function, $injector is the provider injector, where in the run function, $injector is the instance injector.

One's the $injector at the config stage (only providers and constants accessible), and one's the $injector at the run stage. The confusion may be that you're thinking the $injector modifies itself to include the new stuff as it crosses the line from config to run, but that's not true. They're two separate (although related) objects, with their own caches of instances.

A more in-depth reason for this dichotomy will probably come from a deep learning of the $injector internals, but it seems like it's been DRY-ed pretty hardcore, and the two types of injectors share almost all the same behavior, except in how they deal with "cache misses" in their instance caches.

We are going to overhaul the injector in v2, so this will get fixed there (getting rid of the config phase is one of the objectives of the injector v2).

Seems like there is really two different injectors, and angular developers will not fix that behavior(in versions <2.0). And nobody added a note about that aspect to $injector docs for some reason.

I was unable to find a way how to really get instance injector inside a configuration block without hacky tricks. So, I write a cute provider to solve that kind of problems.

.provider('instanceInjector', function () {

    var instanceInjector;

    function get() {
        return instanceInjector;
    }

    function exists() {
        return !!instanceInjector;
    }

    angular.extend(this, {
        get: get,
        exists: exists
    });

    this.$get = function ($injector) {
        instanceInjector = $injector;

        return {
            get: get,
            exists: exists
        };
    }
})

// We need to inject service somewhere.
// Otherwise $get function will be never executed
.run(['instanceInjector', function(instanceInjector){}])
Choate answered 29/4, 2015 at 3:25 Comment(1)
check this: the twin injectorsSate
F
1

Ok. After reading your comments, here is my answer.

I edited code in plunk to make it work, when invoking the providerInjector.get() the code should be as follows:

$scope.getMyServiceFromProviderInjector = function () {
        try {
                 myProvider.providerInjector.get('myServiceProvider');//here is change in provider name
                 return "OK";
            } catch (e) {
                 return e.toString();
            }
   };

According to angular docs the following is quoted for config and run blocks:

  • Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants can be injected into configuration blocks. This is to prevent accidental instantiation of services before they have been fully configured.
  • Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be injected into run blocks. This is to prevent further system configuration during application run time.

This simply means, you cannot get instances of services inside config blocks.

Forayer answered 28/4, 2015 at 8:22 Comment(3)
@Harry Burns please check the edited code which works in your plunkerForayer
myProvider.providerInjector.get('myServiceProvider') will return only provider wrapper for service, not the service itself. And you don't answer my first question - why there is two $injectors? And what the difference? And how to get "service $injector" in configuration block?Choate
then don't you think that you have two questions here. 1. Why two different $injectors. 2. How to use $inject in config. ? To your second question the answer is that, you cannot use $injector in config, because in config blocks one cannot instantiate services. As I mentioned in the answer above.Forayer
G
0

I wrote this a while back, which explains the lifecycle of both injectors of AngularJS, i.e providerInjector and instanceInjector.

http://agiliq.com/blog/2017/04/angularjs-injectors-internals/

Grapheme answered 26/4, 2017 at 12:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.