zoukankan      html  css  js  c++  java
  • 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操作。

    在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定义

     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;

    HTML

    <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也会显示添加的内容,结果如下:

    directive通讯方式2-自定义事件$broadcast及$emit

    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方法,从而过滤相关结果,可以看到所有数据都是从上到下单向流动的,组件之间都是靠数据来通信的。

  • 相关阅读:
    kvm系列之二:kvm日常管理
    kvm系列之一:构建kvm虚拟机(centos7)
    cobbler无人值守安装
    判断我们的服务器是物理机还是虚拟机
    kickstark无人值守安装
    找回密码之单用户模式
    rsync传输引起的网络延迟
    题解 P3628 【[APIO2010]特别行动队】
    题解 P3211 【[HNOI2011]XOR和路径】
    题解 POJ1094 【Sorting It All Out】
  • 原文地址:https://www.cnblogs.com/myzhibie/p/5745018.html
Copyright © 2011-2022 走看看