I am developing an AngularJS application. To ship the code in production, I'm using this Grunt configuration/task:
grunt.registerTask( 'compile', [
'sass:compile', 'copy:compile_assets', 'ngAnnotate', 'concat:compile_js', 'uglify', 'index:compile'
]);
It's really hard to debug, and it's kind of a question to people who already ran into such problems and can point to some direction.
My main module is including those submodules:
angular
.module('controlcenter', [
'ui.router',
'ui.bootstrap',
'templates-app',
'templates-common',
'authentication',
'api',
'reports',
'interceptors',
'controlcenter.websites',
'controlcenter.users',
'controlcenter.campaigns',
'controlcenter.reports',
'controlcenter.login'
])
.run(run);
The error I get is following:
Uncaught Error: [$injector:modulerr] Failed to instantiate module controlcenter due to:
Error: [$injector:modulerr] Failed to instantiate module controlcenter.websites due to:
Error: State 'websites'' is already defined
If I remove the websites module, I get the same error for controlcenter.users.
I am using the ui-router
to handle routing inside the app.
After my build process (for integration testing), everything works just fine:
grunt.registerTask( 'build', [
'clean', 'html2js', 'jshint', 'sass:build',
'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets',
'copy:build_appjs', 'copy:build_vendorjs', 'copy:build_vendorcss', 'index:build', 'karmaconfig',
'karma:continuous'
]);
So maybe ngAnnotate
or or concat
/uglify
are doing weird things here?
UPDATE 1: It has something to do with my configuration of the modules. Here is the code:
angular
.module('controlcenter.websites',
[
'ui.router'
]
)
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider.state( 'websites', {
url: '/websites',
views: {
"main": {
controller: 'WebsitesController',
templateUrl: 'websites/websites.tpl.html'
}
}
});
}
Update 2:
The problem seems concat related.
It takes every JS file and adds it one after another to one, bigger file. All of my modules are at the end. The last module always has the problem with 'state already defined'. So it's not just the order of the modules appending to each other, it's something elsse...
Update 3: I placed my code (I've excluded every Controller-Code and functions, just the scaffold) in a gist. This is the outcome after my compile process, without uglifying it.
Issue:
You have multiple files that contains a config
function to configure your module, like this:
angular
.module('controlcenter.websites', [])
.config(config);
function config() {
// ...
}
The problem is that after you concatenate all files you end up with a big file with multiple declarations of config
. Because of JavaScript's variable hoisting, all declarations are moved to the top and only the very last of them is evaluated, and this one is:
function config($stateProvider) {
$stateProvider.state( 'websites', {
url: '/websites',
views: {
"main": {
controller: 'WebsitesController',
templateUrl: 'websites/overview/websites.tpl.html'
}
},
data : {requiresLogin : true }
});
}
Hence, each time you .config(config)
a module, you are telling Angular to configure your module with that particular configuration function, which means that it executes multiple times and tries to define the state websites
more than once.
Solution:
Wrap each JavaScript file code with a closure. This way you will avoid declaring a variable/function more than once:
(function (angular) {
'use strict';
angular
.module('controlcenter.website.details', ['ui.router'])
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state( 'websiteDetails', {
url: '/websiteDetails/:id',
views: {
"main": {
controller: 'WebsiteDetailsController',
templateUrl: 'websites/details/website.details.tpl.html'
}
},
data : {requiresLogin : true }
})
.state( 'websiteDetails.categories', {
url: '/categories',
views: {
"detailsContent": {
templateUrl: 'websites/details/website.details.categories.tpl.html'
}
},
data : {requiresLogin : true }
})
;
}
})(window.angular);
Edit:
I strongly recommend you wrap your files into closures. However, if you still don't want to do that, you can name your functions according to their respective modules. This way your configuration function for controlcenter.website.details
would become controlcenterWebsiteDetailsConfig
. Another option is to wrap your code during build phase with grunt-wrap.
window.angular
and closures: This is a technique I like to use on my code when I'm going to uglify it. By wrapping your code into a closure and giving it a parameter called angular
with the actual value of window.angular
you are actually creating a variable that can be uglified. This code, for instance:
(function (angular) {
// You could also declare a variable, instead of a closure parameter:
// var angular = window.angular;
angular.module('app', ['controllers']);
angular.module('controllers', []);
// ...
})(window.angular);
Could be easily uglified to this (notice that every reference to angular
is replaced by a
):
!function(a){a.module("app",["controllers"]),a.module("controllers",[])}(window.angular);
On the other side, an unwrapped code snippet like this:
angular.module('app', ['controllers']);
angular.module('controllers', []);
Would become:
angular.module("app",["controllers"]),angular.module("controllers",[]);
For more on closures, check this post and this post.
If you check it in the concatenated file, do you have the states defined twice? Can it be that you are copying the files twice? Check the temporary folders from where you are taking the files (also in grunt config, what you are copying and what you are deleting...).
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