本文執行個體講述了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程式設計有所協助。