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: