28

I'm using grunt to concatenate my .js files for my Angular app.

I've just gone through and tidied up the codebase to follow the conventions discussed here, specifically, grouping my code into small modules that represent features.

However, I'm finding that the order of concatenation appears to break the app, if a module is consumed before it is declared.

eg:

|-- src/
|   |-- app/
|   |   |-- userProfile/
|   |   |   |   userProfile.js
|   |   |   |-- deposits/
|   |   |   |   |-- depositFormCtrl.js

Where:

// userProfile.js
var userProfile = angular.module('userProfile',[])

// depositFormCtrl.js
angular.module('userProfile')
    .controller('DepositFormCtrl', function($scope) {...});

When grunt performs the concatenation, depositFormCtrl.js appears before userProfile.js. This causes the app to throw an error, complaining:

Uncaught Error: No module: userProfile

I see lots of talk about the possibilities of using RequireJS / AMD to manage the load order of the modules. However, often it's stated that this is overkill / not required, as Angular handles this for you.

E.g: Brian Ford of the Angular team mentioned:

My personal take is that RequireJS does too much; the only feature that AngularJS's DI system is really missing is the async loading.

He's also stated elsewhere that he doesn't recommend RequireJS with Angular.

I've also seen mentioned made to using angular-loader.js, as shown on the seed project. However, as I understand it, (there's little official documentation) the loader aims to solve the problem of loading modules out of order, rather than them being referenced before used.

Adding angular-loader.js to my project didn't resolve the issue.

Is there a declaration I should be using that prevents the errors I'm having?

What is the correct way to declare modules & controllers, so that the order files are concatenated doesn't impact the code at runtime?

Community
  • 1
  • 1
Marty Pitt
  • 28,822
  • 36
  • 122
  • 195
  • This is exactly why I DO recommend using RequireJS with Angular. RequireJS let's you quite easily define the load order of all your scripts, and than, when deploying to production, you use r.js (the RequireJS optimiser), which can follow your load order definition file and concatenate the files exactly in order in which you instruct it to do. This is what I'm doing on my projects and it works perfectly. – Stewie Jun 13 '13 at 10:13
  • 1
    I don't know if this fixes your problem (it did mine). But I had to load app.js first (the file with the `var app` declared, then I could wildcard the rest of the files with `grunt-contrib-concat` – chovy Nov 30 '13 at 11:23

4 Answers4

16

One technique I sometimes use is to put declarations at the start of the concatenated file. I do this by putting them in a dedicated file that will be the first one to be picked up by the concatenation utility.

//app/_declarations.js
angular.module('userProfile',[]);

//app/userProfile/userProfile.js
angular.module('userProfile')
   .config(['$routeProvider', function ($router) {...});

//app/userProfile/deposits/depositFormCtrl.js
angular.module('userProfile')
   .controller('DepositFormCtrl', function($scope) {...});

May not be a good fit for all scenarios, but is simple to set up and understand.

ed.
  • 2,696
  • 3
  • 22
  • 25
  • This is the path I ended up going with. It't not fancy, but it works. – Marty Pitt Jun 13 '13 at 12:48
  • If you use Yo, it does the same thing, there is a top-level app.js that defines the module and it always gets processed first. – grettke Jun 13 '13 at 13:57
  • i don't understand. how does adding a declaration control the order when using `grunt-concat-contrib`? – chovy Nov 30 '13 at 11:17
  • 4
    This does work with grunt-concat-contrib. I believe that the key is the _ at the beginning of the declaration filename. Evidently, grunt sorts the files in alpha order. It feels like a hack but it works. – Frederic Fortier Dec 07 '13 at 21:35
  • I just tried this and I believe it is no longer working. '_declarations' doesn't seem to sort it first. '0declarations.js' does work... – kingdango Mar 29 '15 at 20:28
2

I ran into the same issue. I split the concatenation step in my GruntFile.js to two tasks, so essentially my app.js, where my Angular application is defined, is 'prepended' to the intermediate concatenated file (_app.js), and the result is saved with the same name of the intermediate file, resulting into a final '_app.js'

app.js

var TestApp = angular.module('TestApp', [...]);

Sub tasks in my concat section of my GruntFile.js

....
// Concatenate all Angular application JS files into _app.js.  Note the use
// of wildcards to walk the App folder subfolders to pick up all JS files.
        jsCore: {
            src: [                   
                '<%= meta.appPath%>/**/*.js',

                // Do not include app.js, since it needs to be prepended to the final concat file
                '!<%= meta.appPath/app.js',

                // Do not include config.js, since it will need to be tokenized by RM
                '!<%= meta.appPath%>/config.js'
            ],
            dest: '<%= meta.resPath %>/_app.js'
        },

        // Prepend app.js to the beginning of _app.js, and save result as _app.js.  
        // This keeps Angular happy ...
        jsApp: {
            src: [
                '<%= meta.appPath%>/app.js',
                '<%= meta.resPath%>/_app.js'
            ],
            dest: '<%= meta.resPath %>/_app.js'
        }
...

The resultant _app.js file has the source of app.js at the beginning i.e. the declaration of TestApp.

programmerj
  • 1,634
  • 18
  • 29
1

If you split a module in multiples js files and you don't want to manage manually in which order your build should concatenate files then you will need RequireJS.

Personally, I try to avoid splitting a module in several files for several reasons :

  • It can be hard to find all controllers/services/etc.. from a single module
  • If the module is becoming too big, then it probably means you should split in several modules
  • It's cumbersome enough to have to declare manually a list of modules dependencies and a list of injected dependencies for each module. Add to that a list of js dependency files needed by RequireJS and you will spend most of your time declaring boring stuff instead of resolving business problems

However, if you keep the 1 module / 1 file rule, you'll see your index.html growing very quickly. Personally, I don't consider that as a big issue.

Florian F
  • 8,822
  • 4
  • 37
  • 50
1

use gulp and then use gulp-angular-sort

return gulp.src('./build/src/app/**/*.js')
        .pipe(sort())
        .pipe(plug.concat('concat.js'))
        .pipe(gulp.dest('./output/'));
Maccurt
  • 12,655
  • 7
  • 32
  • 43