在 AngularJS 组件中从父级到子级通信事件

Posted

技术标签:

【中文标题】在 AngularJS 组件中从父级到子级通信事件【英文标题】:Communicating Events from Parent to Child in AngularJS Components 【发布时间】:2016-09-23 04:27:18 【问题描述】:

在我正在进行的新项目中,我开始使用组件而不是指令。

但是,我遇到了一个问题,我找不到具体的标准方法。

将事件从孩子通知给父母很容易,您可以在下面的我的 plunkr 上找到它,但是将事件从父母通知给孩子的正确方法是什么?

Angular2 似乎通过使用以下方式解决了这个问题:https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-child-local-var 但我不认为有可能像#timer 示例那样定义指向子组件的“指针”

为了尽可能轻松地转换为 Angular2,我想避免:

事件发射(从作用域发射和广播) 使用来自子级的要求(然后向父级添加回调..UGLY) 使用单向绑定,将作用域注入子级,然后“监视”此属性.. 更难看

示例代码:

var app = angular.module('plunker', []);

app.controller('RootController', function() 
);

app.component('parentComponent', 
  template: `
    <h3>Parent component</h3>
    <a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Notify Child</a>
    <span data-ng-bind="$ctrl.childMessage"></span>
    <child-component on-change="$ctrl.notifiedFromChild(count)"></child-component>
  `,
  controller: function() 
    var ctrl = this;
    ctrl.notifiedFromChild = function(count)
      ctrl.childMessage = "From child " + count;
    
    ctrl.click = function()
    
  ,
  bindings: 
  
);

app.component('childComponent', 
  template: `
    <h4>Child component</h4>
    <a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Notify Parent</a>
  `,
  controller: function() 
    var ctrl = this;
    ctrl.counter = 0;
    ctrl.click = function()
        ctrl.onChange( count: ++ctrl.counter );
    
  ,
  bindings: 
    onChange: '&'
  
);

你可以在这里找到一个例子:

http://plnkr.co/edit/SCK8XlYoYCRceCP7q2Rn?p=preview

这是我创建的一个可能的解决方案

http://plnkr.co/edit/OfANmt4zLyPG2SZyVNLr?p=preview

孩子需要父母,然后孩子设置父母对孩子的引用......现在父母可以使用孩子......丑陋但就像上面的angular2示例

【问题讨论】:

您到底想通知孩子什么?您通常不需要这样做。大多数情况下都覆盖了绑定,例如:plnkr.co/edit/NcbNOAnOqMhxqvvLR74O?p=info还不够吗? 例如,child 是一个网格,parent 有一个搜索按钮,当我按下这个按钮时我想刷新网格,所以我进行了 ajax 调用,这不能通过绑定来实现,对吧? 可以的。您可以对这些绑定中的更改做出反应(没有 $scope.$watch)。如果到那时您还没有得到答复,可以在我 2 小时后回来时发布示例。 太好了,无论如何都会等待您的答复!谢谢队友 【参考方案1】:

简单:您只需要一个属性 1 方式绑定,因为 2 方式绑定仅在创建时调用 onChanges。

    在父控制器上设置一个新的布尔属性。

    vm.changeNow = 假; //当你想告诉组件时,将其更新为 vm.changeNow = !vm.changeNow //调用一个方法。

    在绑定部分打开子组件,

    绑定: a2waybind: '=', 改变现在:'

    您现在需要对孩子进行 $onChanges 事件。

    $onChanges() // 做那些你想从父母那里听到的甜蜜的事情。

    现在调用模板时:

    childComponent a2waybind="$ctrl.mycoolvalue" changenow="$ctrl.changeNow" /childComponent"

第二种方法是在您的子组件中:

        var vm = this;
        var myprop;
        Object.defineProperty(vm, 'mytwowayprop', 
            get() 
                return myprop;
            ,
            set(value) 
                myprop = value; 
                vm.onchangeseventbecausemypropchanged();               
            
        );
       vm.onchangeseventbecausemypropchanged = function () //woot

这将允许您在双向绑定属性在内部和外部发生变化时定义 onchanges 事件。

【讨论】:

【参考方案2】:

我遇到了同样的问题。您如何看待这种方法:通过require 而不是双向绑定使用继承

http://plnkr.co/edit/fD1qho3eoLoEnlvMzzbw?p=preview

var app = angular.module('plunker', []);

    app.controller('RootController', function() 
    );

    app.component('filterComponent', 
      template: `
        <h3>Filter component</h3>
        <a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Search</a>
        <span data-ng-bind="$ctrl.childMessage"></span>

        <grid-component api="$ctrl.gridApi"></grid-component>
      `,
      controller: function() 
        var ctrl = this;

        ctrl.click = function()
          console.log("Search clicked");
          ctrl.gridApi.save();
        ;
      
    );

    app.component('gridComponent', 
      require: parent:'^^filterComponent',
      bindings: api: "<",
      template: `
        <h4>Grid component</h4>
        <p> Save count = $ctrl.count
      `,
      controller: function() 
        var ctrl = this;



        this.$onInit = function() 
            ctrl.count = 0;
            ctrl.api = ;
            ctrl.api.save = save;

            ctrl.parent.gridApi = ctrl.api;
        ;
        function save()
          console.log("saved!");
          ctrl.count++;
        
      
    );

或者我们可以为父级定义setter方法,使其更加明确。

http://plnkr.co/edit/jmETwGt32BIn3Tl0yDzY?p=preview

var app = angular.module('plunker', []);

app.controller('RootController', function() 
);

app.component('filterComponent', 
  template: `
    <h3>Filter component</h3>
    <a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Search</a>
    <span data-ng-bind="$ctrl.childMessage"></span>

    <grid-component pass-api="$ctrl.setGridApi(api)"></grid-component>
  `,
  controller: function() 
    var ctrl = this;

    var gridApi = ;

    ctrl.setGridApi = function(api)
      gridApi = api;
    ;

    ctrl.click = function()
      console.log("Search clicked");
      gridApi.save();
    ;
  
);

app.component('gridComponent', 
  bindings: 
    passApi:'&'
  ,
  template: `
    <h4>Grid component</h4>
    <p> Save count = $ctrl.count
  `,
  controller: function() 
    var ctrl = this;

    this.$onInit = function() 
        ctrl.count = 0;
        ctrl.api = ;
        ctrl.api.save = save;

        ctrl.passApi(api: ctrl.api);
    ;
    function save()
      console.log("saved!");
      ctrl.count++;
    
  
);

【讨论】:

setter 方法还可以,(可能很冗长),但 require 方法不太好,因为你有一个 1on1 依赖【参考方案3】:

在 AngularJS 组件中将事件从父级传递到子级

使用表达式绑定发布指令 $API

要允许父组件将事件传递给子组件,请让子组件发布 API:

<grid-component grid-on-init="$ctrl.gridApi=$API; $ctrl.someFn($API)">
</grid-component>    

JS

app.component('gridComponent', 
  //Create API binding
  bindings: gridOnInit: "&",
  template: `
    <h4>Grid component</h4>
    <p> Save count = $ctrl.count</p>
  `,
  controller: function() 
    var ctrl = this;
    this.$onInit = function() 
        ctrl.count = 0;
        ctrl.api = ;
        //Publish save function
        ctrl.api.save = save;
        //Invoke Expression with $API as local
        ctrl.gridOnInit($API: ctrl.api);
    ;
    function save()
      console.log("saved!");
      ctrl.count++;
    
  
);

上面的示例调用由grid-on-init 属性定义的Angular 表达式,其API 公开为$API。这种方法的优点是,父级可以通过使用 Angular 表达式将函数传递给子组件来对子初始化做出反应。

来自文档:

“隔离”范围对象哈希定义了一组从指令元素的属性派生的本地范围属性。这些本地属性对于模板的别名值很有用。对象哈希中的键映射到隔离范围上的属性名称;这些值通过指令元素上的匹配属性定义属性如何绑定到父范围:

&amp;&amp;attr - 提供了一种在父作用域的上下文中执行表达式的方法。如果未指定 attr 名称,则假定属性名称与本地名称相同。给定&lt;my-component my-attr="count = count + value"&gt; 和隔离作用域定义作用域: localFn:'&amp;myAttr' ,隔离作用域属性localFn 将指向count = count + value expression 的函数包装器。通常希望通过表达式将数据从隔离范围传递到父范围。这可以通过将局部变量名称和值的映射传递到表达式包装器 fn 来完成。例如,如果表达式是increment($amount),那么我们可以通过将localFn 调用为localFn($amount: 22) 来指定金额值。

-- AngularJS Comprehensive Directive API -- scope

按照惯例,我建议在局部变量前面加上 $ 以将它们与父变量区分开来。


交替使用双向绑定

注意:为了便于过渡到 Angular 2+,请避免使用双向 = 绑定。而是使用单向&lt; 绑定和表达式&amp; 绑定。如需更多信息,请参阅AngularJS Developer Guide - Understanding Components。

要允许父组件将事件传递给子组件,请让子组件发布 API:

<grid-component api="$ctrl.gridApi"></grid-component>

在上面的示例中,grid-component 使用绑定将其 API 发布到使用 api 属性的父范围。

app.component('gridComponent', 
  //Create API binding
  bindings: api: "=",
  template: `
    <h4>Grid component</h4>
    <p> Save count = $ctrl.count</p>
  `,
  controller: function() 
    var ctrl = this;
    this.$onInit = function() 
        ctrl.count = 0;
        ctrl.api = ;
        //Publish save function
        ctrl.api.save = save;
    ;
    function save()
      console.log("saved!");
      ctrl.count++;
    
  
);

然后父组件可以使用发布的API调用子save函数:

ctrl.click = function()
  console.log("Search clicked");
  ctrl.gridApi.save();

DEMO on PLNKR。

【讨论】:

我发现第二种方式更加优雅,但是这种方式并不比第一种方式效率低,因为它依赖于双向绑定而不是单向绑定? 第二种方法是正确的方法。而不是调用绑定“api”,而是使用“name”(参见 ngForm)。 第二种方法是双向的,=,通过添加两个观察者绑定会产生更多的开销。第一种使用表达式&amp; 绑定的方法避免了添加任何观察者。 注意:为了便于过渡到 Angular 2+,请避免使用双向 = 绑定。而是使用单向 AngularJS Developer Guide - Understanding Components. 是否也可以为第一个示例创建 PLNKR?这会很有帮助。【参考方案4】:

这是一个简单的方法:http://morrisdev.com/2017/03/triggering-events-in-a-child-component-in-angular/

基本上,您添加一个名为“command”(或任何您想要的)的绑定变量,并使用 $onChanges 来注意该变量的更改并触发它所说的手动触发的任何事件。

我个人喜欢将所有变量放入一个名为“设置”的对象中,并将其发送给我的所有组件。但是,对象中值的更改不会触发 $onChanges 事件,因此您需要告诉它使用平面变量触发事件。

我会说这不是“正确”的做法,但它确实更容易编程,更容易理解,并且以后更容易转换为 A2。

【讨论】:

这是一个好方法。如前所述,$onChanges 生命周期挂钩仅在对象的 identity 更改时触发。 $doCheck 生命周期可用于检查对象内容的更改。有关详细信息,请参阅AngularJS Comprehensive Directive API - Life-Cycle Hooks。 我不得不承认,我让它与 $onChanges 一起工作,松了一口气,然后开始处理下一个问题。我从未尝试过 $doCheck。这听起来像是这种方法的最佳选择。

以上是关于在 AngularJS 组件中从父级到子级通信事件的主要内容,如果未能解决你的问题,请参考以下文章

在 ReactJs 中,从 redux 存储或从父级到子级将 props 传递给子组件的最佳实践是啥?

将动态插槽从父级传递到子级到孙子级

python3 - 使用脚本从父级到子级共享变量的最简单方法

如何正确使用 Angular 中的 Observable 将数组数据从父级发送到子级?

如何从父级获取数据到子级?

JS添加/移除事件