Search code examples
angularjsgrunt-contrib-uglify

AngularJs minification process


I'm finishing a web app and all works like a charm.

I use Grunt to join all my .js files in one unique file and that's the one I use in the index.html file to load the code.

The issue is when I use the .min. version of the file generated by grunt using 'grunt-contrib-uglify' task.

When I reload the page, the following error arises:

angular.js:38Uncaught Error: [$injector:modulerr] http://errors.angularjs.org/1.5.5/$injector/modulerr?p0=myapp&p1=Error%…ogleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.5.5%2Fangular.min.js%3A39%3A222)

I've been reading around this on Google but no success.

How can I solve this?

EDIT:

This is a typical file controller (all have the same structure):

(function() {
  var app = angular.module("post", []);
  var controllers = {};
  controllers.postCtrl = ['$scope', '$rootScope', 'myFactory', function($scope, $rootScope, myFactory) {
      $scope.loading  = {state:false};
      $scope.filters  = $scope.filters  || myFactory.authors;
      $scope.init = function() {
          var idx = myFactory.get_author_by_index(Number($('input[name="author"]').val()));
          $scope.filterSelected = $scope.filters[idx];
          angular.element(document).ready(function () {
              angular.forEach($('div.general_page_content').find('a'), function(value, index) {
                  $(value).attr('target', "_new");
              })
              angular.forEach($('div.general_page_content').find('iframe'), function(value, index) {
                  $(value).attr("width", "100%");
              })
              angular.forEach($('div.general_page_content').find('img'), function(value, index) {
                  $(value).attr("width", "100%").css('width', '100%');
              })
              myFactory.containerResize();
          });
      }
      $rootScope.$on('loading', function(evt, value) {
          $scope.loading.state = value;
      });
      $rootScope.$on('autocomplete:focus', function(ev) {
          $scope.search_focus = true;
      });
      $rootScope.$on('autocomplete:blur', function(ev) {
          $scope.search_focus = false;
      });
      $scope.showSocialShare = function(ev) {
          $scope.url  = decodeURIComponent($('input[name="url"]').val());
          $scope.text = decodeURIComponent($('input[name="text"]').val());
          $scope.img  = decodeURIComponent($('input[name="img"]').val());
          myFactory.showSocialShare($scope, ev);
      };
      $scope.favorite_post = function(ev, id, title) {
          myFactory.favorite_post($scope, ev, id, title);
      }

      $scope.fetchPostsChange = function(selected) {
          document.location = '/blog/?author='+selected.id;
      }
      $scope.search = function(text) {
          document.location = '/blog/?search='+encodeURIComponent(text);
      }
      $scope.go_to_favorites_post = function() {
          document.location = '/blog/archive/';
      }
      $scope.init();
  }];
  app.controller(controllers);
})();

UPDATE:

I took only two .js files and process them to minified it and check if the same error arises or not. The curious thing is that taking into account only two files, the same error arises, so I paste the minified file for you to be able to detect what's wrong.

!function(){angular.module("myapp",["ngMaterial","ngMessages","ngStorage","toaster","ngMdIcons","lvl.services","smart-table","angularGrid","ngFileUpload","angular-timeline","header","dashboard","sidebar","autocomplete","timeline","sidebarCollection","myappFactory","objectCtrl","homeCtrl","Collections","Collection","posts","post","model","postArchive","720kb.socialshare","services","footer"]).config(function(a,b,c){a.theme("default").primaryPalette("lime").accentPalette("grey").warnPalette("red"),a.theme("darkTheme").primaryPalette("lime").accentPalette("grey").warnPalette("red").dark(),b.enabled(!1),c.get("user")})}(),function(){var a=angular.module("autocomplete",[]),b={},c={};b.autocompleteCtrl=["$http","$scope","$mdBottomSheet","$sessionStorage","myappFactory",function(a,b,c,d,e){b.init=function(){b.session=d,angular.isUndefined(b.session.advance_search)&&(b.session.advance_search={select_all:!0,show_cost:!0,show_free:!0,items:[{name:"Thingiverse",selected:!0},{name:"Youmagine",selected:!0},{name:"MyMinifactory",selected:!0},{name:"Cults 3D",selected:!0},{name:"Pinshape",selected:!0},{name:"Turbosquid",selected:!0},{name:"Shapeways",selected:!0},{name:"GrabCAD",selected:!0},{name:"CGTrader",selected:!0},{name:"Threeding",selected:!0}]})},b.querySearch=function(c){var d=c.trim();return d&&d.length>2?(b.isFetching=!0,a.get(e._myapp_link+"/api/index.php/myapp/autocomplete/"+encodeURIComponent(d)).then(function(a){return a.data})):void 0},b.collectionSearch=function(c){var d=c.trim();return d&&d.length>2?(b.isFetching=!0,a.get(e._myapp_link+"/api/index.php/myapp/collection_search/"+encodeURIComponent(d)).then(function(a){return a.data})):void 0},b.search=function(a){var c="";b.session.advance_search.show_cost&&!b.session.advance_search.show_free?c+=" free:0 ":!b.session.advance_search.show_cost&&b.session.advance_search.show_free&&(c+=" free:1 "),angular.forEach(b.session.advance_search.items,function(a,d){(b.session.advance_search.select_all||a.selected)&&(c+=" plataforma:"+a.name)}),window.location="/?search="+encodeURIComponent(a)+"&params="+Base64.encode(c)},b.go_to_collection=function(a){window.location="/collections/"+encodeURIComponent(a)},b.showAdvancedSearch=function(){c.show({templateUrl:"/advanced_search_sheet.html",controller:"ListBottomSheetCtrl"})},b.init()}],b.ListBottomSheetCtrl=["$scope","$mdBottomSheet","$sessionStorage","myappFactory",function(a,b,c,d){a.session=c,a.toggle_all_sites=function(){a.session.advance_search.select_all=!a.session.advance_search.select_all,a.session.advance_search.select_all&&angular.forEach(a.session.advance_search.items,function(a,b){a.selected=!0})},a.toggle_advance_search=function(b){if(a.session.advance_search.select_all)a.session.advance_search.items[b].selected=!0,d.showMessage({msg:"Uncheck 'All repositories' first!"});else if(a.session.advance_search.items[b].selected=!a.session.advance_search.items[b].selected,!a.session.advance_search.items[b].selected){var c=0;angular.forEach(a.session.advance_search.items,function(a,b){a.selected&&++c}),c||(a.session.advance_search.items[b].selected=!0,d.showMessage({msg:"There must be at least 1 respository selected!"}))}},a.free_cost_checked=function(b){var c="cost"==b?!a.session.advance_search.show_cost:!a.session.advance_search.show_free;c?"cost"==b?a.session.advance_search.show_cost=!0:a.session.advance_search.show_free=!0:"cost"==b?a.session.advance_search.show_free?a.session.advance_search.show_cost=!1:d.showMessage({msg:"Free and Price cannot be unchecked!"}):"free"==b&&(a.session.advance_search.show_cost?a.session.advance_search.show_free=!1:d.showMessage({msg:"Free and Price cannot be unchecked!"}))}}],c.myEnter=function(){return function(a,b,c){b.bind("keydown keypress",function(b){13===b.which&&(a.$apply(function(){a.search(a.searchText)}),b.preventDefault())})}},c.onBlur=["$rootScope","$mdUtil","$timeout",function(a,b,c){return{require:"^mdAutocomplete",link:function(d,e,f,g){c(function(){var c=(e.find("input"),e[0],g.blur),h=g.focus;g.blur=function(){c.call(g),b.nextTick(function(){a.$broadcast("autocomplete:blur"),d.$eval(f.mdBlur,{$mdAutocomplete:g})})},g.focus=function(){h.call(g),b.nextTick(function(){a.$broadcast("autocomplete:focus"),d.$eval(f.mdFocus,{$mdAutocomplete:g})})}})}}}],a.controller(b).directive(c)}();

Solution

  • As you certainly found on Google, that error depends on Variable Mangling https://github.com/gruntjs/grunt-contrib-uglify#mangle.

    So, when you mangle $rootScope becomes a and, of course, angular dependency injection cannot resolve it:

    angular
      .module('test', [])
      .run(function($injector) {
        console.log(
          "$rootScope exists?", 
          $injector.has('$rootScope')
        );
        
        try {
          // mangle $rootscope => a
          $injector.get('a');
        } catch(e) { 
          console.log('a exists?', e.message); 
        }
        
      })
    ;
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
    <section ng-app="test"></section>

    There are many ways to manage this issue:

    1. Disable Variable Mangling (not a good way, but is a solution).
    2. Always use Angular DI Annotation

      a. Array notation: someModule.run(["$rootScope", function(a) {}]);

      b. $inject Property: runFn.$inject = ['$rootScope']; function runFn(a) {}; someModule.run(runFn);

    3. Use ngAnnotate that does annotation at build time. I suggest you this option because you don't need to take care about annotation...

    Important thing: Always run your code in angular strict di mode, that gives you the opportunity of control each annotation issue.