In AngularJS, it is a common practice to annotate injected dependencies for controllers, services, directives, etc.
For example:
(function() {
'use strict';
angular.module('exampleApp', [])
.directive('myDirective', ['$location', 'myService', myDirective]);
function myDirective($location, myService) {
return {
scope: {},
link: link,
};
function link(scope, element, attrs) {
myService.doSomething($location.path());
// ...
}
}
})();
Notice how the annotation of the injected arguments causes duplication
($location and $myService appears twice).
It becomes the responsibility of the developer to ensure that
the actual arguments and the annotation are always in sync.
If they are not, problems will occur that can cause lost time while
head-scratching. As the list of arguments grows, it gets even harder to
maintain.
The reason for needing to annotate the injected dependencies is documented in the AngularJS docs under “Dependency Annotation”:
To allow the minifiers to rename the function parameters and still be able to inject right services, the function needs to be annotated…
JavaScript minifiers will rename function arguments and parameters to something short and ambiguous (usually using just one letter). In that case, AngularJS does not know what service to inject since it tries to match dependencies to the argument names.
ng-annotate and ngannotate-rails
ng-annotate is a tool that
automatically adds AngularJS dependency injection annotations.
It is non-intrusive and sends annotated results to the minifier.
It handles rebuilding annotations, adding and removing them as needed.
ngannotate-rails is a Ruby gem that wraps ng-annotate
and seamlessly integrates it into Rails’ asset pipeline.
Both, JavaScript and CoffeeScript, are supported.
Using it is as easy as adding it to the Gemfile and bundling.
Once ngannotate-rails is in place,
AngularJS code can be written without needing to annotate
the injected dependencies.
The previous code can now be written as:
(function() {
'use strict';
angular.module('exampleApp', []).directive('myDirective', myDirective);
function myDirective($location, myService) {
return {
scope: {},
link: link,
};
function link(scope, element, attrs) {
myService.doSomething($location.path());
// ...
}
}
})();
Now there is no duplication of argument names and no weird array notation.
Manual dependency annotation
95% of the time ngannotate-rails will handle annotating the dependencies,
but there are situations where dependencies cannot be derived using static
analysis. In such a situation, manual annotation is required.
John Papa’s wonderful community styleguide suggests the following:
Use $inject to manually identify your dependencies for Angular components.
This is recommended because it’s the same technique that ng-annotate uses.
In addition, it has the benefit of being a straight-forward assignment,
rather than an unusual array notation.
(function() {
'use strict';
angular.module('exampleApp', []).directive('myDirective', myDirective);
function item($location, myService) {
return {
scope: {},
link: link,
controller: controller,
};
function link(scope, element, attrs) {
myService.doSomething($location.path());
// ...
}
controller.$inject = ["$scope"];
function controller($scope) {
$scope.foo = "bar";
// ...
}
}
})();
In this case, myService will be automatically annotated by ngannotate-rails,
but $scope that is passed into the controller will not be.
Thus, it must be manually annotated via controller.$inject = ["$scope"].
Keep in mind these situations and consider checking for any missing injection
annotations next time you get an Unknown Provider error in
your application.
What’s next
If you found this useful, you might also enjoy: