53

I've got this app with two modules:

angular.module('components', []).directive('foo', function () { return {};});
angular.module('gaad', ['components']);

There is a bunch of directives associated with this modules which I'm not including here. The application works fine. However when I'm trying to retrieve injector for module gaad:

var injector = angular.injector(['gaad', 'components']); //called after 'gaad' module initialization

the error is thrown:

Uncaught Error: Unknown provider: $compileProvider from components 

The application is quite big right now and I have no idea where should I look for bugs. So my question is: What could be the reason for my problems?

EDIT: I was able to replicate my problem: http://jsfiddle.net/selbh/ehmnt/11/

Szymon Wygnański
  • 10,642
  • 6
  • 31
  • 44

3 Answers3

122

Before answering the question we need to note that there is only one injector instance per application and not per module. In this sense it is not possible to retrieve an injector per module. Of course if we take the top-level module, it represents the whole application. In this sense, an application and top-level module seem equivalent. It might seem like a subtle difference but it is important to understand in order to fully and properly answer this question.

Next, from what I can understand you would like to retrieve the $injector and not create a new instance of it. The thing is that the angular.injector will create a new $injector instance for modules (an app) specified as arguments. Here the main AngularJS module (ng) must be specified explicitly. So, in this code example:

var injector = angular.injector(['gaad', 'components']);

you were trying to create a new injector from components defined in 'gaad' and 'components' modules and obviously $compileProvider is not defined in your custom modules. Adding the ng module to the list would "solve" the problem by creating a new injector - something that you probably don't want to happen.

To actually retrieve an injector instance associated to a running application we can use 2 methods:

Here is the jsFiddle showing 2 methods of the injector retrieval: http://jsfiddle.net/xaQzb/

Please also note that using $injector directly is not very common scenario outside of unit testing. It might be useful thought for retrieving AngularJS services from outside of AngularJS world. More info here: Call Angular JS from legacy code.

Community
  • 1
  • 1
pkozlowski.opensource
  • 117,202
  • 60
  • 326
  • 286
  • 4
    It is nice to hear this @maxisam! And I'm learning a lot by answering those questions :-) – pkozlowski.opensource Nov 15 '12 at 18:39
  • 1
    The JSFiddle you posted returns `undefined` as the injector. http://jsfiddle.net/xaQzb/ ... if you `console.log(injector)` it returns `undefined`. Was it meant to be pseudo-code? – Ben Lesh Nov 15 '12 at 18:53
  • Apparently there is some delay to the injector's availability. If you put it in a timeout you get the injector just fine... interesting. – Ben Lesh Nov 15 '12 at 18:58
  • See here: http://jsfiddle.net/blesh/xaQzb/6/ ... I'm not sure why it works that way, I still haven't located what the delay is. – Ben Lesh Nov 15 '12 at 18:59
  • 1
    @blesh, yes, meant to be pseudo-code, maybe I shouldn't have posted the jsFiddle... Timing issue comes from the fact that AngularJS needs time to boostrap an app and expose injector on a DOM element. I will change the jsFiddle and add your example, thnx!!! – pkozlowski.opensource Nov 15 '12 at 19:02
  • @pkozlowski.opensource you are my superhero! – madhead Feb 10 '13 at 22:36
  • This deserves a bounty. – Benny Bottema Sep 16 '15 at 13:20
  • Is there a way to get injector from outside, without `angular` object. With a help of querySelector ? – Kanso Code Sep 01 '16 at 12:01
13

It appears you need to include ng in the injector as well.

var injector = angular.injector(['ng', 'b', 'a']);

From: AngularJS: injector

The ng module must be explicitly added.

Mistalis
  • 17,793
  • 13
  • 73
  • 97
Ryan O'Neill
  • 3,727
  • 22
  • 27
2

The problem is in the flow of execution of Angular's code. To integrate on @pkozlowski.opensource's answer and related comments, note that the $injector property is made available to DOM elements after the module code is executed, so that when you access the property (as when you try to console.log it) the property will still be undefined.

You can solve this problem by simply setting a 0 timeout to the execution of the logging function (i.e., no explicit delay in milliseconds is needed). This hack works because the logging function will be executed when the stack is empty, that is when Angular's bootstrapping has finished and the $injector property is available to the DOM elements.

Another (maybe better) solution in Angular's lingo would be to include your $injector-accessing code in a run block (see also the related API). The $injector could then be easily injected into the initialization function as a normal service. This works because run blocks are queued and executed asynchronously when all bootstrapping has terminated.

Below you can find two fiddles, one for each solution.

0 Timeout jsFiddle

Run block jsFiddle

EDIT

Also, there is usually no need to explicitly load the ng module. In normal cases, the ng model is already automatically loaded, unless you want to perform a manual bootstrap of Angular's code. The piece of docs referenced in one of the answers refers (implicitly, unfortunately) to Angular's manual bootstrapping procedure, when you have to manually create the injector and tell it which modules you want to load

tetotechy
  • 725
  • 1
  • 6
  • 16