这里须要将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方法,从而过滤相关结果,能够看到全部数据都是从上到下单向流动的,组件之间都是靠数据来通讯的。