Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS two different $injectors

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

like image 750
Harry Burns Avatar asked Apr 28 '15 06:04

Harry Burns


2 Answers

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){}])
like image 167
Harry Burns Avatar answered Nov 15 '22 05:11

Harry Burns


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.

like image 35
manasi sakhare Avatar answered Nov 15 '22 05:11

manasi sakhare