101

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?

Der Hochstapler
  • 19,560
  • 15
  • 87
  • 126
  • You could try adding distinct comments to every JS source file (if not already the case), and use the preserveComments option of UglifyJS: that would give you an idea of which file contains the incorrect code. – JB Nizet Feb 10 '14 at 22:32
  • Do you happen to be using decorators? I've found that ngmin doesn't seem to re-write decorators properly when I've used it in the past which results in errors like yours. – dherman Feb 10 '14 at 22:47
  • @JBNizet: I like the idea, but adding that directive to the options doesn't seem to have any effect. – Der Hochstapler Feb 10 '14 at 22:51
  • @dherman: Could you give me an example of decorators? I'm not sure what they would be in this context. – Der Hochstapler Feb 10 '14 at 22:52
  • See https://github.com/gruntjs/grunt-contrib-uglify (if you use grunt). The value of the option should be "all". – JB Nizet Feb 10 '14 at 22:54
  • @JBNizet: Yeah, I am using grunt. I was looking at the documentation and am using `"all"`. Which is why I'm confused that I'm not getting the expected output. I also added `beautify:true` to the options to get a clearer output. There are no comments at all though. Regardless though, I'm not sure how I would determine the problematic source from the point where the error is thrown. – Der Hochstapler Feb 10 '14 at 22:56
  • Sorry, I have never actually used this option. I just knew it existed by reading the documentation. You could try tricking Uglify by declaring a unique string at the top of every file: `var foo = "I'm in file Foo.js";` – JB Nizet Feb 10 '14 at 23:02
  • Or you could post a link to the source code here. It shouldn't be that hard to find what's causing the issue. Chrome has pretty good Code beautifier which helps reading minified source. Then it's just a matter of tracking where the exception is thrown. – Michał Miszczyszyn Feb 10 '14 at 23:05
  • @JBNizet: Yeah, the problem I still have though is, even if I could mark all the files, I can't determine which file causes the problem from the point where the error is thrown. The callstack is mess of mangled variables and I'm unable to analyze the point in the source that is actually causing the error. Even with beautified source and multiple breakpoints I was unable to find the source of the problem. The whole call hierarchy at this point seems very complex. – Der Hochstapler Feb 10 '14 at 23:05
  • @Miszy: Sadly, I can't post the source here as it is not a public project. I know where the exception is thrown. But I don't know how to determine the location of the problematic source from there, as the code that evaluates the Angular definitions is [rather complex](http://i.imgur.com/uPfMoYQ.png). – Der Hochstapler Feb 10 '14 at 23:09
  • @OliverSalzburg Here's the example that is in angular's docs: http://docs.angularjs.org/api/AUTO.$provide#methods_decorator – dherman Feb 11 '14 at 15:49
  • @dherman: Ah, I see. No we're not using decorators in our source and a quick `grep` also didn't reveal any uses in our dependencies. – Der Hochstapler Feb 11 '14 at 16:18
  • You can check... http://stackoverflow.com/questions/21688681/unknown-provider-aprovider-a-how-do-i-find-the-original-provider/23130231#23130231 – Deepak Patil Apr 17 '14 at 11:20
  • ngAnnotate is broken for angular 1.5's new way of defining components: https://github.com/olov/ng-annotate/issues/231?_pjax=%23js-repo-pjax-container – Michael Trouw Apr 06 '16 at 16:04

9 Answers9

195

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:

enter image description here

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:

enter image description here

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:

enter image description here

The locals parameter (mangled to d in my code) gives a pretty good idea about which object in your source is the problem:

enter image description here

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:

enter image description here

Prevent this from happening again

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:

  • DI annotation missing
  • DI annotation incomplete
  • DI annotation in wrong order

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.

Der Hochstapler
  • 19,560
  • 15
  • 87
  • 126
  • 12
    This is a fantastic write-up, written with care. I just ran into this issue, seems to be an issue deep in ngmin somewhere. Your tips helped me know where to look. In the end I just "array-ified" all my angular params, and the problem went away. All prior builds ng-minified just fine, and nothing considerable changed. I did not add any global functions -- it just stopped working, mysteriously, by mangling some controller/directive/service/filter? – zenocon May 07 '14 at 23:50
  • This was a great source of help. I didn't know you have to use array (inline) syntax also for other functions, like router resolve, .run, .config, etc. – VDest Aug 27 '14 at 09:59
  • 4
    In my case it was controller in directive. If in 'd' variable you'll see $attr, that's probably same issue. You should wrap params in array brackets for inner directive controller. controller: ["$scope", function($scope){...}] instead of controller: function($scope){...} – alex naumov Sep 24 '14 at 10:47
  • Thank you so much for your write-up and solution using safe dependency injection / array notation for the var function reference. I too had this error and because of your solution I was able to keep moving forward. you rock! – Frankie Loscavio Oct 21 '14 at 15:28
  • 1
    Everytime I have this issue I read this again and want to vote for this again. Btw, here is how to setup gulp version `uglify({ output : { beautify : true }})` – Eugene Gluhotorenko Nov 11 '15 at 12:49
  • changed `app.run(function($httpBackend){//.......};` to `app.run(['$httpBackend',function($httpBackend){//......}];` – Aakash Nov 17 '15 at 04:57
  • Disabling the uglyfiyer, adding ng-strict-di to the ng-app line: `` And angular will tell you where is the problem! fix and turn back Uglify – PyWebDesign Feb 01 '16 at 19:52
  • I haven't run into this problem in the past but just experienced it when building using `$provider` and `$filterProvider` in my app.js. I have been using `ng-annotate` this whole time and I tried to add the full form of direct injection but it still didn't work. The only way I was able to remedy the issue was to turn off `uglify`'s `mangle` option (bummer). – David Rhoderick Sep 29 '16 at 15:47
  • @ArtlyticalMedia Sometimes ng-annotate doesn't pick up the function that requires annotation. We always use `@ngAnnotate` JSDoc on every function we expect to be annotated. – Der Hochstapler Sep 29 '16 at 16:36
  • Hi, I've always turned on strict mode and didn't get error about it normaly. But when I use webpack uglify plugin, it always has that 'unknown provider' error. – runchu Jun 12 '19 at 17:52
  • I can't upvote you enough. Thanks a lot for the detailled answer ! – Freego Mar 19 '21 at 13:21
30

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:

BAD

return {
    restrict: "E",
    scope: {                
    },
    controller: ExampleDirectiveController,
    templateUrl: "template/url/here.html"
};

GOOD

return {
    restrict: "E",
    scope: {                
    },
    controller: ["$scope", ExampleDirectiveController],
    templateUrl: "template/url/here.html"
};
Ash Clarke
  • 4,484
  • 33
  • 44
  • 2
    This was such a cheeky one... Uglify wasn't causing this for me until a recent update! – SamMorrowDrums Dec 07 '15 at 15:21
  • My issue was the same, but it turns out what I needed to add was `/* @ngInject */` before the function. It seems to do the complicated injection part without needing to type each included module (I'm using Yeoman) – Nicholas Blasgen Apr 11 '16 at 21:29
26

use ng-strict-di with ng-app

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.

Mark Fox
  • 8,052
  • 9
  • 48
  • 72
11

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
    };
}]);
Dalorzo
  • 19,312
  • 7
  • 50
  • 97
  • I know. That's why we use ngmin. I suspect that it has a problem with some part of our source or its dependencies. Which is why I'm trying to get to the root of this issue. – Der Hochstapler Feb 10 '14 at 22:06
  • 1
    My recommendation is that you create your code in this way. So you can use any minifier – Dalorzo Feb 10 '14 at 22:07
  • 3
    I *am* creating our code this way. But we have external dependencies which don't. ngmin has resolved this problem well for us in the past. I assume a recent change created this issue. I would now like to find the source of this issue so that I can properly fix it in our code, our dependency or possibly in ngmin itself. – Der Hochstapler Feb 10 '14 at 22:09
  • Since the issue sounds like a very specific to a particular component or code is hard to provide guidance, at least from my end – Dalorzo Feb 10 '14 at 22:11
  • ngmin does not require you to use the array declaration mode, it adds lots of useless declarations. – Nanocom Feb 10 '14 at 23:04
8

I just had the same problem and resolved it by simply replacing ngmin (now deprecated) with ng-annotate for my grunt build task.

It seems that yeoman angular has also been updated to use ng-annotate as of this commit: https://github.com/yeoman/generator-angular/commit/3eea4cbeb010eeaaf797c17604b4a3ab5371eccb

However if you are using an older version of yeoman angular like me, just replace ng-min with ng-annotate in your package.json:

-    "grunt-ngmin": "^0.0.3",
+    "grunt-ng-annotate": "^0.3.0",

run npm install (then optionally npm prune), and follow the changes in the commit to edit Gruntfile.js.

Der Hochstapler
  • 19,560
  • 15
  • 87
  • 126
Xuwen
  • 231
  • 2
  • 3
7

in order to know what the original variable name was you can change how uglify mangles the variables:

../node_modules/grunt-contrib-uglify/node_modulesuglify-js/lib/scope.js

SymbolDef.prototype = {
  unmangleable: [...],
  mangle: function(options) {
    [...]
    this.mangled_name = s.next_mangled(options, this)+"_orig_"+this.orig[0].name;
    [...]
  }
};

and now the error is much more obvious

Error: [$injector:unpr] Unknown provider: a_orig_$stateProvider
http://errors.angularjs.org/1.3.7/$injector/unpr?p0=a_orig_%24stateProvider
at eval (eval at <anonymous> (http://example.com/:64:17), <anonymous>:3155:20)

EDIT

So obvious now...

Gruntfile.js

uglify: {
  example: {
    options: {
      beautify: true,
      mangle: true
    },
    [...]
  },
  [...]
}

../node_modules/grunt-contrib-uglify/node_modulesuglify-js/lib/scope.js

var numberOfVariables = 1;
SymbolDef.prototype = {
  unmangleable: [...],
  mangle: function(options) {
    [...]
    this.mangled_name = s.next_mangled(options, this)+"_orig_"+this.orig[0].name+"_"+numberOfVariables++;
    [...]
  }
};

now each variable is mangled to a unique value that also contains the original... just open up the minified javascript and search for "a_orig_$stateProvider_91212" or whatever... you will see it in it's original context...

couldn't be any easier...

user3338098
  • 845
  • 1
  • 13
  • 33
4

Also do not forget the resolve property of the route. It also must be defined as the array:

$routeProvider.when('/foo', {
    resolve: {
        bar: ['myService1', function(myService1) {
            return myService1.getThis();
        }],
        baz: ['myService2', function(myService2) {
            return myService2.getThat();
        }]
    }
});
Petr Felzmann
  • 1,171
  • 2
  • 18
  • 35
  • This happened to me when I added a bunch of resolves to my routes. You've potentially saved me hours of painful debugging, thanks. – Paul McClean Oct 08 '15 at 15:37
4

With generator-gulp-angular:

   /** @ngInject */
    function SomeController($scope, myCoolService) {

}

Write /** @ngInject */ before each controller, service, directive.

2

A quick and dirty fix for this if you don't require Uglify to mangle/shorten your variable names is to set mangle = false in your Gruntfile

    uglify: {
        compile: {
            options: {
                mangle   : false,
                ...
            },
        }
    }
Parris Varney
  • 10,663
  • 12
  • 44
  • 71