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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM