First off I want to thank Joel Hooks of the DFW Area AngularJS Meetup Group for suggesting this topic and providing the title.javascript
「Modules provide an excellent mechanism for cleanly dividing up our code into functional areas. Angular allows us to use modules in many ways. Let’s take a look at modules and some of the approaches we can leverage to produce cleaner more manageable code.」 – Joel Hookshtml
So with that, let’s get started.
AngularJS modules are the core means by how you define the components in your app. Besides defining your components, modules provide a way to indicate the dependencies your components require and they help you organize your components to help you write modular code that can be re-used across applications.java
As long as I have been developing with AngularJS there has always been the great best practices debate over how to structure your application. Do you use a package by feature or package by layer approach?git
Both have their advantages and disadvantages so let’s take a quick look at each before we get into how to implement each using AngularJS.angularjs
Package by Feature became popular in the Java development camp a few years back. The main tenet is that by keeping all the source code related to a specific feature in a single package, it was easier to develop and promote modularity across your source code. Entire features could be lifted from applications and re-used in others by just taking the source package, there was no need to bring in other packages since all the code for the feature was in one place.github
Package-by-feature uses packages to reflect the feature set. It places all items related to a single feature (and only that feature) into a single directory. This results in packages with high cohesion and high modularity, and with minimal coupling between packages. Items that work closely together are placed next to each other. They aren’t spread out all over the application.web
A lot of developers feel that by using Package by Feature, development on large projects is also easier since all the code you need to deal with is in one place and as the project grows following the pattern keeps everything well organized.express
A good example of Package by Feature, is the angular-sprout seed project. All of the JavaScript files for a particular feature are included in the same directory. So, if we have three different views in our application; User, Movie, and Rating, we’ll have three directories in our source code. Each folder would include the source for the controllers, services, models and templates for the specific feature.bootstrap
If you come from a object-oriented development background, you probably are more familiar with the Package by Layer approach. The basic tenets of object-oriented design is to design by layers; data access, business logic, business entities, and user interface. This way your code is organized in layers that communicate with each other via specific interfaces. Depending on how rigid your design and coding rules are, layers only interacted with adjacent layers and never called across boundaries.app
In package-by-layer, the highest level packages reflect the various application 「layers」, instead of features. Each feature has its implementation spread out over multiple directories, over what might be loosely called 「implementation categories」. Each directory contains items that usually aren’t closely related to each other. This results in packages with low cohesion and low modularity, with high coupling between packages.
Package by Layer works great when it comes to code that implements cross cutting concerns, however when you are developing features that span multiple layers development becomes harder since you have to deal with source files in various places. Package by Layer also promotes cross package dependencies which some feel are a bad thing.
A good example of Package by Layer, is the angular-seed project. All of the JavaScript files for a particular type of AngularJS component are included in the same directory or file. So, our application would be divided across the following source folders; controllers, directives, services, models, filter, etc.
So, you’re probably wondering, 「Why should I care, I write web apps?」 Actually how you partition your code has a lot to do with how you organize your project source code and how much easier it is to maintain as your project grows.
Depending on the size of your project your are going to have to decide on how you want your code structured. Do you keep everything in a single JavaScript file, do you break your files based on layers, do you break your files based on features, or do you structure your code directories by features and structure your source files by layers?
For me, if I have a very small or tiny project that does one thing, I might keep all of my source code in a single file using single module. Below is a picture of the project’s directory structure. Notice we have a single file, app.js which holds our source code and our index.html file which loads and bootstraps our app.
Below is the app.js file, I’ve defined a single module called myApp and used the controller and directive definition methods to define my controller and directive used by my app. Everything is in one place and easy to maintain, which is all you need when writing a very simple application.
angular.module('myApp', [])
// register the controller for the app
.controller('myController',['$scope', function($scope){
$scope.format = 'M/d/yy h:mm:ss a';
}])
// Register the 'myCurrentTime' directive factory method.
// We inject $timeout and dateFilter service since the factory method is DI.
.directive('myCurrentTime', function($timeout, dateFilter) {
// return the directive link function. (compile function not needed)
return function(scope, element, attrs) {
var format, // date format
timeoutId; // timeoutId, so that we can cancel the time updates
// used to update the UI
function updateTime() {
element.text(dateFilter(new Date(), format));
}
// watch the expression, and update the UI on change.
scope.$watch(attrs.myCurrentTime, function(value) {
format = value;
updateTime();
});
// schedule update in one second
function updateLater() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
updateLater(); // schedule another update
}, 1000);
}
// listen on DOM destroy (removal) event, and cancel the next UI update
// to prevent updating time after the DOM element was removed.
element.bind('$destroy', function() {
$timeout.cancel(timeoutId);
});
updateLater(); // kick off the UI update process.
}
});
Below is the index.html file that instantiates the app and displays an input field that allows you to modify the time format that is used by the myCurrentTime directive to display the current date and time to the user.
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title></title>
<script src="lib/angular/angular.js" type="text/javascript"></script>
<script src="app/app.js" type="text/javascript"></script>
</head>
<body>
<div ng-controller="myController">
<input type="text" ng-model="format"/>
<div>Time format is: {{format}}</div>
<div>Current time is: <span my-current-time="format"></span></div>
</div>
</body>
</html>
As your project starts to get a little more complicated you might want to start organizing your code into multiple modules, but still keep it in a single file. Below is a picture of the project’s directory structure. Notice we still have a single file, app.js which holds our source code and our index.html file which loads and bootstraps our app.
Below is the app.js file, I’ve defined multiple modules one for the controllers, a second for the directives and a third for the app. I have again used the controller and directive definition methods to define my controller and directive used by my app, but now they are defined on specific modules defined for each type of AngularJS component type. I have also added dependencies to the application’s module definition so everything get injected properly. Everything is in one place and easy to maintain, but is ready for when you need to separate your code into multiple files when they get too big to maintain.
angular.module('myControllers', [])
// register the controller for the app
.controller('myController',['$scope', function($scope){
$scope.format = 'M/d/yy h:mm:ss a';
}]);
angular.module('myDirectives', [])
// Register the 'myCurrentTime' directive factory method.
// We inject $timeout and dateFilter service since the factory method is DI.
.directive('myCurrentTime', function($timeout, dateFilter) {
// return the directive link function. (compile function not needed)
return function(scope, element, attrs) {
var format, // date format
timeoutId; // timeoutId, so that we can cancel the time updates
// used to update the UI
function updateTime() {
element.text(dateFilter(new Date(), format));
}
// watch the expression, and update the UI on change.
scope.$watch(attrs.myCurrentTime, function(value) {
format = value;
updateTime();
});
// schedule update in one second
function updateLater() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
updateLater(); // schedule another update
}, 1000);
}
// listen on DOM destroy (removal) event, and cancel the next UI update
// to prevent updating time after the DOM element was removed.
element.bind('$destroy', function() {
$timeout.cancel(timeoutId);
});
updateLater(); // kick off the UI update process.
}
});
angular.module('myApp', ['myControllers', 'myDirectives']);
Notice that the index.html file used in our example below hasn’t changed.
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title></title>
<script src="lib/angular/angular.js" type="text/javascript"></script>
<script src="app/app.js" type="text/javascript"></script>
</head>
<body>
<div ng-controller="myController">
<input type="text" ng-model="format"/>
<div>Time format is: {{format}}</div>
<div>Current time is: <span my-current-time="format"></span></div>
</div>
</body>
</html>
As your projects get larger and more complex, you might want to follow the angular-seed approach and split your source code across layers. In the sample code, I’ve broken the code out across multiple source files by layer, there is one file for the app, one for the controllers and one for the directives. Depending on how you want to structure your code, you can use a single module or multiple modules. In this example I use a single module that is assigned to a variable that is referenced by the other source files.
Our source code structure has changed. Now, instead of just an app directory there is now a controllers and directives directory.
Below is the contents of the app.js file. All we are doing is declaring the module that will be used by app:
var myModule = angular.module('myApp', []);
Again, I am using a single module across my entire app so I am assigning it to variable, which I will reference in the other source files to define my controller and directive.
Below is the contents of the controllers.js file. Here we are using the myModule variable to define the controller.
// register the controller for the app
myModule.controller('myController',['$scope', function($scope){
$scope.format = 'M/d/yy h:mm:ss a';
}]);
Below is the contents of the directives.js file. Again we are using the myModule variable to define the directive.
myModule.directive('myCurrentTime', function($timeout, dateFilter) {
// return the directive link function. (compile function not needed)
return function(scope, element, attrs) {
var format, // date format
timeoutId; // timeoutId, so that we can cancel the time updates
// used to update the UI
function updateTime() {
element.text(dateFilter(new Date(), format));
}
// watch the expression, and update the UI on change.
scope.$watch(attrs.myCurrentTime, function(value) {
format = value;
updateTime();
});
// schedule update in one second
function updateLater() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
updateLater(); // schedule another update
}, 1000);
}
// listen on DOM destroy (removal) event, and cancel the next UI update
// to prevent updating time after the DOM element was removed.
element.bind('$destroy', function() {
$timeout.cancel(timeoutId);
});
updateLater(); // kick off the UI update process.
}
});
Finally below is the index.html file used to load and instantiate the app. Notice how we now need to include the new source files in order to load all of our code.
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title></title>
<script src="lib/angular/angular.js" type="text/javascript"></script>
<script src="app/app.js" type="text/javascript"></script>
<script src="controllers/controllers.js" type="text/javascript"></script>
<script src="directives/directives.js" type="text/javascript"></script>
</head>
<body>
<div ng-controller="myController">
<input type="text" ng-model="format"/>
<div>Time format is: {{format}}</div>
<div>Current time is: <span my-current-time="format"></span></div>
</div>
</body>
</html>
As your application starts to grow and include more and more features, it is best to re-structure your source code’s directory structure by using the Package by Feature pattern and then use the Package by Layer pattern for your source files that relate to the feature. Below is a picture of the Large project’s structure. Now, we have created the folder structure based on the features in the app and the source files are based on the different AngularJS layers.
Below is the contents of the app.js file. We are again defining a module for our app. Notice that we have included the names of the other modules which define our features.
angular.module('myApp', ['my-view-controller', 'time']);
Below is contents of the my-view-controller.js app, which defines our controller for the feature.
// register the controller for the app
angular.module('my-view-controller', []).controller('myController',['$scope', function($scope){
$scope.format = 'M/d/yy h:mm:ss a';
}]);
Since we are using the Package by Feature pattern to structure our code, we have now created a template that will be used to instantiate our controller. The reason for breaking out the HTML for the feature is because we want to have a fully re-usable package that we can pick up and drop into another application without having to drag other parts of the source application into your new app.
Below is the contents of my-view-partial.html:
<div ng-controller="myController">
<input type="text" ng-model="format"/>
<div>Time format is: {{format}}</div>
<div>Current time is: <span my-current-time="format"></span></div>
</div>
We have also moved our directive to it’s own feature directory giving us another module that we can reuse in other applications.
// Register the 'myCurrentTime' directive factory method.
// We inject $timeout and dateFilter service since the factory method is DI.
angular.module('time', []).directive('myCurrentTime', function($timeout, dateFilter) {
// return the directive link function. (compile function not needed)
return function(scope, element, attrs) {
var format, // date format
timeoutId; // timeoutId, so that we can cancel the time updates
// used to update the UI
function updateTime() {
element.text(dateFilter(new Date(), format));
}
// watch the expression, and update the UI on change.
scope.$watch(attrs.myCurrentTime, function(value) {
format = value;
updateTime();
});
// schedule update in one second
function updateLater() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
updateLater(); // schedule another update
}, 1000);
}
// listen on DOM destroy (removal) event, and cancel the next UI update
// to prevent updating time after the DOM element was removed.
element.bind('$destroy', function() {
$timeout.cancel(timeoutId);
});
updateLater(); // kick off the UI update process.
}
});
Finally, we have our index.html file which loads our source and instantiates our app. Notice that we have a script tag for each of our source files and that we are using the ng-include directive to load our view’s template.
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title></title>
<script src="lib/angular/angular.js" type="text/javascript"></script>
<script src="app/app.js" type="text/javascript"></script>
<script src="myView/my-view-controller.js" type="text/javascript"></script>
<script src="time/time-directive.js" type="text/javascript"></script>
</head>
<body>
<div ng-include src="'myView/my-view-partial.html'"></div>
</body>
</html>
https://github.com/lavinjj/angularjs-modules-for-great-justice