When I'm loading the minified (through UglifyJS) version of my AngularJS application, I get the following error in the console:
Unknown provider: aProvider <- a
Now, I realize that this is due to variable name mangling. The unmangled version works just fine. However, I do want to make use of variable name mangling, as it drastically reduces the size of our JS output file.
For that reason, we're using ngmin in our build process, but it doesn't seem to resolve this issue, even though it served us well in the past.
So, to debug this issue, I enabled source maps in our uglify grunt task. They are generated just fine and Chrome does load the maps from the server. Yet, I still get the same unhelpful error message, even though I was under the impression that I should now see the original name of the provider.
How do I get Chrome to use the source maps to tell me which provider is the problem here, or, alternatively, how can I find out the provider in another way?
I'd still love to know how I could have found the place in our source code that caused this issue, but I have since been able to find the problem manually.
There was a controller function declared on the global scope, instead of using a .controller()
call on the application module.
So there was something like this:
function SomeController( $scope, i18n ) { /* ... */ }
This works just fine for AngularJS, but to make it work right with mangling, I had to change it to:
var applicationModule = angular.module( "example" );
function SomeController( $scope, i18n ) { /* ... */ }
applicationModule.controller( "SomeController", [ "$scope", "i18n", SomeController ] );
After further tests, I actually found instances of more controllers that also caused issues. This is how I found the source of all of them manually:
First of all, I consider it rather important to enable output beautification in the uglify options. For our grunt task that meant:
options : {
beautify : true,
mangle : true
}
I then opened the project website in Chrome, with the DevTools open. Which results in an error like the one below being logged:
The method in the call trace we're interested in, is the one I marked with an arrow. This is providerInjector
in injector.js
. You're going to want to place a breakpoint where it throws an exception:
When you now re-run the application, the breakpoint will be hit and you can jump up the call stack. There will be a call from invoke
in injector.js
, recognizable from the "Incorrect injection token" string:
The locals
parameter (mangled to d
in my code) gives a pretty good idea about which object in your source is the problem:
A quick grep
over our source finds many instances of modalInstance
, but going from there, it was easy to find this spot in the source:
var ModalCreateEditMeetingController = function( $scope, $modalInstance ) {
};
Which must be changed to:
var ModalCreateEditMeetingController = [ "$scope", "$modalInstance", function( $scope, $modalInstance ) {
} ];
In case the variable does not hold useful information, you can also jump further up the stack and you should hit a call to invoke
which should have additional hints:
Now that you've hopefully found the problem, I feel that I should mention how to best avoid this from happening again in the future.
Obviously, you could just use the inline array annotation everywhere, or the (depending on your preference) $inject
property annotation and simply try not to forget about it in the future. If you do so, make sure to enable strict dependency injection mode, to catch errors like this early.
Watch out! In case you're using Angular Batarang, StrictDI might not work for you, as Angular Batarang injects unannotated code into yours (bad Batarang!).
Or you could let ng-annotate take care of it. I highly recommend doing so, as it removes a lot of potential for mistakes in this area, like:
Keeping the annotations up-to-date is simply a pain in the ass and you shouldn't have to do it if it can be done automatically. ng-annotate does exactly that.
It should integrate nicely into your build process with grunt-ng-annotate and gulp-ng-annotate.
Oliver Salzburg's write-up was fantastic. Upvoted.
Tip for anyone who might have this error. Mine was simply caused by forgetting to pass in an array for a directive controller:
return {
restrict: "E",
scope: {
},
controller: ExampleDirectiveController,
templateUrl: "template/url/here.html"
};
return {
restrict: "E",
scope: {
},
controller: ["$scope", ExampleDirectiveController],
templateUrl: "template/url/here.html"
};
If you're using Angular 1.3 you can save yourself a world of hurt by using ngStrictDi directive with ngApp:
<html lang="en" ng-app="myUglifiablyGreatApp" ng-strict-di>
Now — pre-minification — anything that doesn't use annotations will blow up your console and you can see the friggin' name without hunting through mangled stack traces.
Per the docs:
the application will fail to invoke functions which do not use explicit function annotation (and are thus unsuitable for minification)
One caveat, it only detects that there are annotations, not that the annotations are complete.
Meaning:
['ThingOne', function(ThingA, ThingB) { … }]
Will not catch that ThingB isn't part of the annotation.
Credit for this tip goes to the ng-annotate folks, which is recommend over the now deprecated ngMin.
To minify angular all you need is to do is to change your declaration to the "array" declaration "mode" for example:
From:
var demoApp= angular.module('demoApp', []);
demoApp.controller(function demoCtrl($scope) {
} );
To
var demoApp= angular.module('demoApp', []);
demoApp.controller(["$scope",function demoCtrl($scope) {
}]);
How to declare factory services?
demoApp.factory('demoFactory', ['$q', '$http', function ($q, $http) {
return {
//some object
};
}]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With