【问题标题】:Communicating Events from Parent to Child in AngularJS Components在 AngularJS 组件中从父级到子级通信事件
【发布时间】: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 小时后回来时发布示例。
  • 太好了,无论如何都会等待您的答复!谢谢队友

标签: javascript angularjs angular-directive angular-components


【解决方案1】:

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

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

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

  2. 在绑定部分打开子组件,

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

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

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

  4. 现在调用模板时:

    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。这听起来像是这种方法的最佳选择。
    猜你喜欢
    • 1970-01-01
    • 2016-03-01
    • 2023-03-31
    • 2017-08-16
    • 2018-10-21
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多