Using Angular.js with Require.js
I got used to idea of AMD quite long time ago. That time Require.js was best (and probably only one) good implementation that supports it. It worked great for me while I was involved into Backbone.js development. So, once I jumped in to Angular.js my first wish was reuse the same experience as previously.
There was a few difficulties with that.
Why to use Require.js with Angular.js?
Some people argue about rationality of using Require and Angular together. Indeed, Angular has it’s own module system, dependency resolve system etc. I agree with that, but still my point is: Require.js comes with very handy add-on, called r.js
– it’s code minimizer and optimizer.
Having grunt
build system (which is de-facto standard for JS applications) you can easily integrate with your deployment scenarios.
Project organization
Before deep dive, I want to share you some simple ideas of client side code organization that I think makes sense:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/
+ - build
|
+ - components
|
+ - js
|
+ - controllers
|
+ - directives
|
+ - services
|
+ - app.js
|
main.js
|
config.js
- build – results of build .js and .css is placed here (these files are used in production).
- components – bower components are configured to be placed in this folder.
- js – source code of application, divided to
contollers
, directives
and services
.
main.js
Require.js main file, that contains configuration and main entry.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require.config({
paths: {
'angular' : '../components/angular/angular',
'ngResource': '../components/angular-resource/angular-resource',
'ngCookies': '../components/angular-cookies/angular-cookies',
'ngProgressLite': '../components/ngprogress-lite/ngprogress-lite'
},
shim: {
ngResource: {
deps: ['angular'],
exports: 'angular'
},
ngCookies: {
deps: ['angular'],
exports: 'angular'
},
ngProgress: {
deps: ['angular'],
exports: 'angular'
},
angular: {
exports : 'angular'
}
},
baseUrl: '/js'
});
require(['app'], function (app) {
app.init();
});
app.js
Instance of application, where all Angular.js bootstraping taking place.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
define(function (require) {
'use strict';
var angular = require('angular');
var services = require('./services/services');
var controllers = require('./controllers/controllers');
var directives = require('./directives/directives');
var app = angular.module('likeastore', ['services', 'controllers', 'directives']);
app.init = function () {
angular.bootstrap(document, ['likeastore']);
};
app.config(['$routeProvider', '$locationProvider', '$httpProvider',
function ($routeProvider, $locationProvider, $httpProvider) {
$httpProvider.responseInterceptors.push('httpInterceptor');
$routeProvider
.when('/', { templateUrl: 'partials/dashboard', controller: 'dashboardController' })
.when('/inbox', { templateUrl: 'partials/dashboard', controller: 'dashboardController' })
.when('/facebook', { templateUrl: 'partials/dashboard', controller: 'facebookController' })
.when('/github', { templateUrl: 'partials/dashboard', controller: 'githubController' })
.when('/twitter', { templateUrl: 'partials/dashboard', controller: 'twitterController' })
.when('/stackoverflow', { templateUrl: 'partials/dashboard', controller: 'stackoverflowController' })
.when('/search', { templateUrl: 'partials/dashboard', controller: 'searchController' })
.when('/settings', { templateUrl: 'partials/settings', controller: 'settingsController' })
.when('/ooops', { templateUrl: 'partials/dashboard', controller: 'errorController' })
.otherwise({ redirectTo: '/' });
$locationProvider.html5Mode(true);
}
]);
app.run(function ($window, auth, user) {
auth.setAuthorizationHeaders();
user.initialize();
});
return app;
});
config.js
Configuration object that contains application settings, like itemsPerPage
, logoutTimeout
etc.
1
2
3
4
5
6
7
8
9
define(function (require) {
'use strict';
return {
dashboard: {
limit: 30
}
};
});
Master HTML
The first noticable difference is the way master HTML file is organized. You are no longer define application with directive <html lang="en" ng-app>
, but instead using manual booting of application.
1
2
3
4
5
6
7
8
9
10
11
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div class="main-viewer" ng-view></div>
<script type="text/javascript" src="/components/requirejs/require.js" data-main="/js/main.js"></script>
</body>
</html>
Booting application
Once master HTML is loaded, Require.js will execute main.js
file. This is were the action should be taken to bootstrap Angular.js application.
Take a look at main.js
file from above, here we start application bootstrapping:
1
2
3
require(['app'], function (app) {
app.init();
});
The init()
function of application,
1
2
3
4
5
var app = angular.module('likeastore', ['services', 'controllers', 'directives']);
app.init = function () {
angular.bootstrap(document, ['likeastore']);
};
By doing this, you should be able to start the application and make sure that all modules, controllers and directives are loaded without any issues.
Build configuration
Next tricky part is to configure application for production. There is a great blog post of using r.js
to optimize the project.
In short, you need to add grunt-contrib-requirejs grunt task and configure it.
The problem with Angular.js optimization is that it using dependency injection mechanism, which resolves the services to inject by it’s name. If optimizer would change the name of function parameters, when application would not work. Fortunately, there is workaround for that.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
requirejs: {
js: {
options: {
uglify2: {
mangle: false
},
baseUrl: "public/js",
mainConfigFile: "public/js/main.js",
name: 'main',
out: "public/build/main.js",
optimize: 'uglify2'
}
}
},
You have to use uglify2
optimizer, that has an option mangle that have to set to false
. In this case, uglify2
will do full optimization and minification of javascript code, without ruing functions parameters names, with is vital for Angular.js.
I got used to idea of AMD quite long time ago. That time Require.js was best (and probably only one) good implementation that supports it. It worked great for me while I was involved into Backbone.js development. So, once I jumped in to Angular.js my first wish was reuse the same experience as previously.
There was a few difficulties with that.
Why to use Require.js with Angular.js?
Some people argue about rationality of using Require and Angular together. Indeed, Angular has it’s own module system, dependency resolve system etc. I agree with that, but still my point is: Require.js comes with very handy add-on, called r.js
– it’s code minimizer and optimizer.
Having grunt
build system (which is de-facto standard for JS applications) you can easily integrate with your deployment scenarios.
Project organization
Before deep dive, I want to share you some simple ideas of client side code organization that I think makes sense:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
- build – results of build .js and .css is placed here (these files are used in production).
- components – bower components are configured to be placed in this folder.
- js – source code of application, divided to
contollers
,directives
andservices
.
main.js
Require.js main file, that contains configuration and main entry.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
app.js
Instance of application, where all Angular.js bootstraping taking place.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
config.js
Configuration object that contains application settings, like itemsPerPage
, logoutTimeout
etc.
1 2 3 4 5 6 7 8 9 |
|
Master HTML
The first noticable difference is the way master HTML file is organized. You are no longer define application with directive <html lang="en" ng-app>
, but instead using manual booting of application.
1 2 3 4 5 6 7 8 9 10 11 |
|
Booting application
Once master HTML is loaded, Require.js will execute main.js
file. This is were the action should be taken to bootstrap Angular.js application.
Take a look at main.js
file from above, here we start application bootstrapping:
1 2 3 |
|
The init()
function of application,
1 2 3 4 5 |
|
By doing this, you should be able to start the application and make sure that all modules, controllers and directives are loaded without any issues.
Build configuration
Next tricky part is to configure application for production. There is a great blog post of using r.js
to optimize the project.
In short, you need to add grunt-contrib-requirejs grunt task and configure it.
The problem with Angular.js optimization is that it using dependency injection mechanism, which resolves the services to inject by it’s name. If optimizer would change the name of function parameters, when application would not work. Fortunately, there is workaround for that.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
You have to use uglify2
optimizer, that has an option mangle that have to set to false
. In this case, uglify2
will do full optimization and minification of javascript code, without ruing functions parameters names, with is vital for Angular.js.