Error: [$compile:multidir] for Component Directive

2019-07-29 04:56发布

问题:

I need a 'sticky' directive that adds a css class to element when it is at the top of a page and also signals the changes in its state. For that reason I define a scope like { onStickyChange: '&' }. Now I'd like to use the directive in an angularjs component like:

<my-component sticky on-sticky-change="$ctrl.onStickyChange(sticky)">
</my-component>

I expected the directive to notify the parent controller when the my-component is sticked/unsticked. However I get the following error:

Error: [$compile:multidir] Multiple directives [myComponent, sticky] asking for new/isolated scope on: http://errors.angularjs.org/1.6.2/$compile/multidir?p0=myComponent&p1=&p2=s…icky%3D%22%22%20on-sticky-change%3D%22%24ctrl.onStickyChange(sticky)%22%3E at angular.js:68 at assertNoDuplicate (angular.js:10049) at applyDirectivesToNode (angular.js:9237) at compileNodes (angular.js:8826) at compileNodes (angular.js:8838) at compileNodes (angular.js:8838) at compile (angular.js:8707) at angular.js:1847 at Scope.$eval (angular.js:18017) at Scope.$apply (angular.js:18117)

app.component('myComponent', {
    template: '<div style="height: 6000px; width: 100%; background-color: #ccf></div>',
    controller: function () {
        this.is = 'nothing';
    }
});
app.directive('sticky', ['$window', function($window) {
    return {
        restrict: 'A',
        scope: { onStickyChange: '&' },
        link: link
    };
    function link(scope, element, attributes) {
        if (typeof scope.onStickyChange !== 'function' ) {
            throw Error('Sticky requires change handler');
        }

        let sticky = isSticky(element);

        angular.element($window).bind('scroll', _.throttle(onWindowScroll, 60));

        function onWindowScroll() {
            let isNowSticky = isSticky(element);

            if (sticky === isNowSticky) {
                return;
            }

            sticky = isNowSticky;

            if (sticky) {
                element.addClass('sticky');
            }
            else {
                element.removeClass('sticky');
            }

            scope.onStickyChange({ sticky: sticky });
        }

        function isSticky(element) {
            return window.scrollTop() > element.position().top;
        }
    }

}]);

How is it possible to solve the problem?

PS: there is a plunk.

回答1:

The error occurs because both the component directive and the attribute directive are trying to create an isolate scope.

From the Docs:

Error: $compile:multidir

Multiple Directive Resource Contention

This error occurs when multiple directives are applied to the same DOM element, and processing them would result in a collision or an unsupported configuration.

Example scenarios of multiple incompatible directives applied to the same element include:

  • Multiple directives requesting isolated scope.

— AngularJS Error Reference - Error: $compile:multidir

The solution is to re-write the attribute directive to work without creating an isolate scope:

app.directive('sticky', function($window, $parse) {
    return {
        restrict: 'A',
        ̶s̶c̶o̶p̶e̶:̶ ̶{̶ ̶o̶n̶S̶t̶i̶c̶k̶y̶C̶h̶a̶n̶g̶e̶:̶ ̶'̶&̶'̶ ̶}̶,̶
        scope: false,
        link: postLink
    };
    function postLink(scope, elem, attrs) {

        //code ...

            ̶s̶c̶o̶p̶e̶.̶o̶n̶S̶t̶i̶c̶k̶y̶C̶h̶a̶n̶g̶e̶(̶{̶ ̶s̶t̶i̶c̶k̶y̶:̶ ̶s̶t̶i̶c̶k̶y̶ ̶}̶)̶;̶
            $parse(attrs.onStickyChange)(scope, { sticky: sticky });

        //code ...
    }
});

Use the $parse Service to evaluate the Angular Expression on the on-sticky-change attribute.



回答2:

You can't have two directives requesting isolate scope on the same element-- it creates internal conflicts in Angular. If you need two directives for the same element, you can leverage the attrs argument passed to your link function in order to capture whatever values you need (and you'll need to remove your isolate scope property of the directive).