4、Angular JS 学习笔记 – 模块 [翻译中]

什么是模块?

你可以认为一个模块就是一个app的不同部分,controllers,services,filters,directives,等。

为什么?

大多数的应用有一个main方法实例化并且链接应用的不同部分。

Angular 应用没有main方法,而是使用模块声明指定一个应用如何可以自启动。这种方式有几个优势:

  • 陈述性的过程容易理解
  • 你可以打包代码为一个可复用的模块
  • 这个模块可以以任意的顺序加载(甚至可以并行加载)因为模块是延迟执行的。
  • 单元测试只需要加载相关的模块,保持快速。
  • 端到端的测试可以使用模块去重写配置

基础训练

来看一个hello world :

<div ng-app="myApp">
  <div>
    {{ 'World' | greet }}
  </div>
</div>
// 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 + '!';
  };
});

注意很重要的几点:

  • 模块的API
  • <div ng-app=“myApp”>中引用myApp模块。这个是告诉app使用你的模块。
  • angular.module('myApp', [])中的空数组是myApp模块的依赖组件

推荐的设置:

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:

我们上面的例子很简单,它无法扩展为一个大的应用。替代它我们推荐你分解你的应用到多个模块,像这样:

  • 一个模块只用与一个功能
  • 一个模块对于每个可复用的组件(尤其是指令和过滤器)
  • 一个应用级别的模块依赖上面的模块并且包含任何的初始化代码。

我们还写了一个文档讲解如何组织大型的APP在google 。

上面的建议,根据你的需要使用。

<div ng-controller="XmplController">
  {{ greeting }}
</div>
angular.module('xmpl.service', [])

  .value('greeter', {
    salutation: 'Hello',
    localize: function(localization) {
      this.salutation = localization.salutation;
    },
    greet: function(name) {
      return this.salutation + ' ' + name + '!';
    }
  })

  .value('user', {
    load: function(name) {
      this.name = name;
    }
  });

angular.module('xmpl.directive', []);

angular.module('xmpl.filter', []);

angular.module('xmpl', ['xmpl.service', 'xmpl.directive', 'xmpl.filter'])

  .run(function(greeter, user) {
    // This is effectively part of the main method initialization code
    greeter.localize({
      salutation: 'Bonjour'
    });
    user.load('World');
  })

  .controller('XmplController', function($scope, greeter, user){
    $scope.greeting = greeter.greet(user.name);
  });

模块的加载 & 依赖

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 a 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 namedmyModule. 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!');
    });
  });
});

 

Leave a Comment

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据