Reputation: 715
I've built a small pattern that recursively creates different sub-directives based on the model. I'm using $compile
to build child directives recursively, then append them to the parent.
The directive building itself seems to work just fine, but for some reason, embedded expressions or ng-bind
or interpolation doesn't seem to work on nested directive.
Here's a snippet:
app.directive("child", function ($compile) {
function getTemplate(depth) {
if (depth % 2 == 0) {
return "<even depth='deeper'/>"
} else {
return "<odd depth='deeper'/>"
}
}
return {
scope: {
depth: "="
},
link: function linker($scope, $element) {
if ($scope.depth == 0) {
var child = angular.element("<span ng-bind='depth'/>");
child = $compile(child)($scope);
$element.append(child);
} else {
$scope.deeper = $scope.depth - 1;
var child = angular.element(getTemplate($scope.depth));
child = $compile(child)($scope);
$element.append(child);
}
}
}
})
Basically in this test the directive will recursively dive down until the depth
reaches 0
, then spit out a <span>
element.
The expected result should be a span element with the value of 0
. But it doesn't seem to evaluate. Using <span>{{depth}}</span>
also results in in a literal html instead of evaluating the contents.
I'm trying to achieve a result of nested <even><odd><even>
directives removing the surrounding <child>
- directive.
Here's a complete jsFiddle: https://jsfiddle.net/eg1e1aLz/
The resulting DOM should look like this:
<test depth="4" class="ng-isolate-scope">
<even depth="depth-1" class="ng-scope ng-isolate-scope">
<odd depth="depth-1" class="ng-scope ng-isolate-scope">
<even depth="depth-1" class="ng-scope ng-isolate-scope">
<odd depth="depth-1" class="ng-scope ng-isolate-scope"><span ng-bind="depth" class="ng-binding ng-scope">0</span></odd>
</even>
</odd>
</even>
</test>
Upvotes: 3
Views: 668
Reputation: 28750
Sorry if I'm missing the context of what you're trying to accomplish, but I don't believe you need to use sub directives or anything like that. You can just use the test directive and continuously build out the children and then bind the scope to the last one:
angular.module("app", [])
.directive("test", function ($compile) {
return {
scope: {
depth: "="
},
link: function linker($scope, $element, $attrs) {
var element = $element;
for (var x = 0; x < $scope.depth; x++) {
var elementType = x % 2 === 0 ? 'even' : 'odd';
var subElement = angular.element(document.createElement(elementType));
element.append(subElement);
element = subElement;
}
var span = angular.element('<span ng-bind="depth" />');
element.append(span);
$compile(span)($scope);
}
}
})
Fiddle: https://jsfiddle.net/ffyv0zmy/
I realise in your example you want the output to be "0" for the depth but you can hard code that as I'm not sure as to it's purpose.
Resulting HTML:
<test depth="4" class="ng-isolate-scope">
<even>
<odd>
<even>
<odd>
<span ng-bind="depth" class="ng-binding ng-scope">4</span>
</odd>
</even>
</odd>
</even>
</test>
The nice thing about this is you don't have to attach scope to the even/odd elements unless you want too.
You can still wire up directives to each of the odd/even elements but then you'll have to $compile the $element instead to attach all the scopes.
Upvotes: 0
Reputation: 8465
Your jsfiddle is perfectly fine, the only problem is a small bit of logic.
When you compile the child directive you shouldn't append child.html()
simply because that will append an HTML without bindings and watchers connected to them. Instead, you should append the whole child
https://jsfiddle.net/eg1e1aLz/2/
child = $compile(child)($scope);
$element.append(child);
Upvotes: 0
Reputation: 16540
Based on your comments below and the update to the question, how about the following...
It uses the template function getTemplate
to build the structure, and identical directives: odd
and even
as placeholders for building the functionality.
var app = angular.module("app", []);
app.directive("test", function($compile) {
return {
scope: {
depth: "="
},
link: function linker($scope, $element, $attrs) {
// template accessible to the child directives
$scope.getTemplate = function(depth) {
if (depth <= 0) {
return "<span ng-bind='depth'/>"; // also bindings like {{depth}} work
} else if (depth % 2 === 0) {
return "<even depth='depth-1'></even>"; // bindings like {{depth}} work
} else {
return "<odd depth='depth-1'></odd>";
}
}
var child = angular.element($scope.getTemplate($scope.depth));
$compile(child)($scope);
$element.append(child);
}
}
});
app.directive("odd", function($compile) {
return {
scope: {
depth: "="
},
link: function linker($scope, $element) {
$scope.getTemplate = $scope.$parent.getTemplate; // bring template into current scope
var child = angular.element($scope.getTemplate($scope.depth));
$compile(child)($scope);
$element.append(child);
}
}
})
app.directive("even", function($compile) {
return {
scope: {
depth: "=",
},
link: function linker($scope, $element) {
$scope.getTemplate = $scope.$parent.getTemplate; // bring template into current scope
var child = angular.element($scope.getTemplate($scope.depth));
$compile(child)($scope);
$element.append(child);
}
}
})
var controller = app.controller("controller", function($scope) {});
Updated Fiddle: https://jsfiddle.net/bda411fj/15/
Result:
<test depth="4" class="ng-isolate-scope">
<even depth="depth-1" class="ng-binding ng-scope ng-isolate-scope">
<odd depth="depth-1" class="ng-binding ng-scope ng-isolate-scope">
<even depth="depth-1" class="ng-binding ng-scope ng-isolate-scope">
<odd depth="depth-1" class="ng-binding ng-scope ng-isolate-scope">
<span ng-bind="depth" class="ng-binding ng-scope">0</span>
</odd>
</even>
</odd>
</even>
</test>
Upvotes: 3
Reputation: 12478
Since you don't want to the html(), don't use child.html().
Use the child itself.
Check the documentation here
Check the snippet below. Added based on the sample snippet in your question
var app = angular.module("app", []);
app.directive("test", function($compile) {
return {
scope: {
depth: "="
},
link: function linker($scope, $element, $attrs) {
var child = angular.element("<child depth='depth'/>");
child = $compile(child)($scope);
$element.append(child);
}
}
});
app.directive("child", function($compile) {
function getTemplate(depth) {
return depth % 2 == 0 ? "<even depth='depth-1'/>" : "<odd depth='depth-1'/>"
}
return {
scope: {
depth: "="
},
link: function linker($scope, $element) {
if ($scope.depth == 0) {
var child = angular.element("<span ng-bind='depth'/>");
child = $compile(child)($scope);
$element.append(child);
} else {
var child = angular.element(getTemplate($scope.depth));
child = $compile(child)($scope);
$element.append(child);
}
}
}
})
app.directive("odd", function($compile) {
return {
scope: {
depth: "="
},
link: function linker($scope, $element) {
var child = angular.element("<child depth='depth'/>");
child = $compile(child)($scope);
$element.append(child);
}
}
})
app.directive("even", function($compile) {
return {
scope: {
depth: "="
},
link: function linker($scope, $element) {
var child = angular.element("<child depth='depth'/>");
child = $compile(child)($scope);
$element.append(child);
}
}
})
var controller = app.controller("controller", function($scope) {});
body {
padding: 5px 5px 5px 5px !important;
font-size: 30px;
}
test,
child,
even,
odd {
padding: 5px 5px 5px 5px;
margin: 5px 5px 5px 5px;
border: 1px solid black;
}
test {
background-color: aliceblue !important;
}
child {
background-color: beige !important;
}
even {
background-color: lightgreen !important;
}
odd {
background-color: lightcoral !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>
<body ng-app="app" ng-controller="controller">
<test depth="4"></test>
</body>
Upvotes: 0
Reputation: 11
The compiled part of your template need to refer as child only instead on child.html()
.
Change your code at 3 places from $element.append(child.html());
to $element.append(child);
This will start printing the depth value you are looking for. Is there anything else you are looking for?
Upvotes: 1
Reputation: 1718
Your html might not be sanitized that's why it is not compiling try using ngSanatize ti might solve your problem https://docs.angularjs.org/api/ngSanitize/service/$sanitize it will remove all the potentially dangerous tokens and will return you a proper html.
Upvotes: 0