82

Having the darnedest time trying to figure out why minification is not working.

I have injected via an array object my providers prior the function per numerous suggestions across the web and yet still "Unknown provider: aProvider <- a"

Regular:

var app = angular.module('bpwApp', ['ui.bootstrap', 'ui', 'myTabs'])
    .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider){
    $routeProvider.
        when('/', {templateUrl: 'partials/home.jade', controller: HomeCtrl});

    $locationProvider.html5Mode(true);
    }])

Minified:

var app = angular.module('bpwApp', ['ui.bootstrap', 'ui', 'myTabs'])
    .config(['$routeProvider', '$locationProvider', function(a, b){
    a.
        when('/', {templateUrl: 'partials/home.jade', controller: HomeCtrl});

    b.html5Mode(true);
    }])

Any suggestion would be much obliged!

Dan Kanze
  • 18,097
  • 28
  • 77
  • 133
BPWDevelopment
  • 919
  • 1
  • 7
  • 8
  • 1
    What do you use to minify your code? uglifyJS? Also check out: https://github.com/btford/ngmin ;) – AndreM96 Jun 21 '13 at 15:20
  • I used ngmin, all it did was line up the code in a different white space format. I tried using express-uglify as middleware but it was not working so I tried manually using an online uglifier. Either way the code ended up the same. – BPWDevelopment Jun 21 '13 at 15:23
  • Also, isn't there a missing ````]```` ? (before the closing ````)```` ) – AndreM96 Jun 21 '13 at 15:23
  • There was, I forgot them in this particular snippet. It does not change the fact "unknown provider a" still happens :( – BPWDevelopment Jun 21 '13 at 15:28
  • 2
    Ok, well, what online minifier did you use? This works fine with your code: http://marijnhaverbeke.nl/uglifyjs – AndreM96 Jun 21 '13 at 15:29
  • It minifies the code dandily. That is what I used. The issue is once it runs it says it is not written appropriately and cannot find the provider "a" – BPWDevelopment Jun 21 '13 at 15:43
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32172/discussion-between-bpwdevelopment-and-andrem96) – BPWDevelopment Jun 21 '13 at 15:46
  • [Using uglify and minify together][1] [1]: http://stackoverflow.com/a/24215002/2371560 – ssoward Jun 13 '14 at 23:21

8 Answers8

139

I ran into this problem before with Grunt.js Uglify plugin.

One of the options are mangle

uglify: {
  options: {
    mangle: false
  },

Which I believe runs regex functions on "like strings" and minifys them.

For example:

angular.module("imgur", ["imgur.global","imgur.album"]);

Would become:

angular.module("a", ["a.global","a.album"]);

Disable it --- this feature doesn't play nice with Angular.

Edit:

To be more precise as @JoshDavidMiller explains:

Uglify mangle only mangles like variables, which is what actually causes the AngularJS problem. That is, the problem is in injection and not definition.

function MyCtrl($scope, myService) would get mangled to function MyCtrl(a, b), but the service definition inside of a string should never get altered.

  • Running ng-min before running uglify solves this problem.
Dan Kanze
  • 18,097
  • 28
  • 77
  • 133
  • 3
    He updated his code. His problem wasn't that "$locationProvider" became "b" or anything like that. It just didn't work. However, +1 for this answer :) – AndreM96 Jun 21 '13 at 15:58
  • 1
    Thanks was looking hard to find that option. – reen Oct 12 '13 at 10:04
  • I ran into this exact same thing when using angular bootstrap + yeoman. Using the [yeoman angular generator](https://github.com/yeoman/generator-angular) it produced a `dist` build that would have the mentioned dependency error "Unknown Provider". Setting `mangle: false` solved the problem. (note: the issue was only a problem in the `grunt` built `dist` not the developer friendly `app` build) – craigb Oct 28 '13 at 21:08
  • 6
    Does `mangle: true` *really* mangle "like strings"? I'm pretty sure it only mangles like *variables*, which is what actually causes the AngularJS problem. That is, the problem is in injection and not definition. `function MyCtrl($scope, myService)` would get mangled to `function MyCtrl(a, b)`, but the service definition inside of a string should never get altered. Running `ng-min` before running `uglify` solves this problem, no? – Josh David Miller Nov 15 '13 at 02:06
  • 1
    `ng-min` is now deprecated in favor of [`ng-annotate`](https://github.com/olov/ng-annotate) – Atav32 Nov 20 '14 at 00:23
  • I do not recommend to set mangle to true. Insead of check what angular sey about the error: https://docs.angularjs.org/tutorial/step_05#a-note-on-minification. That worked for me in quite a large Angular app. – ingaham Sep 30 '15 at 11:22
51

Problem

From AngularJS: The Bad Parts:

Angular has a built in dependency injector that will pass appropriate objects to your function based on the names of its parameters:

function MyController($scope, $window) {
    // ...
}

Here, the names of the parameters $scope and $window will be matched against a list of known names, and corresponding objects get instantiated and passed to the function. Angular gets the parameter names by calling toString() on the function, and then parsing the function definition.

The problem with this, of course, is that it stops working the moment you minify your code. Since you care about user experience you will be minifying your code, thus using this DI mechanism will break your app. In fact, a common development methodology is to use unminified code in development to ease debugging, and then to minify the code when pushing to production or staging. In that case, this problem won’t rear its ugly head until you’re at the point where it hurts the most.

(...)

Since this dependency injection mechanism doesn’t actually work in the general case, Angular also provides a mechanism that does. To be sure, it provides two. You can either pass along an array like so:

module.controller('MyController', ['$scope', '$window', MyController]);

Or you can set the $inject property on your constructor:

MyController.$inject = ['$scope', '$window'];

Solution

You can use ng-annotate for auto adding annotations required for minifying:

ng-annotate adds and removes AngularJS dependency injection annotations. It is non-intrusive so your source code stays exactly the same otherwise. No lost comments or moved lines.

ng-annotate is faster and stabler than ngmin (which is now deprecated) and it has plugins for many tools:


Starting from AngularJS 1.3 there's also a new param in ngApp called ngStrictDi:

if this attribute is present on the app element, the injector will be created in "strict-di" mode. This means that the application will fail to invoke functions which do not use explicit function annotation (and are thus unsuitable for minification), as described in the Dependency Injection guide, and useful debugging info will assist in tracking down the root of these bugs.

Paolo Moretti
  • 47,973
  • 21
  • 95
  • 89
  • 1
    +1 Simply switching to grunt-ng-annotate fixed this issue and ngmin is deprecated now anyway so that's another reason to switch. – Pier-Luc Gendreau Aug 21 '14 at 18:13
  • that was the fix I was looking for days! – pedrommuller Dec 13 '14 at 03:48
  • I am facing the same problem building a minified code with browserify, angular and gulp-minify. I have removed gulp minify and replaced it by gulp-ng-annotate, code is still minified but still doesn't work. – Dimitri Kopriwa Jan 13 '15 at 16:52
  • @BigDong if you're using browserify the best way is probably to enable `ngStrictDi` (something like `
    `) and use `gulp-ng-annotate` even in your in your development environment so that you easily track down these minification bugs.
    – Paolo Moretti Jan 13 '15 at 17:16
  • @PaoloMoretti I did try with ngStrictDi and gulp-ng-annotate, browserify can bundle but code isn't minified, isn't it supposed to be ng-annotate job ? – Dimitri Kopriwa Jan 14 '15 at 17:00
  • I create my controller like that : .controller('SomeCtrl', ['$scope', function SomeCtrl ($scope) After looking inside the bundle.js file, I can't see any change by gulp-ng-annotate, so i have just removed the plugin – Dimitri Kopriwa Jan 14 '15 at 17:07
  • We use MyController.$inject = [] but still it doesn't work. I wonder what the issue could be! We even use ng-annotate without success. Is there something else that can be causing this? – Willa Aug 02 '15 at 12:55
  • @Willa make sure that the dependency you're trying to load is actually included in the minified bundle. – Paolo Moretti Aug 02 '15 at 13:07
  • @PaoloMoretti sorry for a late response. We still have this problem. Actually we are loading all dependencies just fine. I even examined the minified version of all the files and I see the controller is there defined and registered with angular. I even tried to disable mangling of the variable but still I can't get the minified version of the app to work. – Willa Aug 17 '15 at 07:14
22

I got same error. However, for me, the problem is directives' controller declaration. You should do this instead.

myModule.directive('directiveName', function factory(injectables) {
    var directiveDefinitionObject = {
      templateUrl: 'directive.html',
      replace: false,
      restrict: 'A',
      controller: ["$scope", "$element", "$attrs", "$transclude", "otherInjectables",
        function($scope, $element, $attrs, $transclude, otherInjectables) { ... }]
    };
    return directiveDefinitionObject;
  });

https://github.com/angular/angular.js/pull/3125

angelokh
  • 9,394
  • 6
  • 61
  • 119
  • 1
    Thank you @angelokh ! I had exactly this problem. I was using the `controller: function ($scope) {}` notation. – jbasko Feb 17 '14 at 15:40
  • 2
    This is more like the solution to the actual problem than `mangle: false` suggested in other responses, because we still want to be able to mangle names. – jbasko Feb 17 '14 at 15:41
9

I had a similar issue using grunt, ngmin and uglify.

I ran the process in this order: concat, ngmin, uglify

I was continuing to get the $injector error from angular until I added in the uglify options mangle: false - then everything was fixed.

I also tried to add the exceptions to uglify like this:

 options: {
  mangle: {
     except: ['jQuery', 'angular']
  }
}

But to no avail...

Here is my gruntFile.js for further clarification:

module.exports = function(grunt) {
'use strict';
// Configuration goes here
grunt.initConfig({
    pkg: require('./package.json'),

    watch: {
        files: ['scripts/**/*.js', 'test/**/*spec.js', 'GruntFile.js'],
        tasks: ['test', 'ngmin']
    },

    jasmine : {
        // Your project's source files
        src : ['bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'scripts/app.js', 'scripts/**/*.js' ],
        // Your Jasmine spec files

        options : {
            specs : 'test/**/*spec.js',
            helpers: 'test/lib/*.js'
        }
    },

    concat: {
      dist : {
          src: ['scripts/app.js', 'scripts/**/*.js'],
          dest: 'production/js/concat.js'
      }
    },

    ngmin: {
        angular: {
            src : ['production/js/concat.js'],
            dest : 'production/js/ngmin.js'
        }

    },

    uglify : {
        options: {
            report: 'min',
            mangle: false
        },
        my_target : {
            files : {
                'production/app/app.min.js' : ['production/js/ngmin.js']
            }
        }
    },

  docular : {
      groups: [],
      showDocularDocs: false,
      showAngularDocs: false
  }

});

// Load plugins here
grunt.loadNpmTasks('grunt-ngmin');
grunt.loadNpmTasks('grunt-docular');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-connect');

// Define your tasks here
grunt.registerTask('test', ['jasmine']);
grunt.registerTask('build', ['concat', 'ngmin', 'uglify']);
grunt.registerTask('default', ['test', 'build', 'watch']);

};

Sten Muchow
  • 6,373
  • 4
  • 33
  • 46
5

AndrewM96 suggestion of ng-min is right.

The alignment and white space matters to Uglify as well as Angular.

Dan Kanze
  • 18,097
  • 28
  • 77
  • 133
BPWDevelopment
  • 919
  • 1
  • 7
  • 8
  • 10
    [ng-min](https://github.com/btford/ngmin#conceptual-overview) appears to just process the angular files so they are friendly to `uglify`. In our build process we use both (`ng-min` before `uglify`) and still had an issue with the uglified js. – craigb Oct 28 '13 at 21:13
  • 4
    Why is this marked as the answer? (Also, AndrewM96 should be AndreM96) – Jay Feb 17 '14 at 21:40
  • though in docs ng-min sounds promising it does not solve the problem – special0ne Feb 23 '14 at 06:27
  • @craigb having the same problem. Maybe it's a combination of things. I use RequireJS as well. I basically do all the function-changing-stuff ng-min should do myself and still run ng-min, than run require build and than run uglify with mangle true. This process seems to work most of the times. – escapedcat Feb 24 '14 at 16:12
3

I had a similar problem. And solved it the following way. We need to run a Gulp module called gulp-ng-annotate before we run uglify. So we install that module

npm install gulp-ng-annotate --save-dev

Then do the require in Gulpfile.js

ngannotate = require(‘gulp-ng-annotate’)

And in your usemin task do something like this

js: [ngannotate(), uglify(),rev()] 

That solved it for me.

[EDIT: Fixed typos]

ashwinjv
  • 2,279
  • 1
  • 19
  • 29
2

Uglify has an option to disable mangling on specific files:

options: {
  mangle: {
     except: ['jQuery', 'angular']
  }
}

https://github.com/gruntjs/grunt-contrib-uglify#reserved-identifiers

Courtenay
  • 99
  • 1
  • 4
2

This is very difficult to debug because a lot of services are named the same (mostly e or a). This will not solve the error, but will provide you with the name of the unresolved service which enables you to track down, in the uglified output, the location in the code and finally enables you to solve the issue:

Go into lib/scope.jsof Uglify2 (node_modules/grunt-contrib-uglify/node_modules/uglify-js/lib/scope.js) and replace the line

this.mangled_name = this.scope.next_mangled(options);

with

this.mangled_name = this.name + "__debugging_" + counter++
Palec
  • 10,298
  • 7
  • 52
  • 116
Bas
  • 21
  • 1