When Dependency Injection fails – AngularJS

If you are into any serious Angular development you see such errors once too often:

Uncaught Error: [$injector:modulerr] Failed to instantiate module app due to:
Error: [$injector:modulerr] Failed to instantiate module ngRoute due to:
Error: [$injector:nomod] Module 'ngRoute' 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.
Or:
Error: [$injector:unpr] Unknown provider: scopeProvider <- scope
http://errors.angularjs.org/1.2.26/$injector/unpr?p0=scopeProvider%20%3C-<div ng-view="" class="ng-scope">
This is a clear case of Dependency Injection failing. If you are one of the early adopters of Angular you, will appreciate how descriptive these messages have become as the framework has evolved. For example the one above clearly mentioning ngRoute module was not found.
Angular dependency injection can fail for number of reasons.  Through this post we will try to understand some common reason for such failures and their resolution. If next time you encounter such errors remember to refer back to this post.

Misspelled dependencies

By far the most common cause of DI failures. Remember dependencies names are case sensitive, hence need to be reproduced verbatim. $scope is not scope, sope, or scoop :). Double check your dependency names.

Order and Count of dependencies

Again very common issue when using minification friendly syntax. These are all are incorrect:
 // $route -> $scope, $scope->$route
.controller('Ctrl1',['$scope', '$route', function($route, $scope)    

//$routeParams is undefined
.controller('Ctrl2',['$scope', '$route', function($scope,$route,$routeParams)     

 // Nothing is injected, so its fine.
.controller('ctrl3',['$scope', '$route','$routeParams', function($scope,$route)
The biggest problem in this scenario is that there are no failures just incorrect assignment and hence is hard to debug.

Duplicate Module declaration

There is a subtle difference between module declaration and getting a reference to an exiting module. The statement below declares a module controllers:
angular.module('controllers',[])  // Second parameters to specify dependency
Whereas this one retrieves the module controllers:
angular.module('controllers')  // No second parameter
If we unknowingly use the first syntax multiple times across the app,  we are actually overwriting the existing module declaration, and the direct impact of this is failed dependency injection. Again a difficult issue to debug. If you see dependency injection failure for controllers, filters, services or directives that seem to be available and there is no typo error, duplicate module declaration could be a prime reason for this failure.
The fix here is simple just make sure module declaration for any module happens only once in the entire app.

Module not injected

With third party components we always make sure to inject module dependencies before using the component. But with framework service that have moved over time  into their own modules we may overlook the change.
Angular routing is a prime example of this. Since 1.2.0  Angular Routing is in a separate module ngRoute (which include $route, $routeProvider and $routeParams services) instead of the core module (changelog). If you are getting error injecting standard AngularJS service it’s better to refer to the documentation and double check which module the dependency is part of.
Remember in Angular ng is core module.  Anything other than core (ng) has to be injected.

Dependency resolution with $routeProvider resolve

$routeProvider allows us to inject dependencies using resolve property. Such dependencies can only be injected into controllers that have been declared in the route definition, such as:
$routeProvider.when('/page1', { 
   templateUrl: 'page1.html',
   controller:'Page1Controller' , 
   resolve: {
      data:function(){ return "someData";} 
   }
});

Only Page1Controller created through the route resolution process is injected with the dependency (data).

The below mechanism of loading controller throws dependency injection error:

<div ng-controller="Page1Controller"></div>

Injecting dependency at config stage

The only things allowed to be injected at the config state are constants and providers. Forget about injecting any standard service at config stage, be it Angular services (like $http, $location, $timeout) or any custom service we create. This piece of code will fail with error Error: [$injector:unpr] Unknown provider: $http:
.config(['$routeProvider','$http', function ($routeProvider) {
At config stage dependencies are not ready to be used and hence Angular safeguards against any accidental injection of such dependencies.

 

I have covered most of the dependency injection errors that are common.Hope this post helps people stuck with DI issues in Angular.
I have also created a fiddle highlighting most of the issues described above. Go check it out!
Tags
AngularJS

Related posts