Управлять определенным дочерним компонентом от родителя в AngularJS

Я использую AngularJS 1.5.8. У меня есть компоненты «myParent» и «myChild». На мой взгляд, у меня есть 1 родитель и 2 ребенка внутри этого родителя. Итак, мой вопрос: я хочу иметь экземпляры дочерних компонентов "myChild" в контроллере компонента "myParent" и дать команду только определенному дочернему элементу для makePrimary() или что-то в этом роде... Как могу ли я достичь этой цели?

JS:

//var myApp = 
angular.module('myApp', ['myChild', 'myParent']);

angular.
module('myChild', []).
component('myChild', {
  template: '<div class="panel panel-default">' +
    '<div class="panel-heading">Child</div>' +
    '<div class="panel-body">' +
    'Child content...' +
    '</div>' +
    '</div>',
  controller: ['$scope', '$element',
    function myChildController($scope, $element) {

    }
  ]
});

angular.
module('myParent', ['myChild']).
component('myParent', {
  template: '<div class="panel panel-default">' +
    '<div class="panel-heading">Parent</div>' +
    '<div class="panel-body">' +
    '<my-child></my-child>' +
    '<hr />' +
    '<my-child></my-child>' +
    '</div>' +
    '</div>',
  controller: ['$scope', '$element',
    function myParentController($scope, $element) {

    // TODO: MAKE CHILD-2 ".panel-primary" CLASS IN HERE.
    // BUT ONLY A SPECIFIC CHILD!

    }
  ]
});

HTML:

<div ng-app="myApp" class="container">
  <my-parent></my-parent>
</div>

Пример JSFIDDLE: https://jsfiddle.net/zhc12t1a/4/

Я просмотрел некоторые связанные вопросы, такие как:

Но я не могу найти правильный способ достичь этой цели с помощью простой архитектуры.

ИЗМЕНИТЬ

Я знаю некоторые сложные методы (с точки зрения архитектуры), но я не хочу их использовать. Например:

Используя $parent или

в myParentController:

$scope.makeChild1Primary = function () {} 

в шаблоне myParent:

<my-child make-primary="makeChild1Primary"></my-child>

в компоненте myChild

//...
var ctrl = this;
ctrl.makePrimary = function () { /* MAKE PRIMARY LOGIC HERE... */ }
//...
,bindings: {
makePrimary: '='
}

person Burak TARHANLI    schedule 04.12.2016    source источник
comment
Рассмотрите возможность использования службы для обмена состоянием; он позволяет избежать многих сложностей с сканирующими областями, исчерпанием обработчиков $broadcast, и его легко совместно использовать в контроллерах и директивах.   -  person RamblinRose    schedule 05.12.2016


Ответы (4)


Как упоминалось в "AngularJS — доступ к дочерней области", нет способа получить доступ дочернюю область от родителя, потому что AngularJS обрабатывает свойства от дочернего к родительскому, а не наоборот.

Если вы не хотите привязывать ng-класс дочернего элемента к родительскому свойству или использовать общий сервис, вы должны явно добавить дочерние области к родительскому свойству следующим образом:

function myParentController($scope, $element) {
    $scope.children = [];
    ...
}

function myChildController($scope, $element) {
    $scope.$parent.children.push($scope);
}

Затем вы можете получить доступ ко всем дочерним элементам от родителя следующим образом:

$scope.children[1].isPrimary = true;

В то время как в вашем HTML вы привязываете класс панели к свойству «isPrimary» следующим образом:

<div class="panel panel-default" ng-class="isPrimary ? 'panel-primary' : ''">

Вы можете найти полную реализацию вашего примера здесь: https://jsfiddle.net/81eesouy/7/

person ggradnig    schedule 04.12.2016
comment
Спасибо за ваш комментарий. Но я не хочу использовать $parent. Потому что в будущем у меня могут быть другие родители, и я могу перемещать свои компоненты в другие компоненты. Таким образом, использование $parent может привести к путанице в будущем. Поэтому я нашел другой способ и разместил его. Конечно, ваш ответ правильный, но новый способ, который я нашел, кажется мне более правильным. - person Burak TARHANLI; 04.12.2016
comment
Спасибо за ответ. Существует много возможных решений для этого требования, но использование дочерней области — это единственный способ дать команду только определенному дочернему элементу, как это требуется в вашем вопросе. - person ggradnig; 04.12.2016

Используйте событие $emit или $broadcast.

Я думаю, что это лучший чистый ответ, вы можете попробовать с сервисами, но этот ответ мне нравится больше :)

Быстрый пример

function controllerParent($scope){
    $scope.emitActionClick = function(){
       $scope.$broadcast('myTestEvent',{data:"passed"})               
    }


}
function controllerChildX($scope){
   $scope.$on('myTestEvent',function(event,data){ 
      // HERE WE ARE :)
       console.log(data);
   }

}

Если вы используете Контроллер как, вам потребуется внедрить $scope или $rootScope. К сожалению, другого способа обработки событий отправки и вещания нет.

person Gosu Przmak    schedule 04.12.2016
comment
Спасибо за ответ. Это правда, что вы сказали, но я думаю, что использование .$emit и .$on в компонентах затрудняет отслеживание того, какой компонент испускает какие события и какие компоненты перехватывают это событие. - person Burak TARHANLI; 06.12.2016
comment
Вам нужно использовать $scope.$broadcast, который отправляет событие всем дочерним элементам (вниз), а $scope.$emit отправляет событие всем предкам (вверх). Я обновил ваш ответ. - person Slavik Meltser; 13.10.2020

Я достиг этой цели, используя конфигурацию bindings дочернего компонента. Ключевым моментом здесь является то, что изменения (сделанные родителем) отражаются в свойстве привязки дочернего элемента. Мы должны помнить об этом всегда! Итак, необходимые изменения приведены ниже:

JS:

function myParentController($scope) {
   // the variables that controls children's primary statuses
   $scope.child1Primary = false;
   $scope.child2Primary = false;
}


// component definition
// ...
controller: ['$scope',
    function myChildController($scope) {
    // This $watch is not required. I used it for only showing that the changes are reflecting dynamically.
      $scope.$watch('$ctrl.isPrimary', function(newVal) {
        alert("CHANGE DETECTED BY CHILD\n$ctrl.isPrimary: " + newVal);
      })
    }
  ],
  bindings: {
    isPrimary: '<'
  }
//...

РОДИТЕЛЬСКИЙ ШАБЛОН:

<my-child is-primary="child1Primary"></my-child><br />
<my-child is-primary="child2Primary"></my-child>

ПОЛНАЯ ССЫЛКА JSFIDDLE: https://jsfiddle.net/zhc12t1a/5/

И всякий раз, когда мы изменяем переменную childXPrimary, переменная $ctrl.isPrimary изменяется в дочерней области. Таким образом, нет необходимости в такой функции, как makePrimary, в компоненте myChild.

ПРИМЕЧАНИЕ. ng-class используется для смены класса, но это деталь. Важным моментом здесь является коммуникационная архитектура.

person Burak TARHANLI    schedule 04.12.2016

Я пробовал привязку переменных триггера, но это было осложнено необходимостью сбрасывать триггер после каждой активации или использовать numerator в качестве триггера. Это потому, что ребенку нужно различать, когда триггер изменился, чтобы знать, был ли он активирован.

Вместо этого я отправил ребенку observable. Ребенок прослушивает родительские команды на observable. Мы можем использовать Rxjs в AngularJs.

Несколько похожий метод может заключаться в том, чтобы предоставить дочернему элементу функцию регистрации от родителя, в которой дочерний элемент вызывает и предоставляет объект интерфейса, который затем родитель может сохранить и использовать для активации методов дочернего элемента. Проблема с этим подходом заключается в необходимости очистки сохраненного интерфейса в родительском элементе, когда дочерний элемент ушел. В parent observable solution мы делаем это в другом направлении. Ребенок несет ответственность за регистрацию себя на родителя и очистить себя.

person elpdpt    schedule 10.10.2018