当前位置: 移动技术网 > IT编程>脚本编程>AngularJs > AngularJS中transclude用法详解

AngularJS中transclude用法详解

2018年05月08日  | 移动技术网IT编程  | 我要评论
本文实例讲述了angularjs中transclude用法。分享给大家供大家参考,具体如下: transclude - 在angular的指令中,大家会看到有一个这样的一

本文实例讲述了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程序设计有所帮助。

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网