Serenader

Learning by sharing

Angular Tips

4种类型的 scope:

  • 普通的子 scope ,继承父 scope 的方法和属性。
    • ng-include
    • ng-switch
    • ng-controller
    • ng-if
    • directive 里的 scope: true
  • 继承父 scope 的方法和属性,并且将父 scope 的属性顺便复制到新的子 scope 里面。
    • ng-repeat
  • 独立的 scope ,不从父 scope 上继承方法和属性。
    • directive 里面的 scope: { ... }
  • transcluded scope ,这里产生的 scope 跟第一种类型的 scope 一致。只是,ng-transclude 里面的 scope 是从父 controller 上继承的 scope ,而非从 directive 里面的 scope 继承的。
    • directive 里面设置了 transclude: true

ng-repeat 里的 scope:

$scope.myArrayOfPrimitives = [1,2,3];
$scope.myArrayOfObjects = [{num: 1}, {num: 2}, {num: 3}];
<ul>
    <li ng-repeat="num in myArrayOfPrimitives">
        <input type="text" ng-model="num">
    </li>
</ul>
<ul>
    <li ng-repeat="obj in myArrayOfObjects">
        <input type="text" ng-model="obj.num">
    </li>
</ul>
默认情况下,angular 会为每一个 ng-repeat 渲染出来的 <li></li> 创建一个子 scope,该 scope 继承自父 scope 。 不仅如此,angular 还会将当前的值复制到子 scope 里。
即,在上面的第一个 <ul></ul> 里,每个 <li></li> 都有一个 scope ,而且每个 scope 都有一个 $scope.num 属性,该属性值则是 num in myArrayOfPrimitives 里面的 num 的值。同样的道理,在第二个 <ul></ul> 里面,每个 <li></li> 都有一个子 scope ,并且该 scope 有 $scope.obj 属性,该属性指向 obj in myArrayOfObjects 里面的 obj 。
在这种情况下,当用户在第一个 <ul></ul> 里的任意一个 input 输入框输入内容时,当前 <li></li> 里面的 scope 的 $scope.num 的值会随着改变,但是这时候父 scope 里面的 myArrayOfPrimitives 数组内的值并没有变化。这是因为改动的变量实际上只是子 scope 里面的属性,而不是父 scope 数组里的变量。
但是,当用户在第二个 <ul></ul> 里的 input 输入框输入内容时,当前 <li></li> 里面的 scope 的 $scope.obj.num 会随着改变, 并且父 scope 里的 myArrayOfObjects 数组内的元素的值也会跟着改变。 这是因为,尽管改动的是子 scope 内的变量,但是由于该变量是一个引用类型值(Object),因此该改动也会影响到父 scope 里数组内的元素,毕竟两者引用的都是同一个对象。

ng-transclude 里的 scope:

angular.module('app', []).controller('ctrl', function ($scope) {
    $scope.data = 'controller data';
    $scope.hello = 'controller hello world';
}).directive('test', function () {
    return {
        restrict: 'E',
        template: '<div>{{data}}<div ng-transclude></div></div>',
        transclude: true,
        link: function (scope) {
            scope.data = 'directive data';
            scope.hello = 'directive hello';
        },
        scope: {
            data: '='
        }
    };
});
<body ng-controller="ctrl">
    <test data="data">
        <span>{{data}}</span>
        <span>{{hello}}</span>
    </test>
</body>
在这个例子中,test 这个 directive 自身创建了一个独立的 scope ,不从父 scope 继承任何东西。但是它与外面的 controller 做了一个双向绑定:scope: { data: '=' } 。directive 里面的 scope.data 会与外部 controller 的 $scope.data 保持一致。
另外,在 directive 里设置了 transclude: true ,因此,会在 ng-transclude 所在元素创建一个 transcluded scope ,该 scope 是个普通的子 scope ,会继承父 scope 的属性和方法。但是,在这个情况下,transcluded scope 继承的是外部的 controller 里的 scope , 而非 directive 内的那个独立的子 scope 。
因此,可以知道,
<test data="data">
    <span>{{data}}</span>
    <span>{{hello}}</span>
</test>
<test></test> 标签里面的 {{data}}{{hello}} 实际上都是指向 ctrl 这个 controller 里的 scope 的 datahello 这两个属性。但是,最终显示结果却是这样的:
directive data
directive data controller hello world
第一个 directive data 是因为 directive 的模板里有个 {{data}} ,因此会显示 directive 的内容。但是下面的那个 directive data 又是怎么回事呢?不是说 transcluded scope 是继承自 controller 里的 scope 的嘛?为什么不是 controller data controller hello world
首先,controller hello world 这个结果表明了刚刚说到的关于 transcluded scope 的继承是对的,transcluded scope 确实是从 controller 里的 scope 继承的。其次,{{data}} 渲染成 directive data 是因为在 directive 里面使用了双向绑定,将 controller 里的 $scope.data 与 directive 的 scope.data 绑定了。然后在 directive 的独立 scope 里面又将 scope.data 赋值为 directive data ,由于双向绑定的机制,外部的 controller 的 data 也因此改动了。

普通的通过继承的子 scope 容易产生误解的地方:

<div ng-controller="ctrl">
    <span>controller's data: {{hideItem}}</span>
    <div ng-if="conditions">
        <span>ngIf's data: {{hideItem}}</span>
        <button ng-click="hideItem=!hideItem">change hideitem</button>
    </div>
</div>
angular.module('app', []).controller('ctrl', function ($scope) {
    $scope.hideItem = true;
    $scope.conditions = true;
});
初始状态下,controller's datangIf's data 里的 {{hideItem}} 均为 true 。但是,当用户点击了change hideitem 之后,ngIf 内的数据变了,但是外部 controller 的数据没变。
这是因为,ngIf 在自身元素上创建了一个 scope ,并且继承了 ctrl 里的属性和方法。因此,一开始的时候,controller's datangIf's data 的值都相同,都指向 controller 的 $scope.hideItem 。但是当用户点击了 change hideitem 之后,会在 ngIf 的 scope 里执行:$scope.hideItem = !$scope.hideItem 。此时,ngIf 的 scope 创建了一个新的属性 hideItem ,并且值为 false 。此时的 hideItem 属性则是 ngIf scope 自身的属性,而不再是通过继承得到的 hideItem 了,因此此时 ngIf 内的 hideItem 与 controller 内的 hideItem 没有关系了,所以 ngIf 内的值变了,但是 controller 内的值没变。
** 结论:** 在能产生子 scope 的 directive 里面,使用对象来表现 model ,而不是基本类型值(string, boolean, number)。如在 ngIf 内使用 $scope.numbers = {a: 1, b: 2} ,而不推荐写成: $scope.a = 1; $scope.b =2;

Provider

创建 provider 有以下几种方式:
module.config(function ($provide) {
    $provide.provider('greeting', function () {
        this.$get = function () {
            return function (name) {
                alert('hello ' + name);
            };
        };
    });
});

module.config(function ($provide) {
    $provide.factory('greeting', function () {
        return function (name) {
            alert('hello ' + name);
        };
    });
});

module.config(function ($provide) {
    $provide.service('greeting', function () {
        return function (name) {
            alert('hello ' + name);
        };
    });
});

module.config(function ($provide) {
    $provide.value('greeting', function (name) {
        alert('hello ' + name);
    });
});
另外还有简便的写法:
module.provider('greeting', ...);
module.factory('greeting', ...);
module.service('greeting', ...);
module.value('greeting', ...);
以上几种方法创建 provider 结果都是一样的。只是,servicefactoryvalue都是provider 的缩写。而 provider 则拥有更高的可配置性。
创建了 provider 之后即可在其他地方通过依赖注入注入 service 。service 则是由刚创建的 provider 所定义的,如 $provide.provider() 里面 this.$get() 方法所返回的函数。
Angular 在启动的时候会经过两个阶段,config 阶段和 run 阶段。config 阶段则是对 provider 进行各种配置,run 则是 angular 开始编译模板处理 DOM 等。在 configrun 阶段可以通过 module.configmodule.run 方法来添加一些需要运行的代码。而且这两个方法都可以进行依赖注入(module.config 只能注入 provider ,不能注入 service)。因此在 config 阶段可以通过 module.config 来对 provider 进行配置。
module.config(function (greeting) {
    // 这种注入是错误的,此时的 greeting 是一个 service 实例,
    // 而 config 阶段是无法注入 service 的,只能注入 provider
});

module.config(function (greetingProvider) {
    // 在这里对 greetingProvider 进行配置
});
因此,我们可以在 config 阶段对 provider 进行必要的配置:
module.provider('greeting', function () {
    var greet = 'hello ';
    this.setGreeting = function (newName) {
        greet = newName;
    };
    this.$get = function () {
        return function (name) {
            alert(greet + name);
        };
    };
});

module.config(function (greetingProvider) {
    greetingProvider.setGreeting('How are you ');
});

module.controller('ctrl', function (greeting) {
    greeting('Mike'); // alert: How are you Mike
});
当创建完 service 之后可以使用 $injector 来获取(实际上是创建一个service实例)。在可以依赖注入的地方(比如 controller、directive 里的 controller)进行依赖注入实际上就是 $injector 在工作。除了在函数里面进行依赖注入之外,也可以手动通过 $injector 进行手动获取 service:
module.controller('ctrl', function (greeting) {
    greeting('world');
});

// 也可以这样写

module.controller('ctr', function ($injector) {
    var greeting = $injector.get('greeting');
    greeting('world');
});
实际上,$injector 只会创建一个 service 实例,然后缓存起来。以后如果有其他地方也需要注入该 service 的话,则直接将缓存下来的 service 实例返回。
$injectorinvoke 方法接受一个函数作为参数。该函数可以对其进行依赖注入:
var func = function (greeting) {
    greeting('world');
};

$injector.invoke(func);