kamituel
kamituel

Reputation: 35970

$compile nested directive from within another directive

TL;DR; jsFiddle here.

I want to use two directives (kmOuter and kmInner) as nested directives:

<div km-outer>
    <div km-inner></div>
    <div km-inner></div>
    <!-- ... -->
</div>

So I declared them as follows. Please note that inner directive requires outer's controller:

app.directive('kmOuter', function () {
    return {
        restrict: 'AC',
        scope: null,
        controller: function ($scope) {
            this.childAdded = function () {
                console.log('Child added.');
            };
        }
    };
});

app.directive('kmInner', function () {
    return {
        restrict: 'AC',
        require: '^kmOuter',
        template: '<div>Lorem ipsum</div>',
        link: function (scope, elem, attrs, kmOuterController) {
            kmOuterController.childAdded();
        }
    };
});

That works just fine (.childAdded() is being called, among others). Now, I want to dynamically insert new instances of inner directive. This action is being triggered from some third, unrelated directive:

app.directive('kmChildAdder', function ($compile) {
    return {
        restrict: 'AC',
        scope: {
            target: '@kmChildAdder'
        },
        link: function (scope, elem) {
            console.log(scope);
            var target = document.querySelector(scope.target);

            angular.element(elem[0]).bind('click', function () {
                // Error is here
                var newInner = $compile('<div km-inner></div>')(scope)[0];
                target.appendChild(newInner);
            });
        }
    };
});

Used like this:

<button km-child-adder="[km-outer]">Add child</button>

But it breaks with the following message:

Error: [$compile:ctreq] Controller 'kmOuter', required by directive 'kmInner', 
can't be found!

.childAdded() isn't called anymore.

How can I fix this? Or maybe this design is itself broken and I should reorganise my code?

Upvotes: 1

Views: 1287

Answers (2)

kamituel
kamituel

Reputation: 35970

I think I made it, borrowing from @Mobin Skariya's answer.

Key was to $compile only the inserted element, not all elements:

link: function (scope, elem) {
    var target = angular.element(document.querySelector(scope.target));

    angular.element(elem[0]).bind('click', function () {
        var newInner = angular.element('<div km-inner="param"/>');
        target.append(newInner);

        scope.$apply(function () {
            $compile(newInner)(scope);
        });
    });
}

I've prepared jsFiddle with example where third, unrelated directive inserts ad compiles inner directive with working, two-way data bindings - you will find it here. Nice thing about it is that third directive (kmChildAdder) can insert inner directives taking bindings from multiple, separate controllers.

Upvotes: 3

Mobin Skariya
Mobin Skariya

Reputation: 392

Made some edits in your code. Code given in jsFiddle link

link: function (scope, elem) {
            console.log(scope);
            var target = document.querySelector(scope.target);
            angular.element(elem[0]).bind('click', function () {
                var newInner = '<div km-inner></div>';
                angular.element(target).append(newInner);
                $compile(target)(scope)
            });
        }

Check whether this is what you expect.

Upvotes: 1

Related Questions