k102
k102

Reputation: 8079

How to share component methods to child?

I've got two components:

<cmp-one></cmp-one> is inserted to the DOM, while I'm using $compile to create a <cmp-top>. Then in cmpTop controller I need to get <cmp-one> and insert it into the <cmp-top>.

Insertion works fine, but I need to access cmpTop controller methods from cmpOne - and can't figure out how.

What I've tried so far is adding require: {cmpTop: '^^'} - not working since there is no parent component before insertion is done.

So, how can I achieve this? I mean - insert some component into another, and share its methods to the added child.

updated

Here is the plunker: http://plnkr.co/edit/mgwC5Mbh5qid5q5ELDdQ?p=info

So, I need to access PanelController's methods from the DialogComponentController.

Or, maybe I'm doing it all wrong - so please give me a clue how to make it properly.

Upvotes: 0

Views: 262

Answers (3)

Vindhyachal Kumar
Vindhyachal Kumar

Reputation: 1794

Check this working example.

 var app = angular.module('plunker', []);


    PanelController.$inject = ['$element'];
    function PanelController($element) {
        var $ctrl = this;

        $ctrl.showAlert = function () {
            alert('Message from PanelController');
        };

        $ctrl.close = function () {
            // $element.remove();
        };

        $ctrl.onShow = function () {
        };

        $ctrl.$onInit = $onInit;
        $ctrl.$onDestroy = $onDestroy;

        function $onInit() {

            var srcEl = angular.element(document.querySelector($ctrl.source));

            srcEl.attr('panel', '$ctrl');

            $element
              .find('.panel')
              .append(srcEl
                .css('display', 'block')
              );

            $ctrl.onShow();

            var bodyRect = document.body.getBoundingClientRect(),
                elRect = $element[0].getBoundingClientRect(),
                position = {
                    left: '',
                    top: '',
                    right: '',
                    bottom: ''
                };

            position.top = (bodyRect.height - elRect.height) / 4; //eslint-disable-line no-magic-numbers
            position.left = (bodyRect.width - elRect.width) / 2; //eslint-disable-line no-magic-numbers

            $element.css('top', position.top + 'px');
            $element.css('left', position.left + 'px');
        }

        function $onDestroy() {
        }
    }

    function DialogComponentController() {
        var $ctrl = this;

        $ctrl.callPanelMethod = function () {
            var scope = angular.element($('ccm-panel div')).scope();
            scope.$ctrl.showAlert();
        }
        $ctrl.actions = {
            close: function (event, button) {

                event.preventDefault();
                event.stopPropagation();

                $ctrl.instance.hide(button);
            }
        };

        $ctrl.$onInit = $onInit;
        $ctrl.$onDestroy = $onDestroy;

        $ctrl.$onChanges = function () {
        };

        function $onInit() {
        }

        function $onDestroy() {
        }
    }


    app.component('ccmDialog', {
        template: '<div style="border: 1px solid green; margin: 5px" >dialog <button ng-click="$ctrl.callPanelMethod()">click here to get alert from controll PanelController </button></div>',
        controller: DialogComponentController,

        bindings: {
            panel: '<',
            panelShown: '&'
        }
    });
    app.component('ccmPanel', {
        template: '<div>' +
                  '<div style="border: 1px solid red" class="panel" ng-click="$ctrl.close()">panel</div>' +
                  '</div>',
        controller: PanelController,
        bindings: {
            source: '='
        }
    });

    app.controller('MainCtrl', function ($rootScope, $scope, $compile) {
        $scope.name = 'World';

        $scope.open = function (source, target) {
            var scope = $rootScope.$new();

            scope.$ctrl = {
                source: source
            };

            var el = $compile('<ccm-panel source="$ctrl.source" style="position: absolute;"></ccm-panel>')(scope);

            angular.element(document.querySelector(target)).append(el);
        }
    });
 <script data-require="[email protected]" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
 <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
 <div ng-app="plunker" ng-controller="MainCtrl">
    <p>Hello {{name}}!</p>
    <button ng-click="open('ccm-dialog', 'body')">open dialog</button>
    <ccm-dialog panel="test" style="display: none"></ccm-dialog>
</div>

Upvotes: 0

blackmiaool
blackmiaool

Reputation: 5344

What's wrong with your code?

$element
      .find('.panel')
      .append(srcEl
        .css('display', 'block')
      );

You use .find here with a class selector, but according to the documentation, angular just supports tag selector.

Inherit?

According to the documentation

Components only control their own View and Data: Components should never modify any data or DOM that is out of their own scope. Normally, in AngularJS it is possible to modify data anywhere in the application through scope inheritance and watches. This is practical, but can also lead to problems when it is not clear which part of the application is responsible for modifying the data. That is why component directives use an isolate scope, so a whole class of scope manipulation is not possible.

How to share methods then?

@K Scandrett's answer is great and may be the best practice, but my favorite solution is:

In DialogComponentController:

$rootScope.$emit("abc"); 

In PanelController:

$rootScope.$on("abc",function(){
    console.log("got it");
    //and do whatever you want 
});

Upvotes: 0

K Scandrett
K Scandrett

Reputation: 16540

You can use a common Service to communicate between them (as playerone mentioned).

app.controller('mainController', function($scope, menuSelection) {
  $scope.menuSelection = menuSelection; // retrieve settings object from service method and bring into scope
  // now whenever one sets $scope.menuSelection.selected = "object 2", it will update the value in the other controller as well (and vice-versa)
});

app.controller('secondController', function($scope, menuSelection) {
  $scope.menuSelection = menuSelection; // retrieve settings object from service method and bring into scope
});

app.factory('menuSelection', function() {
  var settings = {};
  settings.selected = 'Object 1';  // default
  return settings;
});

You can point $scope.menuSelection.myFooFunction = ... to a function in one directive and call it from another.

Upvotes: 1

Related Questions