标签angularJS下的文章

Jerry Bendy 发布于 04月26, 2016

【译】创建自定义angularJS指令(七)- 使用 $asyncValidators 创建唯一值指令

AngularJs


  1. 基础
  2. 独立作用域
  3. 独立作用域和函数参数
  4. transclude与restrict
  5. link函数
  6. 使用控制器
  7. Creating a Unique Value Directive using $asyncValidators

在上一篇文章中我演示了如何创建一个唯一值校验的指令来确定一个 email 地址是否已经被使用过。在 AngularJS 1.3+ 以上的版本中增加了许多新的特性可以使指令的代码变得更加整洁并且更易于使用。在这篇文章中,我将会更新之前的代码,尝试一下新的特性。下面展示的代码是 Customer Manager Standard 中的一部分,你可以在 Github 上看到完整的代码。

下面的截图是运行后的一部分,在截图中,email 地址已经被其它用户使用,导致显示错误信息。

指令使用以下代码绑定到 email 输入框中:

<input type="email" name="email" 
        class="form-control"
        data-ng-model="customer.email"
        data-ng-model-options="{ updateOn: 'blur' }"
        data-wc-unique
        data-wc-unique-key="{{customer.id}}"
        data-wc-unique-property="email"
        data-ng-minlength="3"
        required />

You can see that a custom directive named wc-unique is applied as well as 2 properties named wc-unique-key and wc-unique-property (I prefer to add the data- prefix but it’s not required). Before jumping into the directive let’s take a look at an AngularJS factory that can be used to check if a value is unique or not. 通过上面的代码你可以看到我们使用了一个叫做 wc-unique 的自定义指令,以及两个属性 wc-unique-keywc-unique-property (我更喜欢为添加 data- 前缀,虽然这不是必须的)。在跳到指令代码之前,我们先看下 AngularJS 中可以用来检查一个值是否唯一的 factory 。

创建 Factory

唯一值校验需要依赖后端服务,AngularJS 提供了一些可以与后端通信的服务,如 $http$resource。在这个例子中,我会使用一个叫做 dataService 自定义的 factory,依赖 $http 服务进行 Ajax 操作与后端通信。它内部使用一个叫做 checkUniqueValue() 的方法处理唯一值验证。要注意一下,我并没有特别去区分 "factory" 与 "service",因为它们从根本上来说所做的事情其实是一样的,只是对我来说 "service" 可能更好听一些(个人爱好)。

(function () {

    var injectParams = ['$http', '$q'];

    var customersFactory = function ($http, $q) {
        var serviceBase = '/api/dataservice/',
            factory = {};

        factory.checkUniqueValue = function (id, property, value) {
            if (!id) id = 0;
            return $http.get(serviceBase + 'checkUnique/' + id + '?property=' + property + 
              '&value=' + escape(value)).then(
                function (results) {
                    return results.data.status;
                });
        };

        //More code follows

        return factory;
    };

    customersFactory.$inject = injectParams;

    angular.module('customersApp').factory('customersService', customersFactory);

}());

创建唯一值指令

我创建了一个叫做 wcUnique 的指令用于处理唯一值的验证(wc 是指 Wahlin Consulting,作者所在的公司名)。这是一个相当简单的指令,仅限于被用作一个属性。指令的外壳基本如下:

function () {

    var injectParams = ['$q', 'dataService'];

    var wcUniqueDirective = function ($q, dataService) {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, element, attrs, ngModel) {

                //Validation code goes here

            }
        };
    };

    wcUniqueDirective.$inject = injectParams;

    angular.module('customersApp').directive('wcUnique', wcUniqueDirective);

}());

As the directive is loaded the link() function is called which gives access to the current scope, the element the directive is being applied to, attributes on the element, and the ngModelController object. If you’ve built custom directives before (hopefully you’ve been reading my series on directives!) you’ve probably seen scope, element and attrs before but the 4th parameter passed to the link() function may be new to you. If you need access to the ngModel directive that is applied to the element where the custom directive is attached to you can “require” ngModel (as shown in the code above). ngModel will then be injected into the link() function as the 4th parameter and it can be used in a variety of ways including validation scenarios. One of the new properties available in ngModelController with AngularJS 1.3+ is $asyncValidators (read more about it here) which we’ll be using in this unique value directive.

Here’s the complete code for the wcUnique directive:

(function () {

    var injectParams = ['$q', '$parse', 'dataService'];

    var wcUniqueDirective = function ($q, $parse, dataService) {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, element, attrs, ngModel) {
                ngModel.$asyncValidators.unique = function (modelValue, viewValue) {
                    var deferred = $q.defer(),
                        currentValue = modelValue || viewValue,
                        key = attrs.wcUniqueKey,
                        property = attrs.wcUniqueProperty;

                    //First time the asyncValidators function is loaded the
                    //key won't be set  so ensure that we have 
                    //key and propertyName before checking with the server 
                    if (key && property) {
                        dataService.checkUniqueValue(key, property, currentValue)
                        .then(function (unique) {
                            if (unique) {
                                deferred.resolve(); //It's unique
                            }
                            else {
                                deferred.reject(); //Add unique to $errors
                            }
                        });
                    }
                    else {
                        deferred.resolve(); //Ensure promise is resolved if we hit this 
                     }

                    return deferred.promise;
                };
            }
        };
    };

    wcUniqueDirective.$inject = injectParams;

    angular.module('customersApp').directive('wcUnique', wcUniqueDirective);

}());

You’ll notice that the ngModel parameter that is injected into the link() function is used to access a property named $asyncValidators. This property allows async operations to be performed during the data validation process which is perfect when you need to go back to the server to check if a value is unique. In this case I created a new validator property named unique that is assigned to a function. The function creates a deferred object and returns the promise. From there the code grabs the current value of the input that we’re trying to ensure is unique and also grabs the key and property attribute values shown earlier.

The key represents the unique key for the object (ultimately the unique identifier for the record). This is used so that we exclude the current object when checking for a unique value across objects on the server. The property represents the name of the object property that should be checked for uniqueness by the back-end system.

Once the variables are filled with data, the key and property values are passed along with the element’s value (the value of the textbox for example) to a function named checkUniqueValue() that’s provided by the dataService factory shown earlier. This triggers an Ajax call back to the server which returns a true or false value. If the server returns that the value is unique we’ll resolve the promise that was returned. If the value isn’t unique we’ll reject the promise. A rejection causes the unique property to be added to the $error property of the ngModel so that we can use it in the view to show and hide and error message.

Showing Error Messages

The unique property added to the $error object can be used to show and hide error messages. In the previous section it was mentioned that the $error object is updated but how do you access the $error object? When using AngularJS forms, a name attribute is first added to the <form> element as shown next:

<form name="editForm">

The editForm value causes AngularJS to create a child controller named editForm that is associated with the current scope. In other words, editForm is added as a property of the current scope (the scope originally created by your controller). The textbox shown earlier has a name attribute value of email which gets converted to a property that is added to the editForm controller. It’s this email property that gets the $error object. Let’s look at an example of how we can check the unique value to see if the email address is unique or not:

<div class="col-md-2">
    Email:
</div>
<div class="col-md-10">
    <!-- type="email" causing a problem with Breeze so using regex -->
    <input type="email" name="email" 
            class="form-control"
            data-ng-model="customer.email"
            data-ng-model-options="{ updateOn: 'blur' }"
            data-wc-unique
            data-wc-unique-key="{{customer.id}}"
            data-wc-unique-property="email"
            data-ng-minlength="3"
            required />
    <!-- Show error if touched and unique is in error -->
    <span class="errorMessage" ng-show="editForm.email.$touched && editForm.email.$error.unique">
        Email already in use
    </span>
</div>

Notice that the ngShow directive (on the span at the bottom of the code) checks the editForm property of the current scope and then drills down into the email property. It checks if the value is touched using the $touched property (this property is in AngularJS 1.3+ and reports if the target control lost focus – it has nothing to do with a touch screen) and if the $error.unique value is there or not. If editform.email.$error.unique exists then we have a problem and the user entered an email that is already in use. It’s a little bit confusing at first glance since we’re checking if unique is added to the $error object which means the email is already in use (the unique property is in error). If it’s not on the $error object then then everything is OK and the user entered a unique value.

The end result is the red error message shown next:

Conclusion

Directives provide a great way to encapsulate functionality that can be used in views. In this post you’ve seen a simple AngularJS unique directive that can be applied to textboxes to check if a specific value is unique or not and display a message to the user. To see the directive in an actual application check out the Customer Manager sample application at https://github.com/DanWahlin/CustomerManagerStandard.


From ASP.net

阅读全文 »

Jerry Bendy 发布于 04月26, 2016

【译】创建自定义angularJS指令(六)- 使用控制器

AngularJs


  1. 基础
  2. 独立作用域
  3. 独立作用域和函数参数
  4. transclude与restrict
  5. link函数
  6. 使用控制器
  7. Creating a Unique Value Directive using $asyncValidators

在这个 AngularJS 指令系列的文章中你已经了解到一些指令的关键部分,但还没有任何指令与控制器绑定相关的内容。控制器在 AngularJS 中的典型用途就是把路由和视图联系在一起,在指令中也是如此。事实上,在指令中使用控制器通常会使代码看起来更简洁,并且更易于维护。当然,指令中的控制器是可选的,如果你喜欢用简单的方式创建指令,你会发现控制器在很多情况下是适用的,并且更好用。使用控制器会让指令看起来更像是“子视图”。

在这篇文章中我将会简单的讲解下如何会配控制器到指令,以及控制器在指令中扮演折角色。先看一个不使用控制器的指令的例子:

不使用控制器的指令

指令提供一些不同的方式用于生成 HTML、管理数据以及处理额外的任务等。在指令需要处理大量 DOM 操作时,使用 link 方法是很好的实践。下面是一个使用 link 方法的例子:

(function() {

  var app = angular.module('directivesModule');

  app.directive('domDirective', function () {
      return {
          restrict: 'A',
          link: function ($scope, element, attrs) {
              element.on('click', function () {
                  element.html('You clicked me!');
              });
              element.on('mouseenter', function () {
                  element.css('background-color', 'yellow');
              });
              element.on('mouseleave', function () {
                  element.css('background-color', 'white');
              });
          }
      };
  });

}());

往指令中添加一个控制器对于指令中的 DOM 操作和事件处理来说没有任何意义。虽然例子中可以通过在视图中添加 AngularJS 指令(如 ngClick)和控制器的方式完成相同的功能,但如果 DOM 操作是指令整体要完成的工作的话我们还是没有理由要使用控制器的。

在你需要进行一些简单的 DOM 操作,或整合数据到 HTML, 或处理事件等的时候,添加控制器可以很大程度上简化你的代码。下面通过一个简单的指令的例子来理解上面这句话。例子中生成一个列表,并且可以通过按钮添加项目到列表中。下图是可能的输出:

有很多种方法可以做到这种效果。一个典型的 DOM 为中心的方法是使用 link 函数处理指令中显示的内容。请记住,有很多方法可以做到这一点,总的目标是展示 DOM 为中心的代码(尽可能简单):

(function() {

  var app = angular.module('directivesModule');

  app.directive('isolateScopeWithoutController', function () {

      var link = function (scope, element, attrs) {

              //Create a copy of the original data that’s passed in              
              var items = angular.copy(scope.datasource);

              function init() {
                  var html = '<button id="addItem">Add Item</button><div></div>';
                  element.html(html);

                  element.on('click', function(event) {
                      if (event.srcElement.id === 'addItem') {
                          addItem();
                          event.preventDefault();
                      }
                  });
              }

              function addItem() {
                  //Call external function passed in with &
                  scope.add();

                  //Add new customer to the local collection
                  items.push({
                      name: 'New Directive Customer'
                  });

                  render();
              }

              function render() {
                  var html = '<ul>';
                  for (var i=0,len=items.length;i<len;i++) {
                      html += '<li>' + items[i].name + '</li>'
                  }
                  html += '</ul>';                  

                  element.find('div').html(html);
              }

              init();
              render();        
      };


      return {
          restrict: 'EA',
          scope: {
              datasource: '=',
              add: '&',
          },
          link: link
      };
  });

}());

虽然这些代码完成了这个功能,却是使用了一种类型于 jQuery 插件的思路来写的,使用一种作者称之为 “control-oriented” 的方式,即标签名称和/或ID在代码中普遍存在。手动操作 DOM 算是一种好的方法,尤其是在一些特殊的场景下(为性能考虑),但这绝不是我们构建 Angualar 应用程序的方式。这种混合的写法会使代码变得凌乱并让指令变得臃肿。

在上面的代码中,当点击按钮时,addItem()函数被调用,添加一个新的元素并重新render()render()函数创建一个<ul>标签和多个<li>标签。虽然这样看起来是没有什么问题,事实上这会导致代码的碎片化,将会使以后的维护工作变得非常困难。或许在这种很小的指令里面看起来问题还不算严重,在以后需要给指令添加或修改功能时这个问题才会日渐突出。

还有在这段代码中有一个更细微的问题。当调用scope.add()或其它方式修改了任何父范围内作用域的值后还必须要调用$scope.$apply()应用更改(具体原因在这篇文章中就不详述了,但这是绝对需要考虑的因素)。最后,指令并不像文章开头提到的“子视图”的概念——它只是一串代码。在这个例子中控制器能如何更好的帮助我们吗?Let's take a look。

给指令添加控制器和视图

上一节中的指令能够完成任务,但我们更愿意写一个标准的 AngularJS 视图,使用数据驱动的方式改变 DOM。通过在指令中使用控制器和视图,我们就可以像写一般的视图一样写指令。

下面的例子把之前的指令代码转换为控制器的形式,有没有感觉很清新?

(function() {

  var app = angular.module('directivesModule');

  app.directive('isolateScopeWithController', function () {

    var controller = ['$scope', function ($scope) {

          function init() {
              $scope.items = angular.copy($scope.datasource);
          }

          init();

          $scope.addItem = function () {
              $scope.add();

              //Add new customer to directive scope
              $scope.items.push({
                  name: 'New Directive Controller Item'
              });
          };
      }],

      template = '<button ng-click="addItem()">Add Item</button><ul>' +
                 '<li ng-repeat="item in items">{{ ::item.name }}</li></ul>';

      return {
          restrict: 'EA', //Default in 1.3+
          scope: {
              datasource: '=',
              add: '&',
          },
          controller: controller,
          template: template
      };
  });

}());

这个指令可以以下面任一种方式使用:

属性: <div isolate-scope-with-controller datasource="customers" add="addCustomer()"></div>

元素: <isolate-scope-with-controller datasource="customers" add="addCustomer()">
         </isolate-scope-with-controller>

通读上面的代码你会发现通过控制器与视图绑定的方式写指令格外简单,就像在写一个子视图一样。这样写的好处就在于完全避开了 DOM 操作,现在的代码可以认为是与 DOM 无关的,无疑这会减少很多开发和维护成本。

视图使用 template 属性定义,控制器使用 controller 属性定义。当然视图还可以使用 templateUrl 从外部文件导入,没必要都直接写在指令代码里面。当视图文件有很多代码时,templateUrl$templateCache 会是更好的选择。

继续上面的例子,视图中经常会调用其它指令,如 ng-clickng-repeat,也会使用如 {% raw %}{{ ... }}{% endraw %}ng-bind 这种形式的数据绑定。避免 DOM 操作的好处在这里更加明显。控制器中注入 $scope 并定义 items 属性,视图中使用 ng-repeatitems 中循环并生成 <li>标签。当点击添加按钮时,会调用 $scope 中的 addItem() 方法并添加一个新的项目到 items 中。因为 addItem() 是由 ng-click 调用的,所以父级作用域在使用 $scope.add 的时候无需关心前面提到的关于 $scope.$apply() 的任何问题。

通常在指令需要很高的性能的情况下更建议使用原始的 DOM 操作的方式,直接操作 DOM 总是比使用控制器要快很多(可以避免很多额外的操作)。如果你参加我的讲座将可能会听到我经常说这么一名话:“使用正确的工具做正确的事情(Use the right tool for the right job)”,我从来不会相信有任何一种工具可以适用所有场景,并知道每一种情形和应用都是独一无二的。

This thought process definitely applies to directives since there are many different ways to write them. In many situations I’m happy with how AngularJS performs and know about the pitfalls to avoid so I prefer the controller/view type of directive whenever possible. It makes maintenance much easier down the road since you can leverage existing Angular directives in the directive’s view and modify the view using a controller and scope. If, however, I was trying to maximize performance and eliminate the use of directives such as ng-repeat then going the DOM-centric route with the link function might be a better choice. Again, choose the right tool for the right job.

在指令中使用 controllerAs

如果你是 controllerAs 语法的追捧者你将会为可以在指令中使用同样的语法而感到惊喜。当你在指令中定义了一个 指令定义对象(DDO, Directive Definition Object),你就可以为这个指令添加 controllerAs 属性。从 Angular 1.3 开始,你还需要添加一个 bindToController 属性来确保是绑定到控制器而不是作用域。下面是一个使用 controllerAs 语法的例子:

(function() {

  var app = angular.module('directivesModule');

  app.directive('isolateScopeWithControllerAs', function () {

      var controller = function () {

              var vm = this;

              function init() {
                  vm.items = angular.copy(vm.datasource);
              }

              init();

              vm.addItem = function () {
                  vm.add();

                  //Add new customer to directive scope
                  vm.items.push({
                      name: 'New Directive Controller Item'
                  });
              };
      };    

      var template = '<button ng-click="vm.addItem()">Add Item</button>' +
                     '<ul><li ng-repeat="item in vm.items">{{ ::item.name }}</li></ul>';

      return {
          restrict: 'EA', //Default for 1.3+
          scope: {
              datasource: '=',
              add: '&',
          },
          controller: controller,
          controllerAs: 'vm',
          bindToController: true, //required in 1.3+ with controllerAs
          template: template
      };
  });

}());

注意例子里面的控制器别名 vmviewModel 的缩写)赋给到 controllerAs 属性,并且同时在控制器和视图中被使用。bindToController 设置为 true 用于确保属性绑定到控制器而不是作用域。这种方式相对于前面所述的方法来说要显得更加的简洁,还允许你在视图中使用点语法(如 vm.customers),所以更推荐这种用法。

总结

使用控制器在一些场景下会使指令代码变得更加的简练、直观。虽然使用控制器通常来说不是必要的,但从“子视图”的概念中解放出来,让指令做更多有价值的操作,通常会让你的代码更具可维护性。下一篇文章我们来讨论一些可以在指令中使用的额外的功能,如 $asyncValidators


From ASP.net

阅读全文 »

Jerry Bendy 发布于 04月25, 2016

【译】创建自定义angularJS指令(四)- transclude与restrict

AngularJs


  1. 基础
  2. 独立作用域
  3. 独立作用域和函数参数
  4. transclude与restrict
  5. link函数
  6. 使用控制器
  7. Creating a Unique Value Directive using $asyncValidators

在这个系列的第三节中,我们介绍了怎样定义函数独立作用域属性并且传递参数给函数。这一节我将讲解关于指令的包含(transclude)与限制(restrict)。

restrict (约束)

指令在HTML里可以被定义为元素、属性、CSS类或者注释。那么你将如何限制你的自定义指令可以使用哪种方式?

为了限制一个指令可以如何以及在哪里被使用,你可以用restrict属性来定义,它可以接收以下值:

E —— 元素

指令可以作为一个独立的元素使用,在指令完全需要完全重写元素的行为时推荐使用,也可以和A同样使用:

<my-directive></my-directive>

A —— 属性

指令可以作为某个元素的属性使用,在指令需要增强元素的功能或同一个元素需要有多个指令同时工作时推荐使用:

<div my-directive=exp></div>

C —— CSS类

指令可以作为元素的CSS类使用,不太推荐使用;如果是需要给某些具有特定CSS类的元素附加功能时推荐使用:

<div class=my-directive: exp;></div>

M —— 注释(不推荐)

指令可以作为HTML注释使用,这种方式由于不易读,且可能会被一些HTML压缩工具删除掉,所以不推荐使用:

<!-- directive: my-directive exp -->

这里是一个例子,指令可以被作为一个元素或者元素的属性使用,要注意在多个属性值之间不需要任何分隔符:

app.directive('myDirectiveWithRestriction', function () {
    return {
        restrict: 'EA',
        scope: {
            tasks: '='
        }
    };
});

虽然CM属性值也可以用,但它们被用得很少。大多数的指令只会使用EA

关于restrict的东西就这么简单,下面我们来看transclude

transclude (嵌入)

如果你曾经载入一个CSS样式或HTML模板到一个HTML页面,或载入一个头部或脚部的HTML碎片到一个shtml页面(也就是SSI),那么你就使用过transclude的特性。下面是原作者的一个简单介绍transclude的视频(天朝打不开,不用试了)。

AngularJS Transclusion in 120 Seconds

{% youtube TRrL5j3MIvo %}

我们创建了一个指令,transclude方法提供了一种在指令的消费者那里定义被包含的HTML代码的方式。例如你可能需要输出一个表格,创建一个指令用来控制表格的行如何被渲染。或者,你可能有一个可以输出错误信息的指令,允许指令的消费者提供HTML的内容,指令本身控制输出的错误消息的颜色和行为。需要支持这种类型的功能,指令的消费者需要拥有对指令的HTML有更多的控制权,可以决定指令的HTML如何被生成。

AngularJS提供两个关键的特性用于支持嵌入。第一个是在指令中使用transclude的属性,需要把指令的transclude属性设置为true。第二个使用ng-transclude指令,用来指定嵌入的HTML内容将会被放置在指令模板的什么地方。指令代码使用这两个特性来支持嵌入。

下面是一个简单的指令的例子来演示如何使用transclude。例子中使用嵌入来允许指令消费者提供一段HTML生成每一个任务。看下面的代码你会发现指令的transclude属性被设置成true,并且restrict属性设置成E,所以指令只能被作为一个元素使用。这允许在指令的消费者处使用自定义的内容。另一个属性,replace,用来处理是否使用指令的模板内容替换掉指令的元素(在这个例子中是<isolated-scope-with-transclusion>元素),如果值为false或省略此属性,指令将会保留<isolated-scope-with-transclusion>元素。

angular.module('directivesModule')
.directive('isolatedScopeWithTransclusion', function () {
    return {
        restrict: 'E',
        transclude: true,
        replace: true,
        scope: {
            tasks: '='
        },
        controller: function ($scope) {
            $scope.addTask = function () {

                if (!$scope.tasks) $scope.tasks = [];

                $scope.tasks.push({
                    title: $scope.title
                });

            };
        },
        template: '<div>Name: <input type="text" ng-model="title" />&nbsp;' +
                  '<button ng-click="addTask()">Add Task</button>' +
                  '<div class="taskContainer"><br />' +
                     '<ng-transclude></ng-transclude>' +
                  '</div></div>'
    };
});

仔细看指令的template部分,你会发现<div>上有一个ng-transclude指令。指令中嵌入的内容将会被放置在定义了ng-transclude指令的元素内。下面是一个使用指令的例子,并且在指令内嵌入了一段HTML内容:

<isolated-scope-with-transclusion tasks="tasks">
    <div ng-repeat="task in tasks track by $index">
         <strong>{{ task.title }}</strong>
    </div>
</isolated-scope-with-transclusion>

在这个例子中,指令内部通过ngRepeat指令处理tasks,并把处理后的内容放置在<isolated-scope-with-transclusion>指令元素内部。自定义的HTML内容将会被放置在指令模板中ng-transclude被定义的位置。假定tasks内只包含一个元素,指令初次运行时的输出将会像下图中显示的那样。最后当用户添加任务时,任务会自动被添加到列表中。

这个例子很简单,所以我们可以很容易的把焦点关注在transclude以及它如何使用和工作上面。一般来说通过一些扩展和一些HTML片段就可以让指令拥有更丰富的功能,而且指令中并没有限制只能使用一个ngTransclude指令。一些第三方的指令,如 UI-bootstrap 中的AlertAccordion也是通过transclude来工作的。

最后

transclude在你熟悉了它的基本工作方式和优点之后会显得很简单。像在视频中提到的,通过在指令中使用tranclude以及ngTransclude,你可以方便的为你的指令提供一个自定义的功能。

transclude在指令模板中使用ngRepeatngIf时行为会变得比较复杂,这种情况我将会在以后的文章中具体指出。


此文章由冰翼翻译自 asp.net, 原作者 Dan Wahlin

阅读全文 »

Jerry Bendy 发布于 04月08, 2016

【译】创建自定义angularJS指令(三)- 独立作用域和函数参数

AngularJs


  1. 基础
  2. 独立作用域
  3. 独立作用域和函数参数
  4. transclude与restrict
  5. link函数
  6. 使用控制器
  7. Creating a Unique Value Directive using $asyncValidators

文章的第二部分我们介绍了独立作用域以及独立作用域如何被用来使指令更易于重用。 关于独立作用域的很大一部分都是本地作用域属性以及如何使用如@=以及&来处理数据绑定和委托。使用这些属性你可以传递数据到AngularJS的指令中,以及从指令中输出数据。如果你对这方面还不了解的话可以先阅读上一篇关于独立作用域的文章。这一节将着重讲下指令本地作用域属性中的函数部分,&的具体用法。

独立作用域和函数参数

通过使用本地作用域属性,你可以传递一个外部的函数参数(如定义在控制器$scope中的函数)到指令。这些使用&就可以完成。下面是一个例子,定义一个叫做add的本地作用域属性用来保存传入函数的引用:

angular.module('directivesModule')
.directive('isolatedScopeWithController', function () {
    return {
        restrict: 'EA',
        scope: {
            datasource: '=',
            add: '&',
        },
        controller: function ($scope) {

            // ...            

            $scope.addCustomer = function () {
                // 调用外部作用域函数
                var name = 'New Customer Added by Directive';
                $scope.add();

                // 添加新的`customer`到指令作用域
                $scope.customers.push({
                    name: name                
                });
            };
        },
        template: '<button ng-click="addCustomer()">Change Data</button><ul>
                   <li ng-repeat="cust in customers">{{ cust.name }}</li></ul>'
    };
});

指令的消费者可以通过定义一个add属性的方式传递一个外部的函数到指令。如下:

<div isolated-scope-with-controller datasource="customers" add="addCustomer()"></div>

在这个例子中,函数addCustomer()将会在用户点击指令中创建的按钮时被调用。没有参数传入,所以这里是一个相对简单的操作。

如何向addCustomer()函数中传递参数呢?例如,假设addCustomer()函数显示在下面的控制器下并且当函数被调用时需要传递一个name参数到指令中:

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

app.controller('CustomersController', ['$scope', function ($scope) {
    var counter = 0;
    $scope.customer = {
        name: 'David',
        street: '1234 Anywhere St.'
    };

    $scope.customers = [];

    $scope.addCustomer = function (name) {
        counter++;
        $scope.customers.push({
            name: (name) ? name : 'New Customer' + counter,
            street: counter + ' Cedar Point St.'
        });
    };

    $scope.changeData = function () {
        counter++;
        $scope.customer = {
            name: 'James',
            street: counter + ' Cedar Point St.'
        };
    };
}]);

从指令内传递一个参数到外部函数在你了解它的工作方式后会显得特别简单,下面是一般开发者起初可能会尝试的写法:

angular.module('directivesModule')
.directive('isolatedScopeWithController', function () {
    return {
        restrict: 'EA',
        scope: {
            datasource: '=',
            add: '&',
        },
        controller: function ($scope) {
            ...


            $scope.addCustomer = function () {
                // 调用外部函数,注意这里直接传递了一个 name 参数
                var name = 'New Customer Added by Directive';
                $scope.add(name);

                // 添加新的`customer`
                $scope.customers.push({
                    name: name
                });
            };
        },
        template: '<button ng-click="addCustomer()">Change Data</button><ul>' +
                  '<li ng-repeat="cust in customers">{{ cust.name }}</li></ul>'
    };
});

需要注意的是指令的控制器通过调用$scope.add(name)来尝试调用外部函数并传递一个参数过去。这样可以工作吗?实际上在外部函数中输出这个参数得到的却是undefined,这可能让你抓破脑袋都想不通为什么。那么接下来我们该做什么呢?

选择1:使用对象字面量

一种方法是传递一个对象字面量。下面是演示如何把name传递到外部函数中的例子:

angular.module('directivesModule')
.directive('isolatedScopeWithController', function () {
    return {
        restrict: 'EA',
        scope: {
            datasource: '=',
            add: '&',
        },
        controller: function ($scope) {
            ...


            $scope.addCustomer = function () {
                // 调用外部函数
                var name = 'New Customer Added by Directive';
                $scope.add({ name: name });

                // Add new customer to directive scope
                $scope.customers.push({
                    name: name,
                    street: counter + ' Main St.'
                });
            };
        },
        template: '<button ng-click="addCustomer()">Change Data</button>' +
                  '<ul><li ng-repeat="cust in customers">{{ cust.name }}</li></ul>'
    };
});

需要注意的是$scope.add()方法调用时现在传递了一个对象字面量作为参数。很不幸,这样仍然不能工作!什么原因呢?传递给$scope.add()的对象字面量中定义的name属性在分配给指令时同样也需要在外部函数中被定义。非常重要的一点是,在视图中写的参数名必须要与对象字面量中的名字匹配。下面是一个例子:

<div isolated-scope-with-controller datasource="customers" add="addCustomer(name)"></div>

可以看到在视图中使用指令时,addCustomer()方法添加了个参数name。这个name必须要与指令中调用$scope.add()时传入的对象字面量中的name相匹配。如此一来指令就能正确工作了。

选择2:存储一个函数引用并调用它

上面那种方式的问题在于在使用指令时必须要给函数传递参数而且参数名必须在指令内以对象字面量的形式被定义。如果任何一点不匹配将无法工作。虽然这种方法可以完成需求,但仍然有很多问题。例如如果指令没有完善的使用说明文档就很难知道指令中需要传递的参数名究竟是什么,这时就不得不去翻指令源码查看参数内容了。

另一种可行的方法是在指令上定义一个函数但在函数名后面不加圆括号,如下:

<div isolated-scope-with-controller-passing-parameter2 datasource="customers" add="addCustomer"></div>

为了传递参数到外部的addCustomer函数你需要在指令中做以下事情。把$scope.add()(name)代码放到可被addCustomer调用的方法下面:

angular.module('directivesModule')
.directive('isolatedScopeWithControllerPassingParameter2', function () {
    return {
        restrict: 'EA',
        scope: {
            datasource: '=',
            add: '&',
        },
        controller: function ($scope) {

            ...

            $scope.addCustomer = function () {
                // 调用外部函数
                var name = 'New Customer Added by Directive';

                $scope.add()(name);

                ...          
            };
        },
        template: '<button ng-click="addCustomer()">Change Data</button><ul>' +
                  '<li ng-repeat="cust in customers">{{ cust.name }}</li></ul>'
    };
});

为什么这种方法可以工作?这个需要从&的另一个主要作用说起。&在指令中主要的作用是计算表达式,即在控制器调用以&定义的作用域属性时AngularJS会计算出这个表达式的值并返回。例如在视图中输入add="x = 42 + 2",那么在指令中读取$scope.add()时将会返回这个表达式的计算结果(44),任何一个有效的AngularJS的表达式都可以是add属性的值并在读取add属性时被计算。所以当我们在视图中输入不带圆括号的函数add="customers"时,指令中$scope.add()实际返回的是在控制器中定义的函数customers()。所以在指令中调用$scope.add()(name)就相当于调用控制器的customers(name)

在指令中输出$scope.add()将会得到以下内容(正好验证上面所说):

&背后的运行机制

如果你对&的运行机制感兴趣,当&本地作用域属性被调用(例如上面例子中的a dd本地作用域属性),下面的代码将会执行:

case '&':
    parentGet = $parse(attrs[attrName]);
    isolateScope[scopeName] = function(locals) {
        return parentGet(scope, locals);
    };
break;

上面的attrName变量相当于前面例子中指令本地作用域属性中的add。调用$pares返回的parentGet函数 如下:

 function (scope, locals) {
      var args = [];
      var context = contextGetter ? contextGetter(scope, locals) : scope;

      for (var i = 0; i < argsFn.length; i++) {
        args.push(argsFn[i](scope, locals));
      }
      var fnPtr = fn(scope, locals, context) || noop;

      ensureSafeObject(context, parser.text);
      ensureSafeObject(fnPtr, parser.text);

      // IE stupidity! (IE doesn't have apply for some native functions)
      var v = fnPtr.apply
            ? fnPtr.apply(context, args)
            : fnPtr(args[0], args[1], args[2], args[3], args[4]);

      return ensureSafeObject(v, parser.text);
}

处理代码映射对象字面量属性到外部函数参数并调用函数。

虽然没有必要一定去理解如何使用&本地作用域属性,但是去深入发掘AngularJS在背后做了一些什么总是一件有趣的事情。

结尾

从上面可以看到&的传参过程还是有点困难的。然而一旦学会了如何使用,整个过程其实并不算太难用。

此文章由冰翼翻译自 asp.net, 原作者 Dan Wahlin

阅读全文 »

Jerry Bendy 发布于 04月03, 2016

【译】创建自定义angularJS指令(二)- 独立作用域

AngularJs


  1. 基础
  2. 独立作用域
  3. 独立作用域和函数参数
  4. transclude与restrict
  5. link函数
  6. 使用控制器
  7. Creating a Unique Value Directive using $asyncValidators

在这个系列的第一篇文章中介绍了AngularJS自定义指令以及一些简单的例子,这篇文章我们去了解下AngularJS的独立作用域,以及独立作用域在创建自定义指令时有多重要。

什么是独立作用域?

默认情况下,指令是可以直接访问父作用域中的属性的。例如,下面的指令依靠父作用域来输出一个自定义对象的namestreet属性:

angular.module('directivesModule').directive('mySharedScope', function () {
    return {
        template: 'Name: {{customer.name}} Street: {{customer.street}}'
    };
});

虽然这可以完成工作,但在实际使用中你必须要知道和这个指令相关的父作用域的很多信息来确保指令能够正常工作,或者仅仅使用ngInclude和HTML模板来完成同样的事情(这在上一篇文章中已经讨论过了)。这样做的问题在于如果父作用域作了一点改变,这个指令很可能就不再有用了。

如果你想创建一个可重用的指令,你当然不能让指令去依赖父作用域的任何属性而是使用独立作用域来代替它们。这是一个对比共享作用域独立作用域的示意图:

共享作用域和独立作用域对比图

通过上面的示意图可以看到共享作用域允许父作用域向下延伸到指令中。独立作用域在这种方式下是不能工作的,独立作用域就像在你的指令一圈围上一堵墙,父作用域无法直接翻过围墙访问指令内容的属性。就好像下面这样:

在指令中创建独立作用域

在指令中创建独立作用域是很简单的:只需要在指令中添加一个scope参数即可。像下面代码中所示,这会自动为指令创建一个独立的作用域。

angular.module('directivesModule').directive('myIsolatedScope', function () {
    return {
        scope: {},
        template: 'Name: {{customer.name}} Street: {{customer.street}}'
    };
});

现在作用域是独立的了,上面例子中来自父作用域中的customer对象在指令中将无法使用。当这个指令被用在下个视图中将会有以下输出(注意namestreet的值没有被输出):

Name: Street:

既然独立作用域切断了与父作用域通信的桥梁,那么我们该如何处理与父作用域之间的数据交换呢?我们可以使用@=以及&符号来定义作用域,这一眼看起来似乎有些奇怪,但熟练使用后会发现不算太糟。下面我们就来看一下这些符号如何使用。

本地作用域属性介绍

独立作用域提供3个不种的方式用来与外部作用域之间进行交互。这三种方式通过在指令的scope属性中指定不同的标识符来实现,这三个标识符分别是@=&。下面看一下它们是如何工作的。

@本地作用域属性

@被用来读取在指令外部的字符串值。例如,一个控制器可能在$scope对象上定义一个name属性,你需要在指令里面读取这个name的值,就可以使用@来完成,通过下图我们来进一步讲解:

  1. 在控制器中定义$scope.name
  2. $scope.name属性需要在指令中可读;
  3. 指令在独立作用域中定义一个本地作用域属性name(注意这个属性名可以是任意符合要求的名字,没必要与外部作用域中的相同)。使用scope: {name: '@'}即可;
  4. @字符告诉指令这个新的name属性是一个来自外部作用域的字符串值。如果外部作用域中这个name的值被修改了,指令中的这个值也会自动更新;
  5. 包含这个指令的视图可以通过name属性绑定值到指令。

下面是一个整合起来的例子,假设下面的控制器在一个app中被定义:

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

app.controller('CustomersController', ['$scope', function ($scope) {
    var counter = 0;
    $scope.customer = {
        name: 'David',
        street: '1234 Anywhere St.'
    };

    $scope.customers = [
        {
            name: 'David',
            street: '1234 Anywhere St.'
        },
        {
            name: 'Tina',
            street: '1800 Crest St.'
        },
        {
            name: 'Michelle',
            street: '890 Main St.'
        }
    ];

    $scope.addCustomer = function () {
        counter++;
        $scope.customers.push({
            name: 'New Customer' + counter,
            street: counter + ' Cedar Point St.'
        });
    };

    $scope.changeData = function () {
        counter++;
        $scope.customer = {
            name: 'James',
            street: counter + ' Cedar Point St.'
        };
    };
}]);

指令创建一个独立作用域,允许从外部作用域中绑定name属性:

angular.module('directivesModule')
.directive('myIsolatedScopeWithName', function () {
    return {
        scope: {
            name: '@'
        },
        template: 'Name: {{ name }}'
    };
});

可以像下面这样使用这个指令:

<div my-isolated-scope-with-name name="{{ customer.name }}"></div>

注意$scope.customer.name的值是如何绑定到指令独立作用域中的name属性上去的。

代码将会输出如下:

Name: David

如前面提到的,当$scope.customer.name的值改变时,指令将会立即自动作出改变。然而,如果是指令内部修改了它自己的name属性的话,外部作用域中的$scope.customer.name值是不会作出改变的。如果你需要让独立作用域中的值与外部作用域中的值保持同步,你需要使用=来代替@

有一点也比较重要的是,如果你想让指令独立作用域中的name属性与绑定到视图上的属性不同,你可以使用下面的替代语法:

angular.module('directivesModule')
.directive('myIsolatedScopeWithName', function () {
    return {
        scope: {
            name: '@someOtherName'
        },
        template: 'Name: {{ name }}'
    };
});

这样的话在指令内部将使用name属性,而在外部的数据绑定中将使用someOtherName代替name,数据绑定写法如下:

<div my-isolated-scope-with-name some-other-name="{{ customer.name }}"></div>

我一般更偏向于让独立作用域中的属性名与视图中绑定的属性名保持一致,所以我一般不使用这种写法。然而,这在某些场景下可以保持系统弹性。这在使用@=以及&定义本地作用域时都是有效的。

=本地作用域属性

@在只需要给指令传递字符串值时很方便实用,但在需要把在指令中对值的改变反映到外部作用域时却无能为力。在需要创建在指令的独立作用域和外部作用域中的双向绑定时,你可以使用=字符,如下图:

  1. 在控制器中定义$scope.person对象;
  2. $scope.person对象需要通过创建双向绑定的方式传入到指令中;
  3. 指令创建一个自定义本地的独立作用域属性customer,通过使用scope: {customer: '='}完成;
  4. =告诉指令传入指令本地作用域中的对象需要使用双向绑定的方式。如果外部作用域中的属性值变动,指令本地作用域中的值也会自动更新;如果指令中修改了这个值,外部作用域中对应的也会同步被修改;
  5. 指令内的视图模板现在可以绑定到独立域的customer属性。

下面是一个使用=的例子:

angular.module('directivesModule').directive('myIsolatedScopeWithModel', function () {
    return {
        scope: {
            customer: '=' // 双向数据绑定
        },
        template: '<ul><li ng-repeat="prop in customer">{{ prop }}</li></ul>'
    };
});

在这个例子中,指令使用一个对象作为customer属性的值,并且使用ngRepeat遍历customer对象的所有属性最后将其输出到<li>元素中。

使用下面的方式给指令传递数据:

<div my-isolated-scope-with-model customer="customer"></div>

需要注意下,在使用=本地作用域属性时你不能像使用@时那样使用{{ customer }},而是直接使用属性名(不需要双花括号)。在上面的例子中,customer对象被直接放在的customer属性里。指令使用ngRepeat遍历customer对象的所有属性并输出它们。将会输出以下内容:

  • David
  • 1234 Anywhere St.

&本地作用域属性

在学习使用&之前你需要先了解如何使用@本地作用域属性传递一个字符串值给指令,并且知道如何通过=本地作用域属性完成指令与外部作用域中对象的双向绑定。最后一个本地作用域属性是使用&字符来绑定一个外部函数。

&本地作用域属性允许指令调用方传递一个可被指令内部调用的函数。例如,假设你在写一个指令,终端用户点击指令中的一个按钮并需要在控制器中触发一个事件。你不能把点击事件硬编码在指令的代码内部,这样的话外部的控制器就无法知道指令内部到底发生了什么。在需要时触发一个事件可以很好的解决这个问题(使用$emit$broadcast),但是控制器需要知道具体侦听的事件名是什么所以也不是最优的。

更好的方法是让指令的消费者传递给指令一个在需要时可以被调用的函数。每当指令检测到指定的操作(例如检测当用户点击一个按钮)时它可以调用传递给它的函数。这种方式指令的消费者拥有100%的控制权,能完全知道指令中发生了什么,并委托控制函数传入指令。下面是一张简易示意图:

  1. 在控制器中定义一个叫做$scope.click的函数;
  2. $scope.click函数需要传入到指令中,目的是使指令在按钮点击时可以调用这个函数;
  3. 指令创建一个叫做action的自定义本地作用域属性。使用scope: {action: '&'}可以做到。在这个例子中,action仅仅相当于click的一个别名。当action被调用,click也会被调用;
  4. &字符从根本上来说相当于: “嘿,给我一个函数我可以在指令中发生某些事件时调用它”;
  5. 指令中的模板可以包含一个按钮,当按钮被点击时,action(外部函数的引用)函数将会被调用。

下面是一个使用&的例子:

angular.module('directivesModule')
.directive('myIsolatedScopeWithModelAndFunction', function () {
    return {
        scope: {
            datasource: '=',
            action: '&'
        },
        template: '<ul><li ng-repeat="prop in datasource">{{ prop }}</li></ul> ' +
                  '<button ng-click="action()">Change Data</button>'
    };
});

需要注意的是下面的来自指令模板代码引用到action本地作用域函数并且在按钮被点击时调用。

<button ng-click="action()">Change Data</button>

下面是使用这个指令的例子。当然更建议为指令起一个短一点的名字。

<div my-isolated-scope-with-model-and-function 
     datasource="customer" 
     action="changeData()">
</div>

被传入到指令action属性的changeData()函数在控制器中定义,控制器的定义和文章前面的一样,changeData()函数定义如下:

$scope.changeData = function () {
      counter++;
      $scope.customer = {
          name: 'James',
          street: counter + ' Cedar Point St.'
      };
};

结尾

在这个系列的文章中你将会看到一些关键点,如模板、独立作用域、本地作用域属性等。创建独立作用域只需要在指令定义中添加一个scope属性,值为一个对象即可。一共有三种本地作用域属性可用,分别是:

  • @ 用来传递一个字符串值到指令
  • = 用于创建一个双向绑定的对象
  • & 允许传入一个可被指令内部调用的函数

译者注

scope属性的值可以为一个bool型,值为false时不使用独立作用域,和不写此属性没区别。

scope中定义的属性名要使用驼峰命名的方式,而在模板中使用的时候要使用连字符语法,假设有一个指令叫datePickerscope部分定义如下:

scope: {
    isOpen: "=",
    currentDate: "=",
    onChange: "&"
}

视图中使用方式如下(假设引号里面的函数和作用域属性是已经在控制器中定义的):

<div date-picker
        is-open="openState"
        current-date="currentDate"
        on-change="dateChange()"
        ></div>

另外,如果scope中的一些属性是可选的(如上面例子中,isOpen默认为false,指令的使用者可以选择不传递这个属性),在使用这个指令的时候AngularJS就会报错,也就是说scope定义的属性在调用指令时都需要被传递(不传递会报错,但不影响程序运行)。解决这个问题的话可以在可选参数后面加一个问号?标识这个属性是可选的,修改后的指令scope部分如下:

scope: {
    isOpen: "=?"// 注意这里的问号,指定这个参数是可选的
    currentDate: "=",
    onChange: "&"
}

此文章由冰翼翻译自 asp.net, 原作者 Dan Wahlin

阅读全文 »

Jerry Bendy 发布于 04月01, 2016

【译】创建自定义angularJS指令(一)- 基础

AngularJs


  1. 基础
  2. 独立作用域
  3. 独立作用域和函数参数
  4. transclude与restrict
  5. link函数
  6. 使用控制器
  7. Creating a Unique Value Directive using $asyncValidators

AngularJS提供了很多指令可以帮助我们操作DOM、处理事件、数据绑定、绑定控制器与作用域(ngView)等等。例如ngClickngShowngHidengRepeat以及其它很多AngularJS核心的指令都可以帮助我们很轻松的使用这个框架。

虽然内置的指令已经覆盖了大部分的使用场景,但在实际使用中为了简化操作或组件重用等我们经常需要创建自己的指令。在这个系列的文章中我将一步步带你了解AngularJS指令是如何工作的,以及如何开始使用/创建它们。

在这个系列的文章中我们假定你已经知道指令是什么并且知道如何使用它们。如果你还不知道指令如何使用可以点击这里了解一些基本的用法。

编写AngularJS指令难吗?

AngularJS的指令对第一次接触它的人来说可能会吓一跳。它有很多选项,有一些复杂的特性,对第一次使用来说确实有些挑战。一旦你对它有一些了解你会发现其实并没有那么糟。如果比喻成乐器的话,当你第一次弹钢琴或吉他时你会感觉无从下手以及难以驾驭,然而在经过一段时间必要的练习后你会慢慢开始熟练甚至弹出一些优美的曲子。

开始自定义指令

为什么需要自定义指令?想一下如果你正在执行一项任务:把客户数据的集合转换成指定的格式输出到一个表格中——你当然可以选择直接添加DOM来完成,但这样做的话会使测试和控制变得困难而且使关注分离,这在AngularJS中是很不好的实现,你肯定不想这么做。作为替代方案,你应该自定义一个指令来完成上面的操作。另一方面,你可能有一些数据绑定会多次在不同的视图中出现并且你想重用这些数据绑定。当使用ngInclude载入一个子视图时,指令仍然能很好的工作。当然,指令还有很多实用的场景,上面说的也只是表面。

让我们直接来看一个基本的指令的例子。假设我们在程序中定义了以下模块和控制器:

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

app.controller('CustomersController', ['$scope', function ($scope) {
    var counter = 0;
    $scope.customer = {
        name: 'David',
        street: '1234 Anywhere St.'
    };

    $scope.customers = [
        {
            name: 'David',
            street: '1234 Anywhere St.'
        },
        {
            name: 'Tina',
            street: '1800 Crest St.'
        },
        {
            name: 'Michelle',
            street: '890 Main St.'
        }
    ];

    $scope.addCustomer = function () {
        counter++;
        $scope.customers.push({
            name: 'New Customer' + counter,
            street: counter + ' Cedar Point St.'
        });
    };

    $scope.changeData = function () {
        counter++;
        $scope.customer = {
            name: 'James',
            street: counter + ' Cedar Point St.'
        };
    };
}]);

比方说,我们发现自己写了一个数据绑定,类似于下面这样的代码,在整个程序中一遍又一遍的出现:

Name: {{customer.name}} 
<br />
Street: {{customer.street}}

一种重用方法是把这部分HTML写在一个子视图中(在这里我们命名为myChildView.html),并且在父视图中使用ngInclude来使用它。这使得myChildView.html在程序中得到重用。

<div ng-include="'myChildView.html'"></div>

虽然这能够完成任务,显然另一种更好的方案是把数据绑定表达式写到一个自定义指令中。要创建一个指令,首先需要创建一个指令所属的目标模块(module),并在模块上调用directive()方法。directive()方法有一个名称和一个函数作为参数,以下是一个简单的指令嵌入数据绑定表达式的例子。

angular.module('directivesModule')
.directive('mySharedScope', function () {
    return {
        template: 'Name: {{customer.name}}<br /> Street: {{customer.street}}'
    };
});

这是不是说可以使用自定义指令来代替ngInclude指令载入子视图? 不止如此。指令可以通过很少一部分代码来完成很多功能,它可以使DOM与业务逻辑之间的关系变得更简单。下面是一个简单的把mySharedScope指令绑定到一个<div>元素上的例子:

<div my-shared-scope></div>

当指令执行后将会输出下面的基于控制器中的数据:

Name: David
Street: 1234 Anywhere St.

有一点你可能会引起注意的是mySharedScope指令在视图中被引用的名字是my-shared-scope。为什么是这样?其实指令在命名时是使用的驼峰命名法,而引用时使用连字符方式。例如,当你使用ngRepeat指令时,实际的连字符写法是ng-repeat

在这个指令中还有另一个有趣的事情是它总是默认继承视图的作用域(scope)。如果提前绑定控制器(CustomersController)到视图,这时作用域的customer属性中在指令中就是可用的。这种共享作用域的方式在你了解指令所处的父作用域时可以工作得很好,但是在你需要复用一个指令时你往往不能很好的了解或控制它所在的父作用域,这时我们就可以使用独立作用域。关于独立作用域的具体用法将会在后面的文章中详述。

指令的属性

在上面的mySharedScope指令中我们只是在函数中返回了一个仅包含template属性的对象字面量,这个属性被用来定义指令生成HTML的模板(在这个例子中是一个简单的绑定表达式),那么还有哪些其它可用的属性呢?

自定义指令一般会通过返回一个对象字面量来定义指令所需的属性,例如模板、控制器(如果需要的话)、DOM操作代码等等。有一些不同的属性可以被使用(你可以在这里找到完整的属性列表)。下面是一些你可能遇到的常用的比较关键的属性,以及一个简单的使用它们的例子:

angular.module('moduleName')
    .directive('myDirective', function () {
    return {
        restrict: 'EA', //E = element(元素), A = attribute(属性), C = class(类), M = comment(注释)         
        scope: {
            // @ 读取属性值, 
            // = 双向数据绑定, 
            // & 使用函数
            title: '@'         },
        template: '<div>{{ myVal }}</div>',
        templateUrl: 'mytemplate.html',
        controller: controllerFunction, // 可以在指令中嵌入自定义控制器
        link: function ($scope, element, attrs) { } // DOM操作
    }
});

以下是对部分属性的一些简要说明:

属性 描述
restrict 检测指令可用的位置(是元素、属性、CSS类中还是注释中)
scope 用来创建一个新的子作用域或独立作用域
template 用来定义指令输出的内容,可以包含HTML、数据绑定表达式,甚至包含其它指令
templateUrl 提供一个指令使用的模板的路径,也可以是一个使用<script>标签定义的模板的ID
controller 用来定义一个控制器以联系视图模板
link 主要用来处理一些DOM操作的任务

操作DOM

除了在模板中进行数据绑定操作外,指令也可以被用来操作DOM。这使用了前面提到的link函数。

link函数通常会接收3个参数(在某些情况下还会有其它参数),包含当前作用域、与指令相关联的DOM元素、以及元素上绑定的属性。下面是一个使用指令处理点击、鼠标移入、鼠标移出事件的例子:

app.directive('myDomDirective', function () {
    return {
        link: function ($scope, element, attrs) {
            element.bind('click', function () {
                element.html('You clicked me!');
            });
            element.bind('mouseenter', function () {
                element.css('background-color', 'yellow');
            });
            element.bind('mouseleave', function () {
                element.css('background-color', 'white');
            });
        }
    };
});

要想使用这个指令你需要在你的视图中添加以下代码:

<div my-dom-directive>Click Me!</div>

当鼠标移入或移出时,<div>的背景颜色将会在黄色和白色(虽然在这个例子中使用了内联样式,但使用CSS类将会更好)。当目标元素被点击,内部的HTML就会变成“You clicked me!”。指令在AngularJS中是唯一可以直接操作DOM的服务,学会使用它将会对你的学习和使用很有帮助。

格式化AngularJS指令代码

虽然mySharedScopemyDomDirective指令运行的很好,但是我更喜欢在定义指令和其它AngularJS组件时使用一些特定的格式,像下面这样:

(function () {

    var directive = function () {
        return {

        };
    };

    angular.module('moduleName')
        .directive('directiveName', directive);

}());

这段代码使用了一个自执行函数包围了所有逻辑代码以防止全局命名空间污染。使用directive变量定义指令函数,最后,在模块上调用directive()函数并传递directive变量进去。有很多技巧可以被用来格式化代码,但我比较喜欢上面这种。

总结

这是这个系列的第一篇文章,你可以了解到一些基本的指令知识并且学会如何去创建一个简单的指令。这仅仅是表面!在下一篇文章中我们将会讨论独立作用域以及不同的数据绑定方式。


此文章由冰翼翻译自 asp.net, 原作者 Dan Wahlin

阅读全文 »