• AngularJS中transclude用法详解


    这篇文章主要介绍了AngularJS中transclude用法,详细分析了transclude的具体功能、使用技巧与相关注意事项,需要的朋友可以参考下

    本文实例讲述了AngularJS中transclude用法。分享给大家供大家参考,具体如下:

    Transclude - 在Angular的指令中,大家会看到有一个这样的一个配置属性,这个单词在英文字典里面也查询不到真实的意思,所以就用英文来标示它吧。如果你深入的使用angular的话,你就花很大一部分时间来创建自定义指令,那么就不可避免的要深入理解transclude。简单的讲,transclude主要完成以下工作,取出自定义指令中的内容(就是写在指令里面的子元素),以正确的作用域解析它,然后再放回指令模板中标记的位置(通常是ng-transclude标记的地方),虽然使用内建的ngTransclude对于基本的transclude操作已经足够简单,但是在文档中对这个transclude的解释还是有存在很多疑惑,比如说:

    在compile函数中接收到了一个叫transclude的参数是什么东西呢?有什么用呢?

    在控制器中也有个叫$transclude的可以通过依赖注入的服务,这又是什么呢?

    隔离作用域跟transclude有什么关系?

    属性的transclude操作

    接下来我们将一个个的解释:

    基本的transclude

    我们通过一个基本的transclude例子来讲解吧,我们现在要创建的是一个叫buttonBar的指令,用户可以通过它来添加一组button到页面上,这个指令会对不同的button进行位置的排列。以下例子css样式是使用Bootstrap框架。

    在fiddle中查看例子:http://jsfiddle.net/ospatil/A969Z/157/

    Html代码

    1 <div ng-controller="parentController">
    2   <button-bar>
    3     <button class="primary" ng-click="onPrimary1Click()">{{primary1Label}}</button>
    4     <button class="primary">Primary2</button>
    5   </button-bar>
    6 </div>

    Js代码

     1 var testapp = angular.module('testapp', []);
     2 testapp.controller('parentController', ['$scope', '$window', function($scope, $window) {
     3   console.log('parentController scope id = ', $scope.$id);
     4   $scope.primary1Label = 'Prime1';
     5   $scope.onPrimary1Click = function() {
     6     $window.alert('Primary1 clicked');
     7   };
     8 }]);
     9 testapp.directive('primary', function() {
    10   return {
    11     restrict: 'C',
    12     link: function(scope, element, attrs) {
    13       element.addClass('btn btn-primary');
    14     }
    15   }
    16 });
    17 testapp.directive('buttonBar', function() {
    18   return {
    19     restrict: 'EA',
    20     template: '<div class="span4 well clearfix"><div class="pull-right" ng-transclude></div></div>',
    21     replace: true,
    22     transclude: true
    23   };
    24 });

    我们先看下HTML标签,buttonBar指令包裹着几个button元素。而button元素也被链接上了基于class的primary指令,不要太在意这个primary指令的功能它只不过为button元素添加一些css的样式而已。现在我们来看buttonBar指令,它提供了一个transclude:true属性,同时在它的模板里面使用ng-transclude指令。在运行的过程中,Angular获取到自定义指令的内容,处理完了之后把结果放到了模板中链接上ng-transclude的div。

    transclude到多个位置

    现在我们来增强下我们的buttonBar指令的功能,我们增加了两种按钮,primary和secondary,其中primary按钮是排右边,secondary是排左边。所以要做到这个功能,它必须能够取出指令的内容,然后把它们分别添加到不同的div中,一个用来放primary按钮, 一个用来放secondary按钮。

    这样的话,默认的机制已经满足不了我们的要求,于是我们有了另外一种方法:

    设置transclude为true

    手工移动button元素到合适的div

    最后,在指令的编译或链接函数中移除原始的用来transclude操作的元素

    这种方法就是先把所有的内容插入到ng-transclude标记的元素中,然后在link函数中再找出元素的插入的元素,重新放到元素的其他地方,最后删除原来暂存内容的元素。

    在fiddle中查看例子:http://jsfiddle.net/ospatil/A969Z/158/

    Html代码

    1 <div ng-controller="parentController">
    2   <button-bar>
    3     <button class="primary" ng-click="onPrimary1Click()">{{primary1Label}}</button>
    4     <button class="primary">Primary2</button>
    5     <button class="secondary">Secondary1</button>
    6   </button-bar>
    7 </div>

    Js代码

     1 var testapp = angular.module('testapp', []);
     2 testapp.controller('parentController', ['$scope', '$window',function($scope, $window) {
     3   $scope.primary1Label = 'Prime1';
     4   $scope.onPrimary1Click = function() {
     5     $window.alert('Primary 1 clicked');
     6   }
     7 }]);
     8 testapp.directive('primary', function() {
     9   return {
    10     restrict: 'C',
    11     link: function(scope, element, attrs) {
    12       element.addClass('btn btn-primary');
    13     }
    14   }
    15 });
    16 testapp.directive('secondary', function() {
    17   return {
    18     restrict: 'C',
    19     link: function(scope, element, attrs) {
    20       element.addClass('btn');
    21     }
    22   }
    23 });
    24 testapp.directive('buttonBar', function() {
    25   return {
    26     restrict: 'EA',
    27     template: '<div class="span4 well clearfix"><div class="primary-block pull-right"></div><div class="secondary-block"></div><div class="transcluded" ng-transclude></div></div>',
    28     replace: true,
    29     transclude: true,
    30     link: function(scope, element, attrs) {
    31       var primaryBlock = element.find('div.primary-block');
    32       var secondaryBlock = element.find('div.secondary-block');
    33       var transcludedBlock = element.find('div.transcluded');
    34       var transcludedButtons = transcludedBlock.children().filter(':button');
    35       angular.forEach(transcludedButtons, function(elem) {
    36         if (angular.element(elem).hasClass('primary')) {
    37           primaryBlock.append(elem);
    38         } else if (angular.element(elem).hasClass('secondary')) {
    39           secondaryBlock.append(elem);
    40         }
    41       });
    42       transcludedBlock.remove();
    43     }
    44   };
    45 });

    虽然这种方法达到了我们的目的,但是允许默认的transclude操作,然后再人工的从DOM元素中移出不是非常有效率的。因此,我们有了compile函数中的transclude参数和控制器中的$transclude服务

    编译函数参数中的transclude

    开发者指南中给了我们以下的关于指令中编译函数的形式:

    1
    function compile(tElement, tAttrs, transclude) { ... }

    其中关于第三个参数transclude的解释是:

    transclude - A transclude linking function: function(scope, cloneLinkingFn).

    好的,现在我们利用这个函数来实现我们刚才讲到的功能,从而不需要再先暂存内容,然后再插入到其他地方。

    在fiddle中查看例子:http://jsfiddle.net/ospatil/A969Z/161/

    Html代码

    1 <div ng-controller="parentController">
    2   <button-bar>
    3     <button class="primary" ng-click="onPrimary1Click()">{{primary1Label}}</button>
    4     <button class="primary">Primary2</button>
    5     <button class="secondary">Secondary1</button>
    6   </button-bar>
    7 </div>

    Js代码

     1 var testapp = angular.module('testapp', []);
     2 testapp.controller('parentController', ['$scope', '$window', function($scope, $window) {
     3   $scope.primary1Label = 'Prime1';
     4   $scope.onPrimary1Click = function() {
     5     $window.alert('Primary 1 clicked');
     6   }
     7 }]);
     8 testapp.directive('primary', function() {
     9   return {
    10     restrict: 'C',
    11     link: function(scope, element, attrs) {
    12       element.addClass('btn btn-primary');
    13     }
    14   }
    15 });
    16 testapp.directive('secondary', function() {
    17   return {
    18     restrict: 'C',
    19     link: function(scope, element, attrs) {
    20       element.addClass('btn');
    21     }
    22   }
    23 });
    24 testapp.directive('buttonBar', function() {
    25   return {
    26     restrict: 'EA',
    27     template: '<div class="span4 well clearfix"><div class="primary-block pull-right"></div><div class="secondary-block"></div></div>',
    28     replace: true,
    29     transclude: true,
    30     compile: function(elem, attrs, transcludeFn) {
    31       return function (scope, element, attrs) {
    32         transcludeFn(scope, function(clone) {
    33           var primaryBlock = elem.find('div.primary-block');
    34           var secondaryBlock = elem.find('div.secondary-block');
    35           var transcludedButtons = clone.filter(':button');
    36           angular.forEach(transcludedButtons, function(e) {
    37             if (angular.element(e).hasClass('primary')) {
    38               primaryBlock.append(e);
    39             } else if (angular.element(e).hasClass('secondary')) {
    40               secondaryBlock.append(e);
    41             }
    42           });
    43         });
    44       };
    45     }
    46   };
    47 });

    注意到,transcludeFn函数需要一个可用的scope作为第一个参数,但是编译函数中没有可用的scope,所以这里需要在链接函数中执行transcludeFn。这种方法实际上是在link函数中同时操作编译后的DOM元素和模板元素(主要是因为transcludeFn函数中保存着指令的内容)。

    可在控制器中注入的$transclude服务

    在开发者指南中对$transclude服务是这么解释的:

    $transclude - A transclude linking function pre-bound to the correct transclusion scope: function(cloneLinkingFn).

    看看如何用在我们的例子中:

    在fiddle中查看例子:http://jsfiddle.net/ospatil/A969Z/162/

    Hmtl代码

    1 <div ng-controller="parentController">
    2   <button-bar>
    3     <button class="primary" ng-click="onPrimary1Click()">{{primary1Label}}</button>
    4     <button class="primary">Primary2</button>
    5     <button class="secondary">Secondary1</button>
    6   </button-bar>
    7 </div>

    Js代码

     1 var testapp = angular.module('testapp', []);
     2 testapp.controller('parentController', ['$scope', '$window', function($scope, $window) {
     3   $scope.onPrimary1Click = function() {
     4     alert('Primary1 clicked');
     5   };
     6   $scope.primary1Label = "Prime1"
     7 }]);
     8 testapp.directive('primary', function() {
     9   return {
    10     restrict: 'C',
    11     link: function(scope, element, attrs) {
    12       element.addClass('btn btn-primary');
    13     }
    14   }
    15 });
    16 testapp.directive('secondary', function() {
    17   return {
    18     restrict: 'C',
    19     link: function(scope, element, attrs) {
    20       element.addClass('btn');
    21     }
    22   }
    23 });
    24 testapp.directive('buttonBar', function() {
    25   return {
    26     restrict: 'EA',
    27     template: '<div class="span4 well clearfix"><div class="primary-block pull-right"></div><div class="secondary-block"></div></div>',
    28     replace: true,
    29     transclude: true,
    30     scope: {},
    31     controller: ['$scope', '$element', '$transclude', function ($scope, $element, $transclude) {
    32       $transclude(function(clone) {
    33         var primaryBlock = $element.find('div.primary-block');
    34         var secondaryBlock = $element.find('div.secondary-block');
    35         var transcludedButtons = clone.filter(':button');
    36         angular.forEach(transcludedButtons, function(e) {
    37           if (angular.element(e).hasClass('primary')) {
    38             primaryBlock.append(e);
    39           } else if (angular.element(e).hasClass('secondary')) {
    40             secondaryBlock.append(e);
    41           }
    42         });
    43       });
    44     }],
    45   };
    46 });

    同样的意思,$transclude中接收的函数里的参数含有指令元素的内容,而$element包含编译后的DOM元素,所以就可以在控制器中同时操作DOM元素和指令内容,跟上文的compile函数的实现方式有异曲同工之处,这里有几点需要注意,这个控制器应该是指令的控制器,另一个注意到上文除了第一种方法,其他的地方都没有用到ng-transclude,因为无需插入到模板中。

    Transclude 和 scope

    在开发者指南中提到了a directive isolated scope and transclude scope are siblings,这到底是什么意思呢?假如你认真看前文的例子的话,你就会发现parentController控制器创建了一个作用域,buttonBar指令在parentController下面创建了一个孤立作用域,而根据Angular文档,transclude也创建了另外一个作用域,因此指令的隔离作用域跟transclude作用域是基于同一个父作用域的兄弟作用域。

    transclude内容放入元素的属性

    实际上,你不可以这么做,但是你可以通过一种变通的方法来实现这种效果

    Html代码

     1 var testapp = angular.module('testapp', [])
     2 testapp.directive('tag', function() {
     3  return {
     4   restrict: 'E',
     5   template: '<h1><a href="{{transcluded_content}}">{{transcluded_content}}</a></h1>',
     6   replace: true,
     7   transclude: true,
     8   compile: function compile(tElement, tAttrs, transclude) {
     9     return {
    10       pre: function(scope) {
    11         transclude(scope, function(clone) {
    12          scope.transcluded_content = clone[0].textContent;
    13         });
    14       }
    15     }
    16   }
    17  }
    18 });

    这里没有操作DOM元素,只是把元素的文本内容复制给了作用域属性,然后在通过作用域传给属性。

    另外要注意的是,这里的clone参数是jquery或angular.element封装的整个模板元素。

    var testapp = angular.module('testapp', []);
    testapp.controller('parentController', ['$scope', '$window', function($scope, $window) {
      $scope.primary1Label = 'Prime1';
      $scope.onPrimary1Click = function() {
        $window.alert('Primary 1 clicked');
      }
    }]);
    testapp.directive('primary', function() {
      return {
        restrict: 'C',
        link: function(scope, element, attrs) {
          element.addClass('btn btn-primary');
        }
      }
    });
    testapp.directive('secondary', function() {
      return {
        restrict: 'C',
        link: function(scope, element, attrs) {
          element.addClass('btn');
        }
      }
    });
    testapp.directive('buttonBar', function() {
      return {
        restrict: 'EA',
        template: '<div class="span4 well clearfix"><div class="primary-block pull-right"></div><div class="secondary-block"></div></div>',
        replace: true,
        transclude: true,
        compile: function(elem, attrs, transcludeFn) {
          return function (scope, element, attrs) {
            transcludeFn(scope, function(clone) {
              var primaryBlock = elem.find('div.primary-block');
              var secondaryBlock = elem.find('div.secondary-block');
              var transcludedButtons = clone.filter(':button');
              angular.forEach(transcludedButtons, function(e) {
                if (angular.element(e).hasClass('primary')) {
                  primaryBlock.append(e);
                } else if (angular.element(e).hasClass('secondary')) {
                  secondaryBlock.append(e);
                }
              });
            });
          };
        }
      };
    });
  • 相关阅读:
    虚方法和抽象方法
    c#_实现FTP方法(一) FtpWebRequest
    sql server 分页
    5ucms进阶
    图片处理函数
    [转]C++11新特性:Lambda函数
    [STL]for_each详细用法[转]
    [算法]hash table 与 hash map 实现
    [算法]字典树
    [STL]vector的使用[转]
  • 原文地址:https://www.cnblogs.com/menyiin/p/angular.html
Copyright © 2020-2023  润新知