Nested directives don't work as expected - angularjs

I have a generic directive
genericDirective
that is supposed to choose another specific directive
type1 directive if obj.type == "type1"
type2 directive if obj.type == "type2"
HTML
<div ng-controller="MainCtrl">
<div class="genericdirective" ng-repeat="obj in someArray"></div>
</div>
Javascript
var app = angular.module("myApp", []);
app.controller("MainCtrl", function ($scope) {
$scope.someArray = [
{type:"type1",title:"lorem"},
{type:"type2",title:"ipsum"},
{type:"type2",title:"dolor"}
];
});
app.directive("genericdirective", function(){
return{
restrict: "C",
template: "<div class='{{obj.type}}'>genericdirective</div>"
};
});
app.directive("type1", function(){
return{
restrict: "C",
template: "<div>type1</div>"
};
});
app.directive("type2", function(){
return{
restrict: "C",
template: "<div>type2</div>",
};
});
Output HTML
<div class="genericdirective ng-scope" ng-repeat="obj in someArray">
<!-- Not replaced by the actual directive -->
<div class="type1">genericdirective</div>
</div>
<div class="genericdirective ng-scope" ng-repeat="obj in someArray">
<!-- Not replaced by the actual directive -->
<div class="type2">genericdirective</div>
</div>
<div class="genericdirective ng-scope" ng-repeat="obj in someArray">
<!-- Not replaced by the actual directive -->
<div class="type2">genericdirective</div>
</div>
Any idea why these are not replaced by the actual directives?

By using the return in your genericDirective:
app.directive("genericdirective", function(){
return{
restrict: "C",
template: "<div class='{{obj.type}}'>genericdirective</div>"
};
});
You are returning the link function. The link phase happens after the compile phase. So, by the time you are resolving this template, angular cannot "compile in" your child directives and then link them.
You need to define a compile function and set up the directive at that time in order to modify the html that angular will consider. Any time that you need to manipulate the html before linking the $scope, you probably are wanting to make changes during the compile phase.
To read more about compile and link see the docs here. The section titled "Compilation process, and directive matching" is very helpful.

Building on Davin's answer, if you change your directive to this it should work:
app.directive("genericdirective", function($compile){
return{
restrict: "C",
link: function (scope, element, attrs) {
element.append('<div class="' + scope.obj.type + '">genericdirective</div>');
$compile(element.contents())(scope);
}
};
});

Related

Put directive template as a content of container the directive is attached to

Normally, when i'm creating a directive, i have two possible ways to deal with directive templates. I can create a html file somewhere on my server and use it's URL in directive settings:
#directives.directive "someDirective", [
'$rootScope'
($rootScope) ->
controller: ($scope) ->
link: (scope, element, attrs) ->
return
restrict: 'A'
templateUrl: 'path/to/template.html'
scope: {
eventId: '#'
}
]
or i can put the script tag somewhere in the markup and use it's ID as a templateUrl:
<script type="text/ng-template" id="template.html">
<p>Hello {{ name }}</p>
</script>
However, i was wondering - is that possible to put that template to directive container tag as a body of it? That's how i want it to look:
<div my-ng-directive>
<p>Hello {{ name }}</p>
</div>
I think what you're looking for is ng-transclude,
basically ng-transclude allows your to "create" a slot in your original template that will contain the content of the original html element, so in your example a basic directive that does what you want will look like:
angular.module('myModule', [])
.directive('myNgDirective', function() {
return {
controller: function($scope) {
$scope.name = "Test";
},
transclude: true,
restrict: 'A',
template: '<div><h1>The directive</h1><div ng-transclude></div></div>'
}
});
This code:
<div my-ng-directive>
<p>Hello {{ name }}</p>
</div>
Will return:
<div>
<h1>The directive</h1>
<div>
<p>Hello {{ name }}</p>
</div>
</div>

Concat Template With Custom Directive Template

I'm not sure if its possible to achieve what I want. I'll try to explain it with an example:
a custom directive:
appDirectives.directive("myTestDirective",
function() {
return {
restrict: "E",
templateUrl: "<div> Some template here... {{ testObject }} <div>",
scope: {
'testObject' = '#testObject'
}
};
}]);
Use directive in a tempalte:
<my-test-directive testObject="And some more here...">
<div>
I also want to be in the template!
</di>
</my-test-directive>
And I want to achieve this template:
<div> Some template here... And some more here... <div>
<div>
I also want to be in the template!
</di>
You can do this with transclusion. Just add a param to the directive, and use ng-transclude on the element you want to have the contents be inserted.
You might have to remove some the original since the transclusion needs an element to operate on, but this is the basic idea.
angular.module('test', [])
.directive('myTestDirective', function() {
return {
restrict: "E",
template: "<div> Some template here... {{testObject}} <div><div ng-transclude></div>",
scope: {
testObject: '#'
},
transclude: true
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test">
<my-test-directive test-object="And some more here...">
<div>
I also want to be in the template!
</div>
</my-test-directive>
</div>

How to write an angularjs directive that makes use of both scope and attributes and refer it thru compiled partial?

I want to write a directive which takes advantage of custom attributes, as follows:
<plant-stages
title="Exploration<br/>du cycle de<br/>développement<br/>de la plante"
></plant-stages>
The controller is currently as follows:
app.directive('plantStages', function () {
return {
restrict: 'AE',
templateUrl: 'corn.figure.plant.stages.html',
link: function (scope, element, attrs) {
scope.title = attrs.title;
}
};
});
The partial is as follows:
<figure class="cornStages">
<div>
<p>{{title}}</p>
</div>
<div ng-repeat="stage in stages">
<div class="stage{{stage.stage}}"></div>
<div>
BBCH : {{stage.bbch}}<br/>
{{stage.displayName}}
</div>
</div>
</figure>
The partial makes use of some scope model variables.
And {{title}} should support plain HTML injection out of the view which embeds it, hence should be compiled. I tried to support this but without success.
What modification should I make to have the HTML compiled?
A bonus question: when I pass the attribute in, I create a dummy title variable in the scope that persists where it should only be local. How would one make changes to handle this?
If you want to wrap HTML in your custom directive take a look at the transclude option (see docs):
module.directive('myDirective', function() {
return {
restrict: 'E',
transclude: true,
template: '<div ng-transclude></div>'
};
});
This enables you to place HTML within the directive tag which can be used in the template:
<div ng-controller="Controller">
<my-directive>
<h1>Test</h1>
</my-directive>
</div>
In case you really want to pass HTML via an attribute use ng-bind-html. This requires the ngSanitize module:
module.directive('myDirective', function () {
return {
restrict: 'E',
template: '<div ng-bind-html="title"></div>',
scope: {
title:'#'
}
};
});
I added this to your fiddle.

Using ng-switch in directive with transclude

I am trying to create a template that shows some transcluded content. When I use ng-show everything works fine, but using ng-if or ng-switch gives me problems. I get this error message: Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found
I understand that ng-switch creates a new scope. But the transclude should still go up to the parent chain. Is this a defect in angularjs? See http://jsfiddle.net/HgvP7/
Here is my html, modified from the documentation example:
<div ng-app="docsTransclusionExample">
<div ng-controller="Ctrl">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
<!-- my-dialog.html -->
<script type="text/ng-template" id="my-dialog.html">
<div ng-switch="1+1">
<div ng-switch-when="2">
<div ng-transclude></div>
</div>
</div>
</script>
</div>
And the code:
angular.module('docsTransclusionExample', [])
.controller('Ctrl', function($scope) {
$scope.name = 'Tobias';
})
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
templateUrl: 'my-dialog.html',
link: function (scope, element) {
scope.name = 'Jeff';
}
};
});

ng-click attribute on angularjs directive

I think it should be easy to use the well known angular attributes on a directive out of the box.
For example if the name of my directive is myDirective I would like to use it this way:
<div ng-controller="myController">
<my-directive ng-click="doSomething()"><my-directive>
</div>
instead of needing to define a custom click attribute (onClick) as in the example below
<div ng-controller="myController">
<my-directive on-click="doSomething()"><my-directive>
</div>
It seems that ng-click can work, but then you need to specify ng-controller on the directive tag too which I don't want. I want to define the controller on a surrounding div
Is it possible to use ng-click on a directive together with a controller defined on a parent html element?
Here is updated code. Maybe is this what you were looking for.
Html:
<div data-ng-app="myApp">
<div data-ng-controller="MyController">
<my-directive data-ng-click="myFirstFunction('Hallo')"></my-directive>
<my-directive data-ng-click="mySecondFunction('Hi')"></my-directive>
</div>
</div>
Angular:
var app = angular.module('myApp', []);
app.directive('myDirective', function(){
return {
restrict: 'EA',
replace: true,
scope: {
eventHandler: '&ngClick'
},
template: '<div id="holder"><button data-ng-click="eventHandler()">Call own function</button></div>'
};
});
app.controller('MyController', ['$scope', function($scope) {
$scope.myFirstFunction = function(msg) {
alert(msg + '!!! first function call!');
};
$scope.mySecondFunction = function(msg) {
alert(msg + '!!! second function call!');
};
}]);
Edit
Check solution that I made in jsFiddler is that what you were looking for?
http://jsfiddle.net/migontech/3QRDt/1/

Resources