简体   繁体   中英

Command a specific child component from parent in AngularJS

I'm using AngularJS 1.5.8. I have "myParent" and "myChild" components. In my view, i've 1 parent and 2 children inside that parent. So my question is: I wanna have instances of children "myChild" components in "myParent" component's controller, and give a command to only a specific child to makePrimary(), or like that... How can i achive this goal?

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 for sample: https://jsfiddle.net/zhc12t1a/4/

I inspected some related questions like:

But i can't find a proper way to achive this goal with a simple architecture.

EDIT

I know some complicated methods (from architectural perspective) but i don't want to use them. For example:

Using $parent or

in myParentController:

$scope.makeChild1Primary = function () {} 

in myParent template:

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

in myChild component

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

As mentioned in " AngularJS - Access to child scope ", there is no way to access the childs scope from the parent, because AngularJS processes properties from child to parent, not the other way round.

If you don't want to bind the ng-class of the child element to a parent's property or use a common service, you have to explicitely add the childrens scopes to a parent property like so:

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

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

Then you can access all children from the parent like so:

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

While in your HTML you bind the class of the panel to the property "isPrimary" like so:

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

You can find the entire implementation of your example here: https://jsfiddle.net/81eesouy/7/

Either use $emit or $broadcast event.

This is the best clean answer I think, you can try with services but this is the answer I like more :)

Fast example

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);
   }

}

If you are using Controller As You will need to inject $scope or $rootScope. There is no other way in handling emit and broadcast events unfortunately.

I achieved this goal by using child component's bindings configuration. Key point in here, the changes (that made by parent) are reflected into child's binding property . We must remember that always! So, the required changes are below:

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: '<'
  }
//...

PARENT TEMPLATE:

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

COMPLETE JSFIDDLE LINK: https://jsfiddle.net/zhc12t1a/5/

And whenever we change childXPrimary variable, the $ctrl.isPrimary variable is changing on child's scope. So, there is no need a function like "makePrimary" in "myChild" component.

NOTE: ng-class is used for class change, but it is a detail. The impotant thing in here is the communication architecture .

I have tried trigger variable binding, but that was complicated by the need to reset the trigger after each activation, or use a numerator as a trigger. That is because the child need to differentiate when the trigger has changed to know if it was activated.

Instead, I have sent an observable to the child. The child is listening for the parent commands on the observable . We can use Rxjs in AngularJs .

A somewhat similar method can be is to provide the child a register function from the parent, in which the child calls and provide an interface object that then the parent can store and use to activate methods on the child. Problem with this approach is the necessity to clean the stored interface in the parent when the child is gone. In the parent observable solution , we do it in the other direction. The child is responsible to register itself on the parent and clean itself up.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM