AngularJS中transclude用法详解


Posted in Javascript onNovember 03, 2016

本文实例讲述了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/

<div ng-controller="parentController">
  <button-bar>
    <button class="primary" ng-click="onPrimary1Click()">{{primary1Label}}</button>
    <button class="primary">Primary2</button>
  </button-bar>
</div>

JS:

var testapp = angular.module('testapp', []);
testapp.controller('parentController', ['$scope', '$window', function($scope, $window) {
  console.log('parentController scope id = ', $scope.$id);
  $scope.primary1Label = 'Prime1';
  $scope.onPrimary1Click = function() {
    $window.alert('Primary1 clicked');
  };
}]);
testapp.directive('primary', function() {
  return {
    restrict: 'C',
    link: function(scope, element, attrs) {
      element.addClass('btn btn-primary');
    }
  }
});
testapp.directive('buttonBar', function() {
  return {
    restrict: 'EA',
    template: '<div class="span4 well clearfix"><div class="pull-right" ng-transclude></div></div>',
    replace: true,
    transclude: true
  };
});

我们先看下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/

<div ng-controller="parentController">
  <button-bar>
    <button class="primary" ng-click="onPrimary1Click()">{{primary1Label}}</button>
    <button class="primary">Primary2</button>
    <button class="secondary">Secondary1</button>
  </button-bar>
</div>

JS:

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 class="transcluded" ng-transclude></div></div>',
    replace: true,
    transclude: true,
    link: function(scope, element, attrs) {
      var primaryBlock = element.find('div.primary-block');
      var secondaryBlock = element.find('div.secondary-block');
      var transcludedBlock = element.find('div.transcluded');
      var transcludedButtons = transcludedBlock.children().filter(':button');
      angular.forEach(transcludedButtons, function(elem) {
        if (angular.element(elem).hasClass('primary')) {
          primaryBlock.append(elem);
        } else if (angular.element(elem).hasClass('secondary')) {
          secondaryBlock.append(elem);
        }
      });
      transcludedBlock.remove();
    }
  };
});

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

编译函数参数中的transclude

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

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

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

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

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

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

<div ng-controller="parentController">
  <button-bar>
    <button class="primary" ng-click="onPrimary1Click()">{{primary1Label}}</button>
    <button class="primary">Primary2</button>
    <button class="secondary">Secondary1</button>
  </button-bar>
</div>

JS:

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);
            }
          });
        });
      };
    }
  };
});

注意到,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/

<div ng-controller="parentController">
  <button-bar>
    <button class="primary" ng-click="onPrimary1Click()">{{primary1Label}}</button>
    <button class="primary">Primary2</button>
    <button class="secondary">Secondary1</button>
  </button-bar>
</div>

JS:

var testapp = angular.module('testapp', []);
testapp.controller('parentController', ['$scope', '$window', function($scope, $window) {
  $scope.onPrimary1Click = function() {
    alert('Primary1 clicked');
  };
  $scope.primary1Label = "Prime1"
}]);
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,
    scope: {},
    controller: ['$scope', '$element', '$transclude', function ($scope, $element, $transclude) {
      $transclude(function(clone) {
        var primaryBlock = $element.find('div.primary-block');
        var secondaryBlock = $element.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);
          }
        });
      });
    }],
  };
});

同样的意思,$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内容放入元素的属性

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

var testapp = angular.module('testapp', [])
testapp.directive('tag', function() {
 return {
  restrict: 'E',
  template: '<h1><a href="{{transcluded_content}}">{{transcluded_content}}</a></h1>',
  replace: true,
  transclude: true,
  compile: function compile(tElement, tAttrs, transclude) {
    return {
      pre: function(scope) {
        transclude(scope, function(clone) {
         scope.transcluded_content = clone[0].textContent;
        });
      }
    }
  }
 }
});

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

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

// todo
add comparing with ng-include

希望本文所述对大家AngularJS程序设计有所帮助。

Javascript 相关文章推荐
基于jquery的固定表头和列头的代码
May 03 Javascript
jquery实现点击向下展开菜单项(伸缩导航)效果
Aug 22 Javascript
JavaScript中通过提示框跳转页面的方法
Feb 14 Javascript
jQuery获取多种input值的简单实现方法
Jun 20 Javascript
d3.js中冷门却实用的内置函数总结
Feb 04 Javascript
微信小程序 常用工具类详解及实例
Feb 15 Javascript
JavaScript队列的应用实例详解【经典数据结构】
Apr 12 Javascript
JavaScript函数表达式详解及实例
May 05 Javascript
使用bootstraptable插件实现表格记录的查询、分页、排序操作
Aug 06 Javascript
vue表单绑定实现多选框和下拉列表的实例
Aug 12 Javascript
原生JS实现的自动轮播图功能详解
Dec 28 Javascript
JavaScript碎片—函数闭包(模拟面向对象)
Mar 13 Javascript
基于Vue2的移动端开发环境搭建详解
Nov 03 #Javascript
AngularJS控制器之间的通信方式详解
Nov 03 #Javascript
最细致的vue.js基础语法 值得收藏!
Nov 03 #Javascript
AngularJS创建自定义指令的方法详解
Nov 03 #Javascript
JavaScript遍历Json串浏览器输出的结果不统一问题
Nov 03 #Javascript
3种不同的ContextMenu右键菜单实现代码
Nov 03 #Javascript
利用纯Vue.js构建Bootstrap组件
Nov 03 #Javascript
You might like
PHP实现获取FLV文件的时间
2015/02/10 PHP
php实现跨域提交form表单的方法【2种方法】
2016/10/17 PHP
php arsort 数组降序排序详细介绍
2016/11/17 PHP
php 从指定数字中获取随机组合的简单方法(推荐)
2017/04/05 PHP
PHP简单获取上月、本月、近15天、近30天的方法示例
2017/07/03 PHP
详解PHP中的8个魔术常量
2020/07/06 PHP
PHP文件打开关闭及读写操作示例解析
2020/08/06 PHP
js脚本学习 比较实用的基础
2006/09/07 Javascript
jQuery源码分析-02正则表达式 RegExp 常用正则表达式
2011/11/14 Javascript
Javascript 实现的数独解题算法网页实例
2013/10/15 Javascript
jquery 通过name快速取值示例
2014/01/24 Javascript
正则表达式优化JSON字符串的技巧
2015/12/24 Javascript
js实现文字垂直滚动和鼠标悬停效果
2015/12/31 Javascript
jQuery EasyUI框架中的Datagrid数据表格组件结构详解
2016/06/09 Javascript
JS实现刷新父页面不弹出提示框的方法
2016/06/22 Javascript
将html页面保存成图片,图片写入pdf的实现方法(推荐)
2016/09/17 Javascript
纯javascript版日历控件
2016/11/24 Javascript
网站申请不到支付宝接口、微信接口,免接口收款实现方式几种解决办法
2016/12/14 Javascript
浅谈Vue 初始化性能优化
2017/08/31 Javascript
微信小程序常用的3种提示弹窗实现详解
2019/09/19 Javascript
python 编程之twisted详解及简单实例
2017/01/28 Python
Python操作MySQL数据库的两种方式实例分析【pymysql和pandas】
2019/03/18 Python
python变量的作用域是什么
2020/05/26 Python
在pycharm中创建django项目的示例代码
2020/05/28 Python
无需压缩软件,用python帮你操作压缩包
2020/08/17 Python
举例讲解Python装饰器
2020/12/24 Python
竞争上岗演讲稿
2014/01/05 职场文书
研究生考核个人自我鉴定
2014/03/27 职场文书
民主生活会整改措施(党员)
2014/09/18 职场文书
工作疏忽检讨书500字
2014/10/26 职场文书
风雨哈佛路观后感
2015/06/03 职场文书
走进毛泽东观后感
2015/06/04 职场文书
评估“风险”创业计划的几大要点
2019/08/12 职场文书
教你使用Pandas直接核算Excel中快递费用
2021/05/12 Python
HTML5 语义化标签(移动端必备)
2021/08/23 HTML / CSS
浅谈Redis变慢的原因及排查方法
2022/06/21 Redis