AngularJS中transclude用法詳解_AngularJS

來源:互聯網
上載者:User

本文執行個體講述了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封裝的整個模板元素。

// todoadd comparing with ng-include

希望本文所述對大家AngularJS程式設計有所協助。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.