首页 Angular 1.x 要点
文章
取消

Angular 1.x 要点

1. 指令(Directive)

指令是 DOM 元素上的标记(例如属性,元素名称,注释或CSS类),它告诉 AngularJS 的 HTML 编译器($compile)将指定的行为附加到该 DOM 元素上(例如,通过事件监听器),或者甚至可以转换 DOM 元素及其子元素。

1.1 指令类型

$compile 可以匹配以下四种类型的指令:

  • <my-dir></my-dir>: 元素名称(E),restrict: E
  • <span my-dir="exp"></span>: 属性(A),restrict: A
  • <span class="my-dir: exp;"></span>: 类名(C),restrict: C
  • <!-- directive: my-dir exp -->: 注释(M),restrict: M

注: 默认是 restrict: EA

1.2 创建指令API

我们可以使用 directive(name, directiveFactory) 创建指令,其中:

  • name: 字符串(指令名)或对象(键: 名;值: factories)
  • directiveFactory: 可注入的指令工厂函数

此工厂函数应该返回一个具有不同选项的对象,以告诉 $compile 指令在匹配时应该如何表现。

工厂函数只被调用一次,即当编译器(compile)第一次匹配指令时。 我们可以在工厂函数中执行任何初始化工作。 工厂函数通过 $injector.invoke 被调用,使其像控制器一样可注入。

1.3 模板扩展指令

假设有一个在代码中多次出现代表客户的信息模板。当我们需要修改其信息时,必须在此信息出现的所有地方修改它。此时我们可以使用指令简化模板。

html:

1
2
3
4
5
6
7
8
9
10
<body ng-app="myApp">
  <div ng-controller="MyController">
    <!-- 模板A -->
    <div my-customer-a></div>
    <my-customer-a></my-customer-a>
    <!-- 模板B -->
    <div my-customer-b></div>
    <my-customer-b></my-customer-b>
  </div>
</body>

js:

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
(function(angular) {
  angular
    .module("templateModule", [])
    .controller("MyController", [
      "$scope",
      function($scope) {
        $scope.customerA = {
          name: "name A",
          address: "address A"
        };
        $scope.customerB = {
          name: "name B",
          address: "address B"
        };
      }
    ])
    .directive("myCustomerA", function() {
      return {
        template:
          "<div>Name: { {customerA.name} } <br> Address: { {customerA.address} }</div>"
      };
    })
    .directive("myCustomerB", function() {
      return {
        template:
          "<div>Name: { {customerB.name} } <br> Address: { {customerB.address} }</div>"
      };
    });
})(window.angular);

$compile 编译并链接元素

  • <div my-customer-a></ div>
  • <my-customer-a></my-customer-a>

时,它将尝试把指令 myCustomerA 匹配到元素的子节点上。

1.4 孤立指令的域(Scope)

1.3 中的指令有个弊端:我们得为不同的顾客创建不同的指令。

我们希望能够做的是将指令内的域与外部域分开,然后将外部域映射到指令的内部域。我们可以通过创建我们称之为孤立域(isolate scope)的方法来实现。 为此,我们可以使用指令的 scope 选项:

html:

1
2
3
4
5
6
7
<body ng-app="myApp">
  <div ng-controller="MyController">
    <!-- 通用模板 -->
    <my-customer customer-info="customerA"></my-customer>
    <my-customer customer-info="customerB"></my-customer>
  </div>
</body>

js:

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
(function(angular) {
  angular.module('isolateModule', []).controller('MyController', [
    '$scope',
    function($scope) {
      $scope.customerA = {
        name: 'name A',
        address: 'address A'
      };
      $scope.customerB = {
        name: 'name B',
        address: 'address B'
      };
    }
  ]).directive('myCustomer', function() {
    return {
      // 类型 E: element names
      restrict: 'E',
      // 指定隔离域中的属性;
      scope: {
        // = 为双向绑定;把指令内部属性 customerInfo 绑定至外部的 customer-info
        // 可简写为 customerInfo: '='
        customerInfo: '=customerInfo'
      },
      templateUrl: '01-template.html'// 可以使用 customerInfo
    }
  });
})(window.angular);

模板 html:

1
2
3
4
5
6
<div>
  Name: { {customerInfo.name} }
</div>
<div>
  Address: { {customerInfo.address} }
</div>

注:

  • 1) scope 选项为 true 、 flase 或一个对象:
    • false(default) : 不会为该指令创建作用域。该指令将使用其父作用域。
    • true: 为指令的元素创建一个作用域,它原型式继承自其父作用域。 如果同一元素上的多个指令请求新作用域,则只创建一个新作用域。
    • {...}: 为指令的模板创建了一个新的孤立作用域。
  • 2) customerInfo: '=customerInfo' 告诉 $compile 把指令内部属性 customerInfo 绑定至外部的 customer-info
    可简写为: customerInfo: '=';
  • 3) 孤立域无法访问外部域中的属性;
  • 4) 四种绑定:
    • customerInfo: '<': 单向绑定,但 customerInfo 为对象/数组时,修改其值也会影响其父域;
    • customerInfo: '=': 双向绑定;
    • customerInfo: '@': 多用于传入字符串,特别是不变的字符串;
    • localFn: '&': 提供了一种执行父作用域的上下文中表达式的方法。
    • 通常需要通过表达式将数据从孤立作用域传递到父作用域,这可以通过将局部变量名称和值的映射传递到表达式包装器 fn 来完成。 例如,如果表达式是 increment(amount) ,那么我们可以通过调用 localFn({amount:22}) 来指定 amount 的值。
    • '<?''=?''@?''&?' 中的 ? 表示此绑定的属性为可选。

1.5 操纵DOM的指令

想要修改 DOM 的指令通常使用 link 选项来注册 DOM 监听器以及更新 DOM。

link 采用具有以下签名的函数:

1
function link(scope, element, attrs, controller, transcludeFn) { ... }

其中:

  • scope: 是一个 AngularJS 域对象。
  • element: 是此指令匹配的 jqLit​​e 包装元素。
  • attrs: 是一个哈希对象,具有标准化属性名称及其对应属性值的键值对。
  • controller: 是指令所需的控制器实例或其自己的控制器(如果有的话)。确切的值取决于指令的 require 属性。
  • transcludeFn: 是一个预先绑定到正确的转换域的转换链接函数。

设置一个定时器,每1s更新一次时间。在 <input /> 中修改时间格式(默认为'yyyy-MM-dd hh:mm:ss a'),显示的时间格式也将做对应修改。
html:

1
2
3
4
5
6
7
8
9
10
11
12
<body ng-app="myApp">
  <div ng-controller="MyController">
    <div>
      日期格式:
      <input type="text" ng-model="format" /> <br />
    </div>
    <div>
      当前时间:
      <span my-timer="format"></span>
    </div>
  </div>
</body>

js:

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
(function(angular) {
  angular
    .module('timeDirective', [])
    .controller('MyController', [
      '$scope',
      function($scope) {
        $scope.format = 'yyyy-MM-dd hh:mm:ss a';
      }
    ])
    .directive('myTimer', [
      '$interval',
      'dateFilter',
      function($interval, dateFilter) {
        var timer;// 定时器
        var fmt;// 格式化参数
        function link(scope, element, attrs) {
          // 把当前时间更新至 DOM 中
          function updateTime() {
            element.text(dateFilter(new Date(), fmt));
          }
          // 监听 attrs.myTimer(先执行此函数,然后开启定时器)
          scope.$watch(attrs.myTimer, function(value) {
            fmt = value;
            updateTime();
          });
          // 开启定时器
          timer = $interval(updateTime, 1000);
          // 监听 DOM 销毁事件
          element.on('$distory', function() {
            $interval.cancel(timer);
          });
        }
        // link 用于注册 DOM 监听(listener)和更新 DOM
        return {
          link
        };
      }
    ]);
})(window.angular);

注:

  • module.controller API 一样,module.directive 中的函数参数($intervaldateFilter)是依赖注入;
  • 当使用 AngularJS 的编译器编译的 DOM 节点被销毁时,它会发出 $destroy事件。
    element.on('$distory', function() {}) 监听 DOM 销毁事件。类似地,当 AngularJS 域被销毁时,它会向监听域广播 $destroy 事件。
  • 注册到域和元素的监听器在销毁时会被自动清理,但如果我们在服务上注册了监听器,或者在未删除的 DOM 节点上注册了监听器,则必须自行清理,否则会有内存泄漏的风险。
  • 指令应该自行清理。我们可以在指令被移除时使用以下两种方式来运行清理函数:
    • element.on('$destroy', ...)
    • scope.$on('$destroy', ...)

1.6 包含其他元素的指令

我们已经知道可以通过 scope 选项把字符串、数值、对象等传入指令内。当我们需要传入整个元素模板时,我们可以使用 transclude 选项。

html:

1
2
3
4
5
6
7
<body ng-app="myApp"
  <div ng-controller="MyController">
    <div ng-hide="!isMessageHidden">{ {message} }</div>
    <!-- 注: hideMyDialog(message) 中的 message 不能省略,它将被用于通过 in 操作符号查找 message 的值 -->
    <my-dialog ng-hide="isMessageHidden" on-close="hideMyDialog(message)">name from out scope: { { name } }</my-dialog>
  </div>
</body>

js:

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
(function(angular) {
  angular
    .module('elementDirective', [])
    .controller('MyController', [
      '$scope',
      '$timeout',
      function($scope, $timeout) {
        $scope.name = 'nxj';
        $scope.msg = '';
        $scope.isMessageHidden = false;
        // 隐藏 <my-dialog>
        $scope.hideMyDialog = function(message) {
          $scope.message = message;
          $scope.isMessageHidden = true;
          // 2秒后恢复
          $timeout(function() {
            $scope.message = '';
            $scope.isMessageHidden = false;
          }, 2000);
        }
      }
    ])
    .directive('myDialog', [
      function() {
        return {
          district: 'E',
          // 创建包含任意内容的指令(HTML 标签中使用 ng-transclude,任意内容将会插入到此标签内)
          transclude: true,
          scope: {
            // =: 绑定指令内部属性;
            // &: 绑定指令内部回调函数;把指令内部的 click 绑定至外部的 on-click
            close: '&onClose'
          },
          link: function(scope) {
            scope.name = 'nie(within directive)';
          },
          templateUrl: '03-my-dialog.html'
        };
      }
    ]);
})(window.angular);

模板 html:

1
2
3
4
5
6
<div class="alert">
  <!-- 注:通过传递包含键值对的对象,把隔离域中的值传递出来 -->
  <a class="close" href ng-click="close({message: 'reset in 2 seconds ...'})">X</a>
  <div ng-transclude></div>
  <div>name in directive: { {name} }</div>
</div>

注:

  • 1) html 中的 <my-dialog></my-dialog> 标签内的 name from out scope: { { name } }为我们需要传入的模板,
    其中 { {name} } 为外部域中的 name,不是孤立域中的 name;
  • 2) js 中的 directive 内的 transclude: true 表示创建包含任意内容的指令;
  • 3) 模板 html 中的 <div ng-transclude></div> 表示把传入的模板放入此元素内;

1.7 能够通讯的指令

有时,我们需要一个由指令组合构建的组件。

想象一下,我们希望拥有一个带有选项卡的容器,其中容器的内容对应于当前激活的选项卡。

html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body ng-app="myApp">
  <my-tabs>
    <my-plane title="tab 1">
      <p>Content of tab 1</p>
      <div ng-click="i=i+1">
        <a href="">点击 { {i || 0} }</a>
      </div>
    </my-plane>
    <my-plane title="tab 2">
      <p>Content of tab 2</p>
      <div ng-click="i=i+1">
        <a href="">点击 { {i || 0} }</a>
      </div>
    </my-plane>
  </my-tabs>
</body>

js:

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
42
43
44
45
(function(angular) {
  angular.module('tabsAndPlanes', []);
  angular
    .module('tabsAndPlanes')
    .directive('myTabs', function() {
      return {
        templateUrl: '04-my-tabs-template.html',
        restrict: 'E',
        transclude: true,
        controller: ['$scope', MyTabsController]
      };
      function MyTabsController($scope) {
        var planes = ($scope.planes = []);
        // this 和 $scope 的区别:
        // 1. this 为 MyTabsController
        // 2. $scope 为控制器控制的域(scope)
        this.addPlane = function(plane) {
          if (planes.length === 0) {
            plane.selected = true;
          }
          $scope.planes.push(plane);
        };
        $scope.selectPlane = function(plane) {
          planes.forEach(plane => (plane.selected = false));
          plane.selected = true;
        };
      }
    })
    .directive('myPlane', function() {
      return {
        templateUrl: '04-my-plane-template.html',
        restrict: 'E',
        transclude: true,
        scope: {
          title: '@'
        },
        require: '^^myTabs',
        link
      };
      // 模板被克隆后执行 link
      function link(scope, element, attrs, myTabsCtrl) {
        myTabsCtrl.addPlane(scope);
      }
    });
})(window.angular);

注:

  • 1) controller: ['$scope', MyTabsController]: 在 myTabs 指令中使用此属性时,将会和 ngController 一样,把此控制器绑定至指令的模板;
  • 2) require: '^^myTabs': 在 myPlane 指令中使用此属性时,如果 $compile 找不到指定的控制器 myTabs,将报错;
    其中 ^^ 前缀的含义如下:
    • 无符号表示搜寻自身
    • ^ 表示搜寻自身及其祖先(parents)
    • ^^ 表示搜寻其祖先(parents)
    • ??^?^^ 搜寻不到时不会报错,传 null 给第4个参数
  • 3) function link(scope, element, attrs, myTabsCtrl): myTabs 指令的控制器作为第4个参数传入;
  • 4) MyTabsController 构造函数中 this$scope 的区别:
    • thisMyTabsControllerthis.addPlane 能暴露 addPlane() 方法 ,以供其他指令使用;
    • $scope 为控制器控制的域(scope);
  • 5) linkcontroller 的区别:
    • controller 可以暴露 API 供其他指令使用;
    • link 函数可以通过使用 require 与控制器交互。

tabs 模板 html:

1
2
3
4
5
6
<ul>
  <li ng-repeat="plane in planes" ng-click="selectPlane(plane)">
    <a href="">{ {plane.title} }</a>
  </li>
</ul>
<div ng-transclude></div>

plane 模板 html:

1
2
3
4
<div ng-show="selected">
  <h4>{ {title} }</h4>
  <div ng-transclude></div>
</div>

2. 控制器(Controller)

在 AngularJS 中,Controller 由 JavaScript 构造函数定义,该函数用于扩充 AngularJS Scope。

控制器(controller)可以以不同方式附加到 DOM 上。对于每种方式,AngularJS 将使用指定的 Controller 构造函数实例化一个新的 Controller 对象:

  • ngController 指令,会新建一个子域,并在构造函数中以 $scope 传入
  • 路由定义中的路由控制器
  • 常规指令或组件指令中的控制器

控制器的 API 如下:

1
controller(name, constructor);

注: 在有依赖注释时,constructor 为数组,依赖注释放在数组前面,构造函数放在数组的末尾,如:

1
2
3
4
5
6
angular.module('phonecatApp').controller("PhoneListController", [
  "$scope",
  function($scope) {
    $scope.phones = [/* 省略 */];
  }
])

控制器应该做的事情:

  • 1) 设置 $scope 对象的初始状态;
  • 2) 向 $scope 对象添加行为。

控制器不应该做的事情:

  • 1) 操纵 DOM - 控制器应仅包含业务逻辑。将任何表示逻辑放入控制器会显着影响其可测试性。AngularJS 在大多数情况下使用数据绑定,少数情况下使用指令来封装 DOM 操作。
  • 2) 格式输入 - 改为使用 AngularJS 表单控件。
  • 3) 过滤器输出 - 改为使用 AngularJS 过滤器。
  • 4) 跨控制器共享代码或状态 - 改为使用 AngularJS 服务。
  • 5) 管理其他组件的生命周期(例如,创建服务实例)。

一般来说,控制器不应该尝试做太多。它应该只包含单个视图所需的业务逻辑。

保持控制器苗条的最常用方法是将不属于控制器的工作封装到服务中,然后通过依赖注入在控制器中使用这些服务。

3. 作用域(Scope)

3.1 作用域特性

  • 1) 作用域(scope)提供 API (scope.$watch()) 来观察模型的变化;
  • 2) 作用域提供 API (scope.$apply()) 来实现在 AngularJS 框架之外更新数据时,也能让视图发生相应变化;
  • 3) 作用域可以嵌套以限制对应用程序组件属性的访问,同时提供对共享模型属性的访问。嵌套的作用域为:
    • 子作用域(child scope): 继承了其父作用域的属性;
    • 孤立作用域(isolate scope): 不继承其父作用域的属性;
  • 4) 作用域提供了评估表达式的上下文(context)。例如,在针对定义 username 属性的特定作用域进行评估之前,表达式 { {username} } 是没有意义的。

3.2 作用域作为数据模型

Scope 是应用程序控制器(controller)和视图(view)之间的粘合剂。在模板链接(link)阶段,指令(directive)在域(scope)上设置 $watch 表达式。

$watch 将允许指令监听到属性的变化,这使得该指令能够将更新后的值呈献给 DOM。

控制器(controller)和指令(directive)都引用了 scope ,但没有相互引用。这种安排将控制器与指令以及 DOM 隔离开来。

这是一个重要的特点,因为它使控制器视图不可知,这极大地改善了应用程序的测试难度。

3.3 作用域层次结构

每个 AngularJS 应用程序只有一个根作用域(root scope),但可以有任意数量的子作用域(child scopes)。

因为指令可以创建新的子作用域,应用程序可以有多个作用域。创建新作用域时,它们将作为其父作用域的子作用域。这会创建一个树形结构,与它们所附着的 DOM 平行。

3.4 DOM 中检索作用域

作用域作为 $scope 数据属性附加至 DOM ,且可以检索以进行调试。(不太可能需要在应用程序内部以这种方式检索范围。)

根作用域附加的 DOM 由 ng-app 指令的位置定义。

如何在调试器中查看作用域:

  1. 右键单击浏览器中感兴趣的元素,然后选择“检查元素”;
  2. 在控制台执行(console)中检索关联的作用域: angular.element($0).scope()

注:调试器允许我们将控制台中当前选定的元素作为 $0 变量进行访问。

3.5 作用域生命周期

浏览器接收事件的的正常流程如下:

  1. 执行相应的 JavaScript 回调函数;
  2. 一旦回调函数完成,浏览器将重新呈现 DOM 并返回以等待更多事件。

当浏览器调用 JavaScript 时,代码在 AngularJS 执行上下文之外执行,这意味着 AngularJS 不知道模型的修改。

为了正确处理模型修改,执行必须使用 $apply 方法进入 AngularJS 执行上下文。只有在 $apply 方法内执行的模型修改才能被 AngularJS 正确计算。

例如,如果指令监听如 ng-click 的 DOM 事件,它必须在 $apply 方法内计算表达式。

在评估表达式之后,该 $apply 方法执行一次 $digest 。在 $digest 阶段,作用域检查所有 $watch 表达式并将它们与之前的值进行比较。这种脏值检查是异步完成的。

这意味着诸如 $scope.username="angular" 的赋值不会立即产生一个 $watch 通知,而是被延迟到 $digest 阶段。

这种延迟是可取的,因为它将多个模型更新合并为一个 $watch 通知,并保证在 $watch 通知期间没有其他的 $watch 正在运行。

如果一个 $watch 更改了模型的值,则会强制执行额外一次 $digest 循环。

  1. 创建:根作用域由 $injector 在应用引导过程中创建。在模板链接(link)期间,某些指令会创建新的子作用域。

  2. 注册监听 :在模板链接期间,指令在作用域上注册监听(watches)。这些监听用于将模型中的值传播到 DOM 。

  3. 模型突变:在 $apply() 内进行的模型更改才会被正确处理。AngularJS API 会隐式执行此操作,因此在控制器中执行同步工作或使用 $http、$timeout 或 $interval 服务进行异步工作时不需要额外调用 $apply() 。

  4. 监听突变:在 $apply 结束时,AngularJS 对根作用域执行 $digest 循环,然后传播给所有子作用域。在 $digest 循环期间,会检查所有被监听的表达式或函数,检测到突变时,执行相应的监听函数。

  5. 作用域销毁:当不再需要子作用域时,其创建者有责任通过 $destroy() API 来销毁它们。销毁后, $digest 的调用将不会传播到该子作用域,并允许垃圾收集器回收子作用域模型使用的内存。

3.6 作用域和指令(Scopes and Directives)

在编译阶段,编译器将指令与 DOM 模板匹配。指令通常分为两类:

  • 观察指令,例如双大括号表达式 { {expression} } ,使用 $watch() 方法注册监听器。只要表达式发生更改,就需要通知此类指令,以便更新视图。
  • 监听器指令,例如 ng-click ,向 DOM 注册监听器。当 DOM 监听器触发时,该指令执行关联的表达式并使用 $apply() 方法更新视图。

当收到外部事件(例如用户操作,计时器或 XHR )时,必须通过 $apply() 方法将关联的表达式应用于作用域,以便正确更新所有监听器。

3.7 作用域和控制器(Scopes and Controllers)

在以下情况下,作用域和控制器相互作用:

  • 控制器使用作用域(scope)将控制器方法暴露给模板(请参阅 ng-controller)。
  • 控制器定义可以改变模型(model)的方法(行为)(作用域上的属性)。
  • 控制器可以在模型上注册监听器。这些监听在控制器行为执行后立即执行。

3.8 与浏览器事件循环集成

下图描述了AngularJS如何与浏览器的事件循环交互。

  1. 浏览器的事件循环等待事件到达。事件是用户交互、计时器事件或网络事件(来自服务器的响应)。
  2. 事件的回调函数被执行。这会进入 JavaScript 上下文。回调函数可以修改 DOM 结构。
  3. 一旦回调函数执行完毕,浏览器就会离开 JavaScript 上下文并根据 DOM 更改重新渲染视图。

AngularJS 通过提供自己的事件处理循环来修改正常的 JavaScript 流。这将 JavaScript 拆分为经典执行上下文和 AngularJS 执行上下文。

只有在 AngularJS 执行上下文中应用的操作才能受益于 AngularJS 数据绑定、异常处理、属性监听等。

我们还可以使用 $apply() 从 JavaScript 进入 AngularJS 执行上下文。请记住,在大多数地方(控制器,服务),通过处理事件的指令已为我们调用了 $apply 。

仅在实现自定义事件回调或使用第三方库回调时才需要显式调用 $apply 。

  1. 通过调用 scope.$apply(stimulusFn) 进入 AngularJS 执行上下文,其中 stimulusFn 是我们希望在 AngularJS 执行上下文中执行的工作。
  2. AngularJS 执行 stimulusFn() ,通常会修改应用程序状态。
  3. AngularJS 进入 $digest 循环。循环由两个较小的循环组成,它们处理 $evalAsync 队列和 $watch 列表。$digest 循环不断迭代直到模型稳定,这意味着 $evalAsync 队列为空,且 $watch 李彪没有检测到任何变化。
  4. $evalAsync 队列用于调度需要在当前堆栈帧之外但在浏览器的视图渲染之前发生的工作。这通常使用 setTimeout(0) 来完成,但是 setTimeout(0) 方法会导致运行缓慢,并且可能导致视图闪烁,因为浏览器在每个事件之后渲染视图。
  5. $watch 列表是一组表达式,它自上次迭代后可能已经发生了改变。如果检测到更改,则调用 $watch 函数,该函数通常使用新值更新 DOM 。
  6. 一旦 AngularJS $digest 循环结束,执行就会离开 AngularJS 和 JavaScript 上下文。接下来是浏览器重新渲染 DOM 以反映任何更改。

4. $provide

$provide 服务有许多通过 $injector 来注册组件的方法。其中许多方法也暴露给了 angular.Module。

  • provider(name, provider) - 使用 $injector 注册一个 service provider 。注册后通过 name + ‘Provider’ 使用。
  • constant(name, obj) - 注册可由 providers 和 services 访问的值/对象。
  • value(name, obj) - 注册只能由 services 访问的值/对象,而不是 providers 。
  • factory(name, fn) - 注册一个包装在 service provider 对象中的服务工厂函数,其 $get 属性将包含给定的工厂函数。
  • service(name, Fn) - 注册一个包装在 service provider 对象中的构造函数,该 $get 属性将使用给定的构造函数实例化一个新对象。
  • decorator(name, decorFn) - 注册一个装饰器函数,它将能够修改或替换另一个服务的实现。

注:

  1. service: 单例对象,由 service factory 创建;
  2. service factory: 函数,由 service provider 创建;
  3. service provider: 构造函数,必须包含指向 service factory 函数的 $get 属性;
  4. 当我们请求一个服务(service)时,$injector 负责查找正确的 service provider,实例化它 并调用其 $get 函数(指向 service factory 函数)来获取服务(service)的实例。
  5. 通常,服务(services)没有配置选项,也不需要向 service provider 添加方法。 service provider 只不过是具有 $get 属性的构造函数。 对于这些情况,$provide 服务具有其他辅助方法来注册服务(service)而不需要指定一个提供者(provider)。

5. $injector

$injector 用于检索由provider、实例化类型、调用方法和加载模块定义的对象实例。

1
var $injector = angular.injector();

5.1 注入函数注释

JavaScript 没有注释,而依赖注入需要注释。以下是使用注入参数注释函数的所有有效方法,并且是等效的。

1
2
3
4
5
6
7
8
9
10
// inferred (only works if code not minified/obfuscated)
$injector.invoke(function(serviceA){});

// annotated
function explicit(serviceA) {};
explicit.$inject = ['serviceA'];
$injector.invoke(explicit);

// inline
$injector.invoke(['serviceA', function(serviceA){}]);
  1. 推断(inferred):在 JavaScript 中调用函数的 toString() 方法返回函数定义。然后可以解析定义并提取函数参数。 当注射器处于严格模式时,不允许这种发现注释的方法。
  2. 注解(annotated):通过在函数上添加 $inject 属性,可以指定注入参数。
  3. 内联(inline):作为注入名称数组,数组中的最后一项是要调用的函数。
本文由作者按照 CC BY 4.0 进行授权