3

I have a parent-child controller relationship between my <main>, <div> and <my-directive> as such:

<main ng-controller="MainController">

    <nav>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
        </ul>
    </nav>

    <div ng-controller="AboutController">

    </div>

    <my-directive></my-directive>

</main>

Within the MainController, i perform a $broadcast with a:

$scope.$broadcast('shoppingCartReady', msg);

Within AboutController, i can successfully receieve this $broadcast via:

angular.module('aboutModule', [])
    .controller('AboutController', require('./about/aboutController'));

var AboutController = function ($scope, $rootScope) {

    $scope.$on('shoppingCartReady', function(event, msg){
        console.log(msg);
    });

};

module.exports = [ '$scope', '$rootScope', AboutController];

However, when trying to receive the $broadcast within <my-directive> link function, it seems to never be received:

angular.module('shopModule', [])
    .directive('shopModuleDirective', require('./shopModuleDirective'))
    .directive('myDirective', require('./myDirective'));

var myDirective = function ($rootScope) {

    function link($scope, element, attrs, baseCtrl) {

        $scope.$on('shoppingCartReady', function (event, msg) {
            console.log(msg);
        });
    }

    return {
        restrict: 'E',
        require: '^shopModuleDirective',
        link: link
    };
};

return ['$rootScope', myDirective];

UPDATE


I have even created a controller within my directive:

angular.module('shopModule', [])
    .directive('shopModuleDirective', require('./shopModuleDirective'))
    .directive('myDirective', require('./myDirective'));

var myDirective = function ($rootScope) {

    var controller = ["$scope", "$element", function ($scope, element) {
        console.log('waiting for .on....');
        $scope.$on('shoppingCartReady', function (event, cache) {
             console.log('shoppingCartReady .on rcvd!');
        });
    }]

    function link($scope, element, attrs, baseCtrl) {

        $scope.$on('shoppingCartReady', function (event, msg) {
            console.log(msg);
        });
    }

    return {
        restrict: 'E',
        require: '^shopModuleDirective',
        link: link
    };
};

return ['$rootScope', myDirective];

I can see the log for

console.log('waiting for .on....');

But the .on is never received.

UPDATE:


I think the possible cause is due to scope's not being 'ready'.

Within my original broadcast from MainController, if i perform another after a setTime out, all .on are received:

MainController:

$scope.$broadcast('shoppingCartReady', msg);

setTimeout(function(){
    $scope.$broadcast('shoppingCartReady', msg);
}, 8000);
Oam Psy
  • 8,555
  • 32
  • 93
  • 157

2 Answers2

4

From the Documentation:

$broadcast(name, args);

Dispatches an event name downwards to all child scopes (and their children) notifying the registered $rootScope.Scope listeners.

As in your directive you are not creating a new scope I guess you are just on the same scope level as your MainController. In this case you are not in a childScope of MainController so the event can't be triggered there. I would try one of the following:

  1. Broadcast your events always via $rootScope.$broadcast. All other scopes are childs of $rootScope and so wherever you register to an event with $scope.$on, you will receive it. You may also be interested in Why do we use $rootScope.$broadcast in AngularJS?

  2. Give your directive an isolated $scope. That will act as a childScope to MainController and should work as well. Isolationg the $scope of a directive.

  3. In addition to option 1. you can also use $rootScope.$emit together with $rootScope.$on. For further information read $rootScope.$broadcast vs. $scope.$emit

I always chose option 1. as sometimes you don't want your directive to have an isolated scope.

Community
  • 1
  • 1
Fidel90
  • 1,828
  • 6
  • 27
  • 63
  • thanks for the above, have tried option 1.. It doesnt seem to improve things. Infact it looks like there is no difference. How would you recommend option 2? – Oam Psy Apr 27 '16 at 10:56
  • @OamPsy: Strange. Option `1.` always works for me. According option `2.` take a look at: https://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive – Fidel90 Apr 27 '16 at 11:03
  • @OamPsy: FYI, I added a third option. – Fidel90 Apr 27 '16 at 11:15
  • please see second UPDATE for possible 'scope not ready' cuase – Oam Psy Apr 27 '16 at 13:30
3

There is a race condition.

myDirective controller and post-link function are executed after MainController, and the listener is set up after shoppingCartReady event has been triggered.

A rule of thumb for good, testable directive design is to keep all scope-related logic in controller, linking functions and $onInit hook are used exactly for the things that should happen there and not anywhere else, like manipulations on compiled DOM content.

Less obvious but distinctive use case for link is the desirable order of execution (post-linking functions are executed in reverse order, from children to parent). If MainController becomes a directive, and scope.$broadcast('shoppingCartReady', msg) is executed in link function and not controller, this guarantees the proper order of execution.

Wrapping controller code into zero $timeout will also postpone the code

$timeout(function(){
    $scope.$broadcast('shoppingCartReady', msg);
});

to be executed after linking functions in children directives, but will also designate some code smell. This is one of the reasons why scope events are less desirable ways of directive communication and are prone to be anti-patterns.

The alternative way of handling this depends on what shoppingCartReady event is about, but in current case it is redundant: it has already happened at the moment when children controllers are executed, they can safely assume that 'shopping cart is ready'.

A suggested reading for this topic: Directive Controller And Link Timing In AngularJS .

Estus Flask
  • 206,104
  • 70
  • 425
  • 565