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

时间:2016-05-25 13:53:30

标签: javascript angularjs angular-directive angular-components

在我正在研究的新项目中,我已经开始使用组件而不是指令。

然而,我遇到了一个问题,我无法找到具体的标准方法。

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

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

中的示例一样

为了保证可以轻松转换为Angular2,我想避免:

  • 事件发射(从示波器发射和广播)
  • 使用来自子项的require(然后将回调添加到parent..UGLY)
  • 使用单向绑定,在子项中注入范围,然后“监视”此属性..更多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示例

4 个答案:

答案 0 :(得分:29)

在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 Expression,其API公开为$API。这种方法的优点是父级可以通过使用Angular Expression将函数传递给子组件来对子初始化做出反应。

来自文档:

  

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

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

- AngularJS Comprehensive Directive API -- scope

作为惯例,我建议在$前加上局部变量,以区别于父变量。

或者使用双向结合

注意:为了简化向Angular 2+的过渡,请避免使用双向=绑定。而是使用单向<绑定和表达式&绑定。有关详细信息,请参阅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

答案 1 :(得分:3)

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

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

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

我认为这不是“正确”的方式,但它确实更容易编程,更容易理解,并且在以后的路上更容易转换为A2。 / p>

答案 2 :(得分:1)

我面对同样的问题。您如何看待这种方法:通过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++;
    }
  }
});

答案 3 :(得分:0)

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

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

    vm.changeNow = false; //当您要告诉组件执行以下操作时,将其更新为vm.changeNow =!vm.changeNow //调用一个方法。

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

    绑定:{     a2waybind:'=',     changenow:“ <” }

  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事件。