There has been a lot written about code organization in AngularJS. Of course, there are many schools of thought. Here are the best articles I've seen on them:
All of those have some great ideas for structuring your AngularJS code. My problem was with actually using them in two environments:
- Raw development code
- Minified development/production code in PhoneGap
When developing, it's inconventient to run Grunt to concat all my code just to get an idea of how things look in the browser. So, I need a code structure/process that lets me see changes live in the browser while also working when minified.
For a while, I was doing something like this where each directive/controller/service was actually in a separate file (way too busy if all kept in one file):
var app = angular.module('App', []);
app.directive('myGreatDirective', function(){
return {
//...
}
});
app.directive('myBetterDirective', function(){
return {
//...
}
});
app.controller('SomeController', [ '$scope', function($scope) {
}]);
app.controller('AnotherController', [ '$scope', function($scope) {
}]);
app.factory('BestService', [ function() {
}]);
That works fine.... Until you need to inject a service into your app.config. Then you're hosed because the service doesn't exist yet because it's further down in the code. Ugh.
Next, I tried using distinct modules for directives, controllers, and services. Then, I'd inject them into the main module. (Again, using separate files for each individual directive, controller, and service). Such as :
// One Directive File
angular.module('app.directives', [])
.directive('myGreatDirective', function(){
return {
//...
}
});
// Another Directive File
angular.module('app.directives', [])
.directive('myBetterDirective', function(){
return {
//...
}
});
...
...
angular.module('MyGreatApp', [ 'app.directives', 'app.services', 'app.controllers' ]);
This seemed to work... But then I realized each directive file was replacing the previous directive module. So, in the end only one directive ever existed.
Finally, I figured out how to have individual files for each directive (or service or controller) without each replacing the previous module.
Note how only the very first module using the []
.
// File : /js/directives/mainDirective.js
angular.module('app.directives',[]);
// File : /js/directives/myGreatDirective.js
angular.module('app.directives')
.directive('myGreatDirective', function(){
return {
//...
}
});
// File : /js/directives/myBetterDirective.js
angular.module('app.directives')
.directive('myBetterDirective', function(){
return {
//...
}
});
...
...
// File : /js/app.js
angular.module('MyGreatApp', [ 'app.directives', 'app.services', 'app.controllers' ]);
Now, in my original development source, my index.html looks like this:
<head>
<meta charset="utf-8">
<title>My Great App</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" href="css/app.css">
<script src="/js/angular/angular.min.js"></script>
<script src="/js/angular/angular-animate.min.js"></script>
<script src="/js/angular/angular-route.min.js"></script>
<script src="/js/angular/angular-touch.min.js"></script>
<script src="/js/angular/angular-sanitize.min.js"></script>
<!-- GRUNTSTART -->
<script src="/js/directives/mainDirective.js"></script>
<script src="/js/directives/myGreatDirective.js"></script>
<script src="/js/directives/myBetterDirective.js"></script>
<script src="/js/controllers/mainController.js"></script>
<script src="/js/controllers/myGreatController.js"></script>
<script src="/js/controllers/myBetterController.js"></script>
<script src="/js/services/mainService.js"></script>
<script src="/js/services/myGreatService.js"></script>
<script src="/js/services/myBetterService.js"></script>
<script src="/js/app.js"></script>
<!-- GRUNTEND -->
</head>
<html ng-app="MyGreatApp">
</html>
This allows me to develop locally and see changes instantly. Then, when I run Grunt, I use a contact and replace method to replace all of those individual files with a single concatenated file.
concat: {
js: {
src: [
'src/js/directives/mainDirective.js',
'src/js/directives/**.js',
'src/js/controllers/mainController.js',
'src/js/controllers/**.js',
'src/js/services/mainService.js',
'src/js/services/**.js',
'src/js/app.js'
],
dest: 'www/js/app.concat.js'
}
},
replace: {
html: {
src: ['src/index.html'],
dest: 'www/index.html',
replacements: [
{
from: /<!-- GRUNTSTART -->[\s\S]*?<!-- GRUNTEND -->/g,
to: '<script src="js/app.concat.js"></script>'
}
]
}
}
And ultimately end up with this:
<head>
<meta charset="utf-8">
<title>My Great App</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" href="css/app.css">
<script src="/js/angular/angular.min.js"></script>
<script src="/js/angular/angular-animate.min.js"></script>
<script src="/js/angular/angular-route.min.js"></script>
<script src="/js/angular/angular-touch.min.js"></script>
<script src="/js/angular/angular-sanitize.min.js"></script>
<script src="js/app.concat.js"></script>
</head>
<html ng-app="MyGreatApp">
</html>
NOTE : There's a lot more to the Grunt process than this, but I just showed the relevant chunks. Some of this is based on the work of Sergiator