Angular1.x組件通信方式總結

Angular1開發模式

這裏須要將Angular1分爲Angular1.5以前和Angular1.5兩個不一樣的階段來說,二者雖然同屬Angular1,可是在開發模式上仍是有較大區別的。在Angular1.4及之前,主要是基於HTML的,將全部view劃分爲不一樣的HTML片斷,經過路由,transclude,include等方式,按照用戶行爲切換顯示不一樣界面。對於每一個template內部來說,能夠是純HTML,也能夠是自定義的directive。directive之間能夠有層級關係,也能夠沒有層級關係。在Angular1.5中,引入了Component API,鼓勵使用單向數據流及Component tree 開發模式,在這種狀況下,整個應用徹底是基於組件的,除了root Component,其餘Component都有本身的父級組件,組件之間主要是靠由上到下的單向數據流動來通訊的,在這種模式下,directive不容許有本身的template,只可以來封裝DOM操做。html

在Angular1中提供了不少組件(指令)之間的通信方式,本文主要來將這些方式一一列舉出來,便於總結和使用。前端

directive通信方式1-共享Service數組

在Angular1中,全部service都是單例的,這意味着一旦應用啓動以後,在內存中會維護一些註冊的service實例,在代碼中任意地方對這些service的操做,都會致使其發生改變,而其餘controller或者directive獲取該service的值,也是發生改變以後的值。在這種狀況下,一個directive對service進行寫操做,另一個directive對它進行讀操做,二者就能經過該service進行通信。前端框架

service定義框架

 1 class HomeService {
 2   constructor() {
 3     this.names = [];
 4   }
 5   addName(name){
 6     this.names.push(name);
 7   }
 8 }
 9
10 export default HomeService;

directiveA定義dom

 1 function aDirective(homeService) {
 2     "ngInject";
 3     return {
 4         restrict: 'E',
 5         template: `name:<input type='text' ng-model='showValue'>
 6   <br><button ng-click="addName()">addName</button>`,
 7         link: (scope, element, attrs) => {
 8             scope.addName = () => {
 9                 if (scope.showValue) {
10                     homeService.addName(scope.showValue);
11                 }
12             }
13         }
14     };
15 }
16 
17 export default aDirective;

directiveB定義函數

 1 function bDirective(homeService) {
 2     "ngInject";
 3     return {
 4         restrict: 'E',
 5         template: `<ul>
 6             <li ng-repeat="list in lists">{{list}}</li>
 7         </ul>`,
 8         link: (scope, element, attrs) => {
 9             scope.lists=homeService.names;
10         }
11     };
12 }
13 
14 export default bDirective;

HTMLui

<main class="home">
  <h2>共享service實現Directive通訊</h2>
  <div>
    <a-directive></a-directive>
    <b-directive></b-directive>
  </div>
</main>

在這裏,咱們在homeService中定義了一個addName方法,用來給該service的names中添加元素,而後讓directiveA調用addName方法添加元素,隨着names屬性的變化,directiveB的list也會顯示添加的內容,結果以下:this

directive通信方式2-自定義事件$broadcast及$emitspa

Angular1提供了scope之間事件的傳播機制,使用scope.$emit能夠往父級scope傳播自定義事件並攜帶數據,使用scope.$broadcast能夠給全部子scope廣播事件和數據,全部須要接收到的scope須要使用scope.$on(eventName,callback)方法來監聽事件。該機制頗有用,可是我認爲能不用就不用,主要是在實際開發中大多數是使用$rootScope來廣播事件,每廣播一次,整個scope下面的$$listeners都要去檢測是否有對應的事件監遵從而執行,若是scope層級較深,那麼效率不會很高。除此以外,在各類directive,controller中監聽自定義事件會致使混亂,代碼很差去追蹤,並且有可能引起命名衝突。

directiveA定義

 1 function aDirective(homeService,$rootScope) {
 2     "ngInject";
 3     return {
 4         restrict: 'E',
 5         template: `name:<input type='text' ng-model='showValue' class='about'>
 6   <br><button ng-click="addName()">addName</button>`,
 7         link: (scope, element, attrs) => {
 8             scope.addName = () => {
 9                 if(scope.showValue){
10                     $rootScope.$broadcast('addName',scope.showValue);
11                 }        
12             }
13         }
14     };
15 }
16 
17 export default aDirective;

directiveB定義

 1 function bDirective(homeService) {
 2     "ngInject";
 3     return {
 4         restrict: 'E',
 5         template: `<ul>
 6             <li ng-repeat="list in lists">{{list}}</li>
 7         </ul>`,
 8         link: (scope, element, attrs) => {
 9             scope.lists=[];
10             scope.$on('addName',(...params)=>{
11                 scope.lists.push(params[1]);
12             });
13         }
14     };
15 }
16 
17 export default bDirective;

HTML

1 <section>
2   <event-adirective class="about"></event-adirective>
3   <event-bdirective></event-bdirective>
4 </section>

在這裏,DirectiveA使用rootScope來廣播事件,directiveB來監聽事件,而後將事件傳遞的參數添加到lists數組當中去,結果同上。

directive通信方式3-在link函數中使用attrs通信

每一個directive在定義的時候都有一個link函數,函數簽名的第三個參數是attrs,表明在該directive上面的全部atrributes數組,attrs提供了一些方法,比較有用的是$set和$observe,前者能夠自定義attr或修改已經有的attr的值,後者能夠監聽到該值的變化。利用這種方式,咱們可讓在位於同一個dom元素上的兩個directive進行通信,由於它們之間共享一個attrs數組。

directiveA定義

 1 function aDirective($interval) {
 2     "ngInject";
 3     return {
 4         restrict: 'A',
 5         link: (scope, element, attrs) => {
 6             let deInterval=$interval(()=>{
 7               attrs.$set('showValue', new Date().getTime());
 8             },1000);
 9             scope.$on('$destroy',()=>{
10                 $interval.cancel(deInterval);
11             });
12         }
13     };
14 }
15 
16 export default aDirective;

directiveB定義

 1 function bDirective() {
 2     "ngInject";
 3     return {
 4         restrict: 'E',
 5         template: `<span>{{time|date:'yyyy-MM-dd HH:mm:ss'}}</span>`,
 6         link: (scope, element, attrs) => {
 7             attrs.$observe('showValue', (newVal)=>{
 8                 scope.time=newVal;
 9                 console.log(newVal);
10             });
11         }
12     };
13 }
14 
15 export default bDirective;

HTML

1 <div>
2   <h2>{{ $ctrl.name }}</h2>
3   <directive-b directive-a></directive-b>
4 </div>

這裏讓directiveA不斷修改showValue的值,讓directiveB來observe該值並顯示在template中,結果以下:

directive通信方式4-使用directive的Controller+require來進行通信

在directive中,能夠在本身的controller中定義一些方法或屬性,這些方法或者屬性能夠在其餘directive中使用require來引入目標directive,而後在本身的link函數中的第四個參數中就能夠拿到目標directive的實例,從而操做該實例,進行二者通信。

directiveA定義

 1 function aDirective() {
 2     "ngInject";
 3     return {
 4         restrict: 'E',
 5         transclude: true,
 6         scope: {},
 7         template: `<div><div ng-transclude></div><ul>
 8             <li ng-repeat="list in lists">{{list}}</li>
 9         </ul></div>`,
10         controller: function($scope) {
11             "ngInject";
12             $scope.lists  = [];
13              this.addName = (item) => {
14                     $scope.lists.push(item);
15             }
16         }
17     };
18 }
19 
20 export default aDirective;

directiveB定義

 1 function bDirective() {
 2     "ngInject";
 3     return {
 4         restrict: 'E',
 5         require:'^^aCtrlDirective',
 6         template: `name:<input type='text' ng-model='showValue'>
 7   <br><button ng-click="addName()">addName</button>
 8        `,
 9         link: (scope, element, attrs,ctrls) => {
10             scope.addName=function(){
11                 if(scope.showValue){
12                     ctrls.addName(scope.showValue);
13                 }       
14             }
15         }
16     };
17 }
18 
19 export default bDirective;

HTML

1  <a-ctrl-directive>
2   <div>
3     <div>
4       <b-ctrl-directive class='ctrls'></b-ctrl-directive>
5     </div>
6     </div>
7   </a-ctrl-directive>

在directiveA中定義本身的controller,暴露addName方法給外部,而後再directiveB中require引用directiveA,在directiveB的link函數中調用addName方法,從而操做directiveA的lists數組,lists並無在directiveB中定義。

Component通信方式-單向數據流+$onChanges hook方法

在Angular1.5以後,爲了更好的升級到Angular2,引入了Component API,並鼓勵使用單向數據流加組件樹開發模式,這和以前的directive相比,開發模式發生了比較大的變化。雖然component自己僅僅是directive語法糖,可是其巨大意義在於讓開發者脫離以前的HTML爲核心,轉而適應組件樹開發方式,這種模式也是目前主流前端框架都鼓勵使用的模式,如Angular2,React及Vue。在這種模式下,上述幾種通信方式仍然有效,可是並非最佳實踐,Component因爲天生具備層級關係,因此更鼓勵使用單向數據流+生命週期Hook方法來進行通信。

這裏咱們模擬一個查詢的場景,使用三個組件來完成該功能,最外層的一個searchBody組件,用來做爲該功能的根組件,searchFiled組件用來接收用戶輸入,並提供查詢按鈕,searchList組件用來顯示查詢結果。

searchList定義(爲了便於查看,我將該組件的HTMl及核心js一塊兒展現)

 1 import template from './searchList.html';
 2 import controller from './searchList.controller';
 3 
 4 let searchListComponent = {
 5   restrict: 'E',
 6   bindings: {
 7     searchMessages:'<',
 8     searchText:'<'
 9   },
10   template,
11   controller
12 };
13 
14 class SearchListController {
15     constructor() {
16         this.name = 'searchList';
17     }
18     $onInit() {
19         this.initialMessages = angular.copy(this.searchMessages);
20     }
21     $onChanges(changesObj) {
22         if (changesObj.searchText && changesObj.searchText.currentValue) {
23             this.searchMessages = this.initialMessages.filter((message) => {
24                 return message.key.indexOf(this.searchText) !== -1;
25             })
26         }
27     }
28 
29 
30 }
31 
32 export default SearchListController;
33 
34 <div>
35   <ul class="search-ul">
36     <li ng-repeat="item in $ctrl.searchMessages">
37       {{item.key+"-"+item.val}}
38     </li>
39   </ul>
40 </div>

這裏定義了一個controller,將searchList的全部邏輯都放在該controller中,6-9行在component定義中使用單向綁定<來定義來將其父組件上的數據綁定到controller上。18-20行在$onInit方法中初始化保留一份原始數據供查詢使用。21-27行使用Angular1.5中Component的生命週期hook方法,當父組件中綁定的數據發生變化以後,都會觸發該方法,該方法有一個參數,changesObj表明本次發生變化的對象,咱們須要監聽changesObj.searchText的變化,並按照searchText的最新值來過濾searchMessages.

searchField定義

 1 import template from './searchField.html';
 2 import controller from './searchField.controller';
 3 
 4 let searchFieldComponent = {
 5   restrict: 'E',
 6   bindings: {},
 7   template,
 8   controller,
 9   require:{
10     searchBody:'^searchBody'
11   }
12 };
13 
14 class SearchFieldController {
15   constructor() {
16     this.searchWords = '';
17   }
18  
19   doSearch(){
20     if(!this.searchWords) return;
21     this.searchBody.doSearch(this.searchWords);
22   }
23 
24 }
25 
26 export default SearchFieldController;
27 
28 
29 <div>
30   <input type="text" name="" value="" ng-model="$ctrl.searchWords">
31   <button ng-click="$ctrl.doSearch()">search</button>
32 </div>

searchField的做用是使用input接受用戶輸入的查詢參數,而後在點擊button的時候調用searchBody的doSearch方法,來通知最外層的searchBody更新searchText。

searchBody定義

 1 import template from './searchBody.html';
 2 import controller from './searchBody.controller';
 3 
 4 let searchBodyComponent = {
 5   restrict: 'E',
 6   bindings: {},
 7   template,
 8   controller
 9 };
10 class SearchBodyController {
11   constructor() {
12     this.searchTitle = 'searchBody';
13   }
14   $onInit(){
15     this.messages=[
16       {key:"erer",val:"ererererererere"},
17       {key:"1111",val:"111111111111111"},
18       {key:"2222",val:"222222222222222"},
19       {key:"3333",val:"333333333333333"},
20       {key:"4444",val:"444444444444444"},
21       {key:"5555",val:"555555555555555"},
22       {key:"6666",val:"666666666666666"},
23       {key:"7777",val:"777777777777777"},
24       {key:"8888",val:"888888888888888"}
25     ]
26   }
27 
28   doSearch(text){
29     this.searchText=text;
30   }
31 }
32 
33 export default SearchBodyController;
34 
35 <div>
36   <h1>{{ $ctrl.searchTitle }}</h1>
37   <search-field></search-field>
38   <search-list search-messages="$ctrl.messages" search-text="$ctrl.searchText"></search-list>
39 </div>

在上述代碼中的37-38行,引用searchField和searchList兩個組件,並將searchBody的messages及searchText做爲最初的數據源傳遞給searchList組件,而後再searchField中點擊查詢按鈕,會調用searchBody的doSearch方法,改變searchBody的searchText的值,而後觸發searchList中的$onChanges方法,從而過濾相關結果,能夠看到全部數據都是從上到下單向流動的,組件之間都是靠數據來通訊的。

相關文章
相關標籤/搜索