Коммуникация событий от родителя к ребенку в компонентах AngularJS
в новом проекте, над которым я работаю, я начал использовать компоненты вместо директив.
однако я столкнулся с проблемой, когда я не могу найти конкретный стандартный способ сделать это.
Легко уведомить событие от дочернего к родительскому, вы можете найти его на моем 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 выше
Ответы
Ответ 1
Общение событий с родительского элемента на ребенка в компонентах 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++;
}
}
});
В приведенном выше примере вызывается выражение Angular, определяемое атрибутом grid-on-init
, с его API, открытым как $API
. Преимущество этого подхода состоит в том, что родитель может реагировать на инициализацию ребенка, передавая функцию дочернему компоненту с выражением Angular.
Из Документов:
Хеш объекта объекта "изоляция" определяет набор свойств локальной области, полученных из атрибутов элемента директивы. Эти локальные свойства полезны для значений псевдонимов для шаблонов. Ключи в хэш-карте объекта относятся к имени свойства в области выделения; значения определяют, как свойство привязано к родительской области, посредством сопоставления атрибутов элемента директивы:
-
&
или &attr
- предоставляет способ выполнения выражения в контексте родительской области. Если имя attr не указано, предполагается, что имя атрибута совпадает с локальным именем. Учитывая <my-component my-attr="count = count + value">
и область определения области выделения: { localFn:'&myAttr' }
, свойство scope изоляции localFn
будет указывать на оболочку функции для count = count + value expression
. Часто желательно передавать данные из изолированной области видимости через выражение в родительскую область. Это можно сделать, передав карту локальных имен переменных и значений в оболочку выражения fn. Например, если выражение increment($amount)
, то мы можем указать значение суммы, вызвав localFn
как localFn({$amount: 22})
.
- API-интерфейс полной архитектуры AngularJS
Как правило, я рекомендую префикс локальных переменных $
отличать их от родительских переменных.
Альтернативно используйте двунаправленную привязку
ПРИМЕЧАНИЕ. Чтобы облегчить переход на Angular 2+, избегайте использования двунаправленного привязки =
. Вместо этого используйте одностороннюю привязку <
и выражение &
. Для получения дополнительной информации см. Руководство разработчика AngularJS - Понимание компонентов.
Чтобы родительские компоненты могли передавать события дочернему компоненту, родитель должен опубликовать 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++;
}
}
});
Затем родительский компонент может вызывать дочернюю функцию save
, используя опубликованный API:
ctrl.click = function(){
console.log("Search clicked");
ctrl.gridApi.save();
}
DEMO на PLNKR.
Ответ 2
Вот простой способ: http://morrisdev.com/2017/03/triggering-events-in-a-child-component-in-angular/
в основном, вы добавляете связанную переменную, называемую "команда" (или все, что хотите), и используйте $onChanges, чтобы обратить внимание на изменения этой переменной и вызвать любое событие, которое, по его словам, запускается вручную.
Я лично хотел бы поместить все мои переменные в объект с именем "настройки" и отправить его всем моим компонентам. Однако изменение значения внутри объекта НЕ запускает событие $onChanges, поэтому вам НЕОБХОДИМО сообщить ему, чтобы он инициировал событие с плоской переменной.
Я бы сказал, что это не "правильный" способ сделать это, но это намного проще в программировании, намного легче понять и намного проще конвертировать в A2 позже по дороге.
Ответ 3
Я столкнулся с тем же вопросом. Что вы думаете об этом подходе: использовать наследование через 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++;
}
}
});