8

Im trying to understand what is the best GENERIC approach to communicate between parent and child directive with isolated scopes (they might be reusable items).

meaning if child directive needs to update parent directive in some manner (both have isolated scopes), should I pass a callback function :

e.g:

.directive('filterReviewStepBox', function () {
    return {
        restrict: 'EA',
        scop: {
            //some data
        },
        template: '<div>text<reusable-dir></reusable-dir call-back="foo"></div>',
        link: function (scope, elem, attrs) {
            scope.foo = function () {
                console.log('bar');
            };
        }
    };
}).directive('reusableDir', function () {
    return {
        restrict: 'EA',
        scope: {
            callBack: '=callBack'
                //other data
        },
        template: '<div>text<button type="button" ng-click="bar"></button></div>',
        link: function (scope, elem, attrs) {
            scope.bar = function () {
                scope.callBack();
            }
        }
    };
});

or should I use $emit():

e.g:

  directive('filterReviewStepBox', function () {
        return {
            restrict: 'EA',
            scope: {
                // some data
            },
            template: '<div>text<reusable-dir></reusable-dir></div>',
            link: function (scope, elem, attrs) {
                scope.$on('foo', function () {
                    console.log('bar');
                });
            }
        };
    }).directive('reusableDir', function () {
        return {
            restrict: 'EA',
            scope: { //some data
            },
            template: '<div>text<button type="button" ng-click="bar"></button></div>',
            link: function (scope, elem, attrs) {
                scope.bar = function () {
                    scope.$emit('foo');
                };
            }
        };
    });

I feel that emit would be easier to understand on a larger scale but worried about performance and overhead, but Im still unsure

tried looking for the best approach online but Im still stomped

EDIT

I forgot about the

require

option. but I'm still not sure this is the most correct solution. Since this doesn't allow me to reuse the child or grandchild and kind of makes the directive a single purpose item.

Jony-Y
  • 1,579
  • 1
  • 13
  • 30
  • there's `require` option that allows you to access another directive's controller. this should allow you to do what you want. – xZ6a33YaYEfmv Oct 24 '15 at 08:14
  • I see what you're saying... haven't thought about that... but what happens if I want the child to be reusable ? then your approach cannot be used. – Jony-Y Oct 24 '15 at 08:28
  • 1
    In my experience, well designed directives should be self contained, and should be able to function entirely as if they were the only component in existence on the page. If you are trying to notify some other directive of something, then you aren't self contained. In nearly every instance where a directive needs to communicate outside itself, a simple two way bound property should work. – Claies Oct 24 '15 at 08:57
  • @Claies, Directives should be self contained and still should be able to communicate with their parent directive or controller. there are many examples in which you have to preform some sort of update to parent. two way binding as a rule is an approach that is being rejected (e.g: angular2 and reactjs) and also at a large scale hard to understand so I dont want to relay on it especially for more complex actions. for some actions some sort of callback or $emit is needed. – Jony-Y Oct 24 '15 at 09:17
  • 1
    I think both are valid approaches, I've personally use both in different situations, as well as see plugins using these. It all comes down to standardize (use same approach in similar code) and good documentation so it can be maintained more easily in the future. – Icycool Oct 26 '15 at 02:56

3 Answers3

1

For this purpose the best is to utilize "require" attribute.

Complete guide to directives tell us this about require attribute : https://docs.angularjs.org/api/ng/service/$compile

Require another directive and inject its controller as the fourth argument to the linking function. The require takes a string name (or array of strings) of the directive(s) to pass in.

Require just tells the directive it should look for some parent directive and take its controller. You can tell directive to search in parent elements with ^ prefix and tell if this requirement is optional with ? prefix.

I have modified your example, so reusableDir can call filterReviewStepBox controller, but can be also used alone.

http://jsbin.com/gedaximeko/edit?html,js,console,output

angular.module('stackApp', [])  
.directive('filterReviewStepBox', function () {
        return {
            restrict: 'EA',
            scope: {
                // some data
            },
            template: '<div>text<reusable-dir></reusable-dir></div>',
            link: function (scope, elem, attrs) {

            },
            controller : function() {
              this.notify = function(value) {
                console.log('Notified : ' + value);
              };              
            },
            controllerAs: 'vm',
            bindToController: true
        };
    }).directive('reusableDir', function () {
        return {
            restrict: 'EA',
            scope: { //some data
            },
            require: '?^filterReviewStepBox',
            template: '<div>text<button type="button" ng-click="bar()"></button></div>',
            link: function (scope, elem, attrs, controller) {
                scope.bar = function () {
                  if (controller) {
                    controller.notify('foo');
                  }
                };
            }
        };
    });
  • your solution utilizes the controller option, but that also does not solve my issue for generic directives that cannot predict the parent controller which is exactly why I asked this question. here either I define a strict controller or none, this will not work across multiple controllers, meaning you can not use it with controller1 and then with controller2, this is why I prefered to use $emit or callbacks since they can be implemented generically. – Jony-Y Oct 27 '15 at 15:23
  • If you mean it like directive doesn't know who it calls, then $emit would be really more general. I wouldn't use callback, because ussually i utilize transclude, so my reusableDir wouldn't be inside parent's template. But how did you mean it doesn't allow you reuse 'child or grandchildren'? – Víťa Plšek - angular.cz Oct 27 '15 at 15:41
  • when you use require you are actually forced to use the parent or grandparent controller so if I want to use the functionality and callback in a different controller I cant... Personally Im more drawn towards $emit than callBacks but Im just trying to understand the best practice – Jony-Y Oct 27 '15 at 15:55
1

Personally, I try to avoid generating my own custom events. Mainly because it's incredibly easy to cause unwanted event propagation, or bubbling (often causing unwanted scope digests), but also because of the systematic 'noise' that in bigger applications can become very hard to manage.

My own personal views aside though, and if it's a more 'future-proof' approach you're looking for, the callback approach is by far the safest. Here's why...

  • Angular2 encourages developers to expose onChange attributes from directives and components that depend on child-to-parent communication. Note that these are nothing more than bound functions, and should be defined accordingly in the bindings or scopes of the given component or directive.
  • Similar to the above, Angular2 documents an ngOnChanges method that can be defined by any controller wishing to subscribe to changes to it's bindings. If supplied, this method is called before any DOM manipulation occurs (with the new bound values), so it's a perfect candidate for a more 'general' parent to child communication over the current AngularJS $scope.$watch implementation.

Something that wasn't mentioned however, is that communication via services is still recommended, in both AngularJS and Angular2.


Iain J. Reid
  • 978
  • 1
  • 11
  • 23
0

I think it is not just about the performance, you also need to take few more factors into consideration from design and maintenance perspective.

  1. Callback method: This is perhaps the most efficient option. The child directive would just call one method of parent directive, no overheads. There are quite a few options explained here: https://stackoverflow.com/a/27997722/2889528

  2. Emit (or for that matter broadcast): This method publishes the event to all $scope(s) up (emit) or down (broadcast) to hierarchy, relatively costly but it gives you a flexibility to watch and handle the event wherever $scope is available.

  3. Injectable Common Service: This option can be used when you want to call some method where $scope is not available. However, it created tight dependency on service. This is more of a pub-sub design.

Community
  • 1
  • 1
Yogesh Manware
  • 1,843
  • 1
  • 22
  • 25