在 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 表达式将函数传递给子组件来对子初始化做出反应。
来自文档:
“隔离”范围对象哈希定义了一组从指令元素的属性派生的本地范围属性。这些本地属性对于模板的别名值很有用。对象哈希中的键映射到隔离范围上的属性名称;这些值通过指令元素上的匹配属性定义属性如何绑定到父范围:
&
或&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。
【讨论】:
我发现第二种方式更加优雅,但是这种方式并不比第一种方式效率低,因为它依赖于双向绑定而不是单向绑定? 第二种方法是正确的方法。而不是调用绑定“api”,而是使用“name”(参见 ngForm)。 第二种方法是双向的,=
,通过添加两个观察者绑定会产生更多的开销。第一种使用表达式&
绑定的方法避免了添加任何观察者。
注意:为了便于过渡到 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 - 使用脚本从父级到子级共享变量的最简单方法