从 AngularJS 模块定义说起
来源:互联网 发布:南京浦口行知中学 编辑:程序博客网 时间:2024/05/17 01:49
最近的项目使用了AngularJS作为主要前端框架,在使用模块时遇到一些有意思的问题,本文围绕模块的定义与加载的话题,小结一下我的体会。
首先,定义一个Angular模块(module)很简单,直接使用angular模块的module方法即可,如:
var myApp = angular.module('myApp', []);
值得注意的是,module函数的第二个参数定义了该模块所依赖的模块。在首次定义一个模块时,必须同时指定模块名和依赖,尽管该模块可能没有依赖(使用空数组)。如果不指定依赖,则Angular会试图定位已经定义好的名称为myApp的模块,这通常会导致模块未定义错误。如果依赖不为空,则要保证其依赖的模块已经加载。这里面就有个加载顺序的问题。
最简单的情况下,我把使用模块所在的JS文件全部按顺序列在HTML的script元素中,只要保证模块之间的依赖关系正确即可,如:
<!doctype html><html lang="en" ng-app="myApp"><head> <meta charset="utf-8"> <title>My HTML File</title> <link rel="stylesheet" href="css/app.css"> <script src="/scripts/myApp.js"></script> <script src="/scripts/app.js"></script></head><body><div>Hello Angular !</div></body></html>如果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的自动初始化发生在两个时机:
- 响应DOMContentLoaded事件(页面文档完全加载并解析完毕后会触发该事件,不会等待图片、样式文件)
- document.readyState的值为'complete'。(兼容IE8,页面完全加载,相当于load事件触发)
这时,Angular会调用angularInit方法进行初始化,首先查找ngApp directive,该directive指定了应用程序的根结点,通常位于初始页面的<html>标记上。当然,ngApp如果位于某个子结点(如div元素),则可以将Angular应用程序限制在DOM树的一部分上。如果Angular找到了ngApp,则会调用bootstrap方法开始启动过程,主要工作如下:
- 加载ngApp指定的模块
- 创建应用程序的依赖注入对象(injector)。此时$compile,$rootScope会被注入,用于后续的compile过程。有关依赖注入过程有后文。
- 以ngApp为根结点,compile整个DOM树。有关compile过程,在后续文章中详谈。
<script> angular.module('myApp', []) .controller('MyController', ['$scope', function ($scope) { }]); angular.element(document).ready(function() { angular.bootstrap(document, ['myApp']); });</script>
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']]); });});});
有几点需要注意:
- 使用手动启动时,不要再指定ngApp directive。
- 在使用延迟启动时需要指定ngApp或者手动调用bootstrap方法,否则会报错:resumeBootstrap不是一个函数。原因是指定ngApp会内部调用bootstrap方法,而resumeBootstrap方法定义于bootstrap方法内部,如果没有指定ngApp,又没有手动调用bootstrap,则resumeBootstrap未定义。
- 如果在HTML指定了多个Angular实例(如多次加载angular.js),Angular只会启动第一个加载成功的实例启动。如果不延迟启动,则angular在遇到ng-app directive时开始初始化,并加载相应模块。如果需要加载额外的模块而需要延迟启动,则需定义window.name为NG_DEFER_BOOTSTRAP!,然后在所有模块加载完毕后,调用resumeBootstrap方法,继续启动,并加载指定模块。angular在自动加载时会调用bootstrap方法,如果没有指定ng-app,则需手动调用bootstrap方法。由于resumeBootstrap方法定义于bootstrap方法内部,因此如果bootstrap没有调用,就会报resumeBootstrap is not a function的错误。
(本文参考了AngularJS官方文档及源代码)
- 从 AngularJS 模块定义说起
- 从 AngularJS 模块定义说起
- 理解AngularJS——从WPF说起
- 从用 AngularJS 开发 PC 客户端说起
- AngularJS 模块定义应用 angular.module
- AngularJS学习笔记——使用模块定义AngularJS组件
- IT思考——从SOA的定义说起
- 从linux内核启动说起~模块篇(四)
- 从一段.html代码说起谈谈AngularJs中的双向数据绑定
- AngularJS教程 AngularJS从0到1——AngularJS模块
- AngularJS 模块
- AngularJS-模块
- AngularJS 模块
- AngularJS 模块
- AngularJS 模块
- AngularJS 模块
- AngularJS 模块
- AngularJS模块
- mysqlbinlog 读取多个文件
- Android待机及延时网路关闭的修改
- 实验说明Servlet的生命周期
- YTU 2391: 求素数
- linux离线搭建Python环境及安装numpy、pandas
- 从 AngularJS 模块定义说起
- HDOJ 2159-FATE【完全背包】
- 20160314-01-认识 Android 中的 Activity 组件(完结)
- 华为手机(P6s)logcat无法正常打印输出信息解决方案
- 5.1数据验证技术
- 2016+蓝桥杯算法训练——最小乘积(基本型)
- Java反射系列之——什么是反射?
- 线段树(区间更新)codeforces 292E Copying Data
- 简单工厂