AngularJS学习:Angular的模块

原文地址:https://code.angularjs.org/1.3.0-beta.14/docs/guide/module。

模块是什么?

你可以把模块看成是一个你的app的不同部分(controller,services,filters,directives,等等)的一个容器。

大多数应用有一个main方法,把应用的不同部分实例化和装配在一起。

Angular app没有main方法。取而代之的是模块声明地指定了一个应用应该如何被启动。这种方法有几个优点:

  • 声明过程很容易被理解。
  • 你可以把代码打包成可重用的模块。
  • 因为模块的延迟执行,模块可以以任何顺序被加载(甚至是平行)。
  • 单元测试仅仅加载相关的模块,这会使得它们可以很快地进行。
  • 端到段地测试可以使模块去复写配置。

基础

我有点着急。我怎么能让一个Hello World模块工作呢?

Edit in Plunker

index.htmlscript.js

<!doctype html>
<html >
<head>
  <meta charset="UTF-8">
  <title>Example - example-example106-production</title>
  
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.13/angular.min.js"></script>
  <script src="script.js"></script>
  
</head>
<body ng-app="myApp">
    <div>
    {{ 'World' | greet }}
  </div>
</body>
</html>

(注:这个地方原本的index.html展示的代码不完整,我将它补全了。)

重要的事情需要注意:

  • 这个Module API。(注:实际上这个手册页面里面没有找到这个module方法的说明,在http://www.tutorialsteacher.com/angularjs/modules-in-angularjs找到一些说明:“In the above example, the angular.module() method creates an application module, where the first parameter is a module name which is same as specified by ng-app directive.The second parameter is an array of other dependent modules []. In the above example we are passing an empty array because there is no dependency.”,即两个参数,一个是模块的名字,这个名字要和ng-app指示符里面所写的一样;一个是这个模块所依赖的模块数组)。
  • 在<html ng-app="myApp">对myApp模块的引用。这就是用你的模块去引导app的事物。
  • 在angular.module('myApp', [])的空数组。这个数组是myApp依赖的一个模块列表。

推荐安装

尽管上面的例子很简单,但是它可以被扩展成一个大的应用程序。不过我们推荐你把你的应用拆成多个模块,就像这样:

  • 每个功能一个模块。
  • 每一个可重用的组件一个模块。
  • 和一个应用程序层面的模块,这个模块依赖于上面模块,并且包含一些初始化代码。

我们已经written a document (编写了一个文档),关于我们在google是如何组织大型的app的。

以上只是一个建议。根据你的需求去进行剪裁。

(注:下面的原文摘抄有点问题,更多请参考Plunker)

Edit in Plunker

index.htmlscript.js

<div ng-controller"XmplController">
  {{ greeting }}!
</div>

模块加载 & 依赖

一个模块是一个集合,在自启过程中被应用到应用程序上的配置和运行块的集合。最简单的模式就是包含两类块的集合:

  1. Configuration blocks(配置块) - 在provider注册和配置阶段被执行。只有providers和constants可以被注入到配置块中。这一点防止了services在它们被完全配置之前意外地被初始化。
  2. Run blocks(运行块) - 在注入器被创建后被执行,被用于去启动应用。只有实例和constants才能被注入到运行块中。这是为了防止在应用运行进行期间更多的系统配置。
angular.module('myModule', []).
  config(function(injectables) { // provider-injector
    // This is an example of config block.
    // You can have as many of these as you want.
    // You can only inject Providers (not instances)
    // into config blocks.
  }).
  run(function(injectables) { // instance-injector
    // This is an example of a run block.
    // You can have as many of these as you want.
    // You can only inject instances (not Providers)
    // into run blocks
  });

配置块

模块上有很多方便的方法,可以等同于config块的作用。例如:

angular.module('myModule', []).
  value('a', 123).
  factory('a', function() { return 123; }).
  directive('directiveName', ...).
  filter('filterName', ...);

// is same as

angular.module('myModule', []).
  config(function($provide, $compileProvider, $filterProvider) {
    $provide.value('a', 123);
    $provide.factory('a', function() { return 123; });
    $compileProvider.directive('directiveName', ...);
    $filterProvider.register('filterName', ...);
  });

在启动的时候,首先Angular应用所有constant的定义。然后Angular用配置块注册的顺序去应用所有配置块。

运行块

运行块是Angular中最接近main方法的东西。 运行块是需要运行以启动应用程序的代码。 它在所有服务已配置并且注入器已创建后执行。 运行块通常包含难以进行单元测试的代码,因此应该在隔离模块中声明,以便可以在单元测试中忽略它们。

依赖

模块可以列出其他模块作为它们的依赖关系。 根据模块意味着需要的模块需要在加载要求的模块之前加载。 换句话说,所需模块的配置块在需求模块的配置块之前执行。 对于运行块也是如此。 每个模块只能加载一次,即使多个其他模块需要它。

异步加载

模块是一种管理$injector配置的方法,与将脚本加载到VM无关。 有现有的项目处理脚本加载,可以与Angular一起使用。 因为模块在加载时什么都不做,所以它们可以按任何顺序加载到VM中,因此脚本加载器可以利用此属性并且并行化加载过程。

创建与检索

注意,使用angular.module('myModule',[])将创建模块myModule并覆盖任何名为myModule的现有模块。 使用angular.module('myModule')来检索现有模块。

var myModule = angular.module('myModule', []);

// add some directives and services
myModule.service('myService', ...);
myModule.directive('myDirective', ...);

// overwrites both myService and myDirective by creating a new module
var myModule = angular.module('myModule', []);

// throws an error because myOtherModule has yet to be defined
var myModule = angular.module('myOtherModule');

单元测试

单元测试是实例化应用程序的子集以对其应用激励的方式。 小型结构化模块有助于保持单元测试简洁和集中。

每个模块每个进样器只能加载一次。 通常Angular应用程序只有一个注射器,模块只加载一次。 每个测试都有自己的注入器,模块被多次加载。

在所有这些例子中,我们将假设这个模块定义:

angular.module('greetMod', []).

  factory('alert', function($window) {
    return function(text) {
      $window.alert(text);
    }
  }).

  value('salutation', 'Hello').

  factory('greet', function(alert, salutation) {
    return function(name) {
      alert(salutation + ' ' + name + '!');
    }
  });

让我们编写一些测试来展示如何覆盖测试中的配置。

describe('myApp', function() {
  // load application module (`greetMod`) then load a special
  // test module which overrides `$window` with a mock version,
  // so that calling `window.alert()` will not block the test
  // runner with a real alert box.
  beforeEach(module('greetMod', function($provide) {
    $provide.value('$window', {
      alert: jasmine.createSpy('alert')
    });
  }));

  // inject() will create the injector and inject the `greet` and
  // `$window` into the tests.
  it('should alert on $window', inject(function(greet, $window) {
    greet('World');
    expect($window.alert).toHaveBeenCalledWith('Hello World!');
  }));

  // this is another way of overriding configuration in the
  // tests using inline `module` and `inject` methods.
  it('should alert using the alert service', function() {
    var alertSpy = jasmine.createSpy('alert');
    module(function($provide) {
      $provide.value('alert', alertSpy);
    });
    inject(function(greet) {
      greet('World');
      expect(alertSpy).toHaveBeenCalledWith('Hello World!');
    });
  });
});

以下是原文:

What is a Module?

You can think of a module as a container for the different parts of your app – controllers, services, filters, directives, etc.

Why?

Most applications have a main method that instantiates and wires together the different parts of the application.

Angular apps don't have a main method. Instead modules declaratively specify how an application should be bootstrapped. There are several advantages to this approach:

  • The declarative process is easier to understand.
  • You can package code as reusable modules.
  • The modules can be loaded in any order (or even in parallel) because modules delay execution.
  • Unit tests only have to load relevant modules, which keeps them fast.
  • End-to-end tests can use modules to override configuration.

The Basics

I'm in a hurry. How do I get a Hello World module working?

Edit in Plunker

index.htmlscript.js

// declare a module
var myAppModule = angular.module('myApp', []);

// configure the module.
// in this example we will create a greeting filter
myAppModule.filter('greet', function() {
 return function(name) {
    return 'Hello, ' + name + '!';
  };
});

Important things to notice:

  • The Module API
  • The reference to myApp module in <html ng-app="myApp">. This is what bootstraps the app using your module.
  • The empty array in angular.module('myApp', []). This array is the list of modules myApp depends on.

Recommended Setup

While the example above is simple, it will not scale to large applications. Instead we recommend that you break your application to multiple modules like this:

  • A module for each feature
  • A module for each reusable component (especially directives and filters)
  • And an application level module which depends on the above modules and contains any initialization code.

We've also written a document on how we organize large apps at Google.

The above is a suggestion. Tailor it to your needs.

Edit in Plunker

index.htmlscript.js

<div ng-controller="XmplController">
  {{ greeting }}!
</div>

Module Loading & Dependencies

A module is a collection of configuration and run blocks which get applied to the application during the bootstrap process. In its simplest form the module consist of collection of two kinds of blocks:

  1. Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants can be injected into configuration blocks. This is to prevent accidental instantiation of services before they have been fully configured.
  2. Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be injected into run blocks. This is to prevent further system configuration during application run time.
angular.module('myModule', []).
  config(function(injectables) { // provider-injector
    // This is an example of config block.
    // You can have as many of these as you want.
    // You can only inject Providers (not instances)
    // into config blocks.
  }).
  run(function(injectables) { // instance-injector
    // This is an example of a run block.
    // You can have as many of these as you want.
    // You can only inject instances (not Providers)
    // into run blocks
  });

Configuration Blocks

There are some convenience methods on the module which are equivalent to the config block. For example:

angular.module('myModule', []).
  value('a', 123).
  factory('a', function() { return 123; }).
  directive('directiveName', ...).
  filter('filterName', ...);

// is same as

angular.module('myModule', []).
  config(function($provide, $compileProvider, $filterProvider) {
    $provide.value('a', 123);
    $provide.factory('a', function() { return 123; });
    $compileProvider.directive('directiveName', ...);
    $filterProvider.register('filterName', ...);
  });

When bootstrapping, first Angular applies all constant definitions. Then Angular applies configuration blocks in the same order they were registered.

Run Blocks

Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the service have been configured and the injector has been created. Run blocks typically contain code which is hard to unit-test, and for this reason should be declared in isolated modules, so that they can be ignored in the unit-tests.

Dependencies

Modules can list other modules as their dependencies. Depending on a module implies that required module needs to be loaded before the requiring module is loaded. In other words the configuration blocks of the required modules execute before the configuration blocks of the requiring module. The same is true for the run blocks. Each module can only be loaded once, even if multiple other modules require it.

Asynchronous Loading

Modules are a way of managing $injector configuration, and have nothing to do with loading of scripts into a VM. There are existing projects which deal with script loading, which may be used with Angular. Because modules do nothing at load time they can be loaded into the VM in any order and thus script loaders can take advantage of this property and parallelize the loading process.

Creation versus Retrieval

Beware that using angular.module('myModule', []) will create the module myModule and overwrite any existing module named myModule. Use angular.module('myModule') to retrieve an existing module.

var myModule = angular.module('myModule', []);

// add some directives and services
myModule.service('myService', ...);
myModule.directive('myDirective', ...);

// overwrites both myService and myDirective by creating a new module
var myModule = angular.module('myModule', []);

// throws an error because myOtherModule has yet to be defined
var myModule = angular.module('myOtherModule');

Unit Testing

A unit test is a way of instantiating a subset of an application to apply stimulus to it. Small, structured modules help keep unit tests concise and focused.

Each module can only be loaded once per injector. Usually an Angular app has only one injector and modules are only loaded once. Each test has its own injector and modules are loaded multiple times.

In all of these examples we are going to assume this module definition:

angular.module('greetMod', []).

  factory('alert', function($window) {
    return function(text) {
      $window.alert(text);
    }
  }).

  value('salutation', 'Hello').

  factory('greet', function(alert, salutation) {
    return function(name) {
      alert(salutation + ' ' + name + '!');
    }
  });

Let's write some tests to show how to override configuration in tests.

describe('myApp', function() {
  // load application module (`greetMod`) then load a special
  // test module which overrides `$window` with a mock version,
  // so that calling `window.alert()` will not block the test
  // runner with a real alert box.
  beforeEach(module('greetMod', function($provide) {
    $provide.value('$window', {
      alert: jasmine.createSpy('alert')
    });
  }));

  // inject() will create the injector and inject the `greet` and
  // `$window` into the tests.
  it('should alert on $window', inject(function(greet, $window) {
    greet('World');
    expect($window.alert).toHaveBeenCalledWith('Hello World!');
  }));

  // this is another way of overriding configuration in the
  // tests using inline `module` and `inject` methods.
  it('should alert using the alert service', function() {
    var alertSpy = jasmine.createSpy('alert');
    module(function($provide) {
      $provide.value('alert', alertSpy);
    });
    inject(function(greet) {
      greet('World');
      expect(alertSpy).toHaveBeenCalledWith('Hello World!');
    });
  });
});