Передача событий от родителя к дочернему элементу в компонентах AngularJS

в новом проекте, над которым я работаю, я начал использовать компоненты вместо директив.

однако я столкнулся с проблемой, когда не могу найти конкретный стандартный способ сделать это.

Легко уведомить о событии от дочернего к родительскому, вы можете найти его в моем plunkr ниже, но как правильно уведомить о событии от родителя к дочернему?

Angular2, кажется, решает эту проблему, используя что-то вроде этого: " rel="noreferrer">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 выше


person Luca Trazzi    schedule 25.05.2016    source источник
comment
О чем именно вы хотите сообщить ребенку? Обычно вам не нужно этого делать. Большинство случаев покрыты привязками, например: info Тебе этого мало?   -  person dfsq    schedule 25.05.2016
comment
Например, дочерний элемент — это сетка, у родителя есть кнопка поиска, когда я нажимаю эту кнопку, я хочу обновить сетку, поэтому я делаю вызов ajax, этого нельзя добиться с помощью привязки, верно?   -  person Luca Trazzi    schedule 25.05.2016
comment
Может. Вы можете реагировать на изменения в этих привязках (без $scope.$watch). Можете опубликовать пример, когда я вернусь через 2 часа, если вы не получили ответа к тому времени.   -  person dfsq    schedule 25.05.2016
comment
Отлично, все равно буду ждать вашего ответа! Спасибо друг   -  person Luca Trazzi    schedule 25.05.2016


Ответы (4)


Передача событий от родителя к дочернему элементу в компонентах 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 Expression.

Из документов:

Хэш объекта области «изолировать» определяет набор свойств локальной области, полученных из атрибутов элемента директивы. Эти локальные свойства полезны для создания псевдонимов значений для шаблонов. Ключи в хэше объекта сопоставляются с именем свойства в изолированной области; значения определяют, как свойство привязано к родительской области, посредством сопоставления атрибутов в элементе директивы:

  • & или &attr — обеспечивает способ выполнения выражения в контексте родительской области. Если имя атрибута не указано, предполагается, что имя атрибута совпадает с локальным именем. Учитывая <my-component my-attr="count = count + value"> и область определения области изоляции: { localFn:'&myAttr' }, свойство области изоляции 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();
}

ДЕМО на PLNKR.

person georgeawg    schedule 25.05.2016
comment
Я нахожу второй способ гораздо более элегантным, но не менее ли он эффективен, чем первый, поскольку он основан на двухсторонней привязке вместо односторонней? - person davidxxx; 19.03.2017
comment
Второй подход является правильным подходом. Вместо вызова API привязки обычно используется имя (см. ngForm). - person G-Wiz; 06.11.2017
comment
Второй подход с двусторонней привязкой, =, создает дополнительные накладные расходы за счет добавления двух наблюдателей. Первый подход, использующий привязку выражения &, позволяет избежать добавления каких-либо наблюдателей. - person georgeawg; 06.11.2017
comment
ПРИМЕЧАНИЕ. Чтобы упростить переход на Angular 2+, избегайте использования двунаправленной привязки =. Вместо этого используйте одностороннюю привязку ‹ и выражение и привязку. Для получения дополнительной информации см. Руководство разработчика AngularJS — Общие сведения о компонентах. - person georgeawg; 06.11.2017
comment
Можно ли создать PLNKR и для первого примера? Это было бы очень полезно. - person mareoraft; 09.11.2018

Вот простой способ: http://morrisdev.com/2017/03/triggering-events-in-a-child-component-in-angular/

в основном вы добавляете связанную переменную с именем «команда» (или что угодно) и используете $onChanges, чтобы обращать внимание на изменения этой переменной и запускать любое событие, которое, по его словам, должно запускаться вручную.

Лично мне нравится помещать все мои переменные в объект под названием «Настройки» и отправлять его всем моим компонентам. Однако изменение значения внутри объекта НЕ вызывает событие $onChanges, поэтому вам НЕОБХОДИМО сообщить ему, чтобы он запускал событие с плоской переменной.

Я бы сказал, что это не «правильный» способ сделать это, но, безусловно, его намного проще программировать, намного проще понять и намного проще преобразовать в A2 позже в будущем.

person Daniel Morris    schedule 31.03.2017
comment
Это хороший подход. Как уже отмечалось, хук жизненного цикла $onChanges срабатывает только при изменении identity объекта. Жизненный цикл $doCheck можно использовать для проверки изменений в содержимом объекта. Для получения дополнительной информации см. API комплексных директив AngularJS — жизненный цикл Крючки. - person georgeawg; 25.09.2017
comment
Должен признаться, я научился работать с $onChanges, вздохнул с облегчением и перешел к следующей задаче. Я никогда не пробовал $doCheck. Это звучит как лук на вершине этого подхода. - person Daniel Morris; 26.09.2017

Я столкнулся с тем же вопросом. Что вы думаете об этом подходе: использовать наследование через 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++;
        }
      }
    });

Или мы можем определить метод установки для родителя, чтобы сделать его более явным.

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++;
    }
  }
});
person ViES    schedule 09.11.2017
comment
способ установки в порядке (возможно, многословный), но подход требования не так хорош, потому что тогда у вас есть зависимость 1 на 1 - person Luca Trazzi; 09.11.2017

ПРОСТО: вам просто нужно одно свойство с 1-сторонней привязкой, потому что 2-сторонняя привязка вызывает только onChanges при создании.

  1. Установите новое логическое свойство на родительском контроллере.

    vm.changeNow = ложь; //обновите это на vm.changeNow = !vm.changeNow, когда вы хотите, чтобы компонент //вызывал метод.

  2. Откройте дочерний компонент, в разделе привязок

    привязки: { a2waybind: '=', changenow: '‹' }

  3. Теперь вам нужно событие $onChanges для дочернего элемента.

    $onChanges() { // делаем те приятные вещи, которые вы хотели услышать от родителя. }

  4. Теперь при вызове шаблона:

    дочерний компонент 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, когда ваше свойство двусторонней привязки изменяется как внутри, так и снаружи.

person Patrick Knott    schedule 14.12.2019