最近的项目使用了AngularJS作为主要前端框架,在使用模块时遇到一些有意思的问题,本文围绕模块的定义与加载的话题,小结一下我的体会。
首先,定义一个Angular模块(module)很简单,直接使用angular模块的module方法即可,如:
var myApp = angular.module('myApp', []);
值得注意的是,module函数的第二个参数定义了该模块所依赖的模块。在首次定义一个模块时,必须同时指定模块名和依赖,尽管该模块可能没有依赖(使用空数组)。如果不指定依赖,则Angular会试图定位已经定义好的名称为myApp的模块,这通常会导致模块未定义错误。如果依赖不为空,则要保证其依赖的模块已经加载。这里面就有个加载顺序的问题。
最简单的情况下,我把使用模块所在的JS文件全部按顺序列在HTML的script元素中,只要保证模块之间的依赖关系正确即可,如:
My HTML File
Hello Angular !
如果app.js中的模块依赖模块myApp,则需先将定义它的myApp.js加载进来。在项目实践中,这种方法显然是不实际的,你不能在一开始把所有的JS文件全部加载进来。通常会使用一些延迟/异步加载机制,如使用RequireJS。
使用RequireJS定义的Angular模块可以是这样:
define(['angular'], function(angular) {
angular.module('myApp', [])
.controller('MyController', ['$scope', function ($scope) {
//define scope data
}]);
});
其中define函数加载了angular.js,并取得angular模块,然后定义了myApp模块。如果myApp模块(或者MyController)依赖于其他模块(或组件),则需要告诉RequireJS在需要的时候加载:
define(['angular',
'./scripts/anotherApp',
'./scripts/service/utility'
], function(angular) {
angular.module('myApp', ['newApp'])
.controller('MyController', ['$scope', 'utils', function ($scope, utils) {
//define scope data
}]);
});
这里,myApp依赖定义于anotherApp.js的newApp模块,并且需要定义于utility.js的工厂服务utils。这里存在的一个陷阱是,依赖的两个JS文件不一定会按照代码中声明的顺序加载,即utility.js可能先于anotherApp.js加载。因此在使用requireJS加载依赖时,要注意这些依赖本身之间的相互关系,不能指望requires按某种顺序加载这些依赖。否则,会导致一些概率性出现的问题,不易调试。
另外一点,如果在首次定义模块A时使用了空数组作为依赖,然后在某次使用A模块的过程中,误将依赖数组又传递一次,那么A模块原来的定义就会被覆盖,并且原来A模块定义的controller,service等组件也将不存在,这样也会导致难以调试的问题。因此在定义和使用Angular模块时不能大意。
-------------------------------------------------------------------------------------------------------------
2016/03/19 Update
谈到模块加载这个话题,就不能不提及Angular的启动(bootstrap)机制。简单来说,Angular的启动分为手动和自动两种。在前面的例子中,采用的就是自动的方式:通过内置的directive ngApp 来指定启动时加载的模块。
根据官方文档(这里),Angular的自动初始化发生在两个时机:
这时,Angular会调用angularInit方法进行初始化,首先查找ngApp directive,该directive指定了应用程序的根结点,通常位于初始页面的标记上。当然,ngApp如果位于某个子结点(如div元素),则可以将Angular应用程序限制在DOM树的一部分上。如果Angular找到了ngApp,则会调用bootstrap方法开始启动过程,主要工作如下:
window.name = "NG_DEFER_BOOTSTRAP!";
requirejs.config({
baseUrl: '.',
paths: {
app: 'app/scripts/app',
jquery: 'common/lib/jquery/dist/jquery',
angular: 'common/lib/angular/angular'
},
shim: {
angular : {
exports : 'angular'
}
}
});
require(['angular', 'jquery'], function(){
require(['app'], function(app) {
angular.element().ready(function() {
angular.resumeBootstrap([app['name']]);
});
});
});
有几点需要注意:
(本文参考了AngularJS官方文档及源代码)