Andrei Toutoukine
Andrei Toutoukine

Reputation: 107

Error: [$compile:multidir] for Component Directive with Attribute Directive

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.

Upvotes: 1

Views: 1646

Answers (2)

georgeawg
georgeawg

Reputation: 48968

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.

Upvotes: 2

Alexander Nied
Alexander Nied

Reputation: 13668

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

Upvotes: 1

Related Questions