Broadcast not received in directive

瘦欲@ 提交于 2019-12-12 13:35:05

问题


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

回答1:


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 .




回答2:


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.



来源:https://stackoverflow.com/questions/36882975/broadcast-not-received-in-directive

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!