Adding a controller to an element directive - angularjs

When adding a controller to a element directive, for example:
.directive('hello', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div class="hello" ng-transclude></div>'
};
});
I'm unable to access the scope of the controller:
.controller('HelloCtrl', function($scope) {
$scope.hello = "Hello World";
});
<hello ng-controller="HelloCtrl">
<h1>Hello Directive</h1>
<p>{{ hello }}</p>
</hello>
In this case {{ hello }} is undefined. The directive doesn't create a child nor an isolated scope. I also tried accessing the property with {{ $parent.hello }}.
What is happening here?
I created a CodePen to demonstrate this behaviour: http://codepen.io/jviotti/pen/ktpbE

Per the docs...
transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside.
Therefore you need to move the ng-controller="HelloCtrl" declaration to an element higher up in scope.
Here is your CodePen fixed http://codepen.io/anon/pen/BjKHG

Related

AngularJS Directive Transclude parent scope

I'm working on a directive and I'm using transclude so the inner element of the directive use is used inside it.
Let's say that this is my directive's view:
<div>
<div ng-repeat="opt in options">
<ng-transclude></ng-transclude>
</div>
</div>
Directive:
app.directive("myDirective", function(){
return {
restrict: "E",
transclude: true,
templateUrl: 'my-directive.html',
scope: {
options: '='
}
};
});
And a simple use of it:
<my-directive options="someOptions">
<p>{{someObject[$parent.opt]}}</p>
</my-directive>
This works just fine. My problem with this solution is that that $parent.opt is not very readable and clear...
Is there any other option?
Thanks
Your directive seems to be a very specific one.
How about passing the parent to the directive too?
<my-directive options="someOptions" object="someObject"></my-directive>
In the directive:
<div>
<div ng-repeat="opt in options">
<p>{{object[opt]}}</p>
</div>
</div>
And then add object: '<' in your isolated scope declaration.

why does having a <p/> tag within a directive's transcluded content change the scope hierarchy?

Best explained with a couple fiddles. I'm using these simple directives in both:
var demo = angular.module('demo', []);
demo.directive('redBox', function() {
return {
restrict: 'E',
replace: false,
transclude: true,
template: '<div class="bg-red size-med"><b>I am inside a red box.</b><div ng-transclude></div></div>'
}
});
demo.directive('blueBox', function() {
return {
restrict: 'E',
replace: false,
transclude: true,
template: '<div class="bg-blue size-med"><b>I am inside a blue box.</b><div ng-transclude></div></div>'
}
});
function MyCtrl ($scope) {
};
I include both of these and print the scope ids in Fiddle #1. This works as I expect - the scopes of both redBox and blueBox are children of MyCtrl's scope.
<div ng-app='demo'>
<div ng-controller='MyCtrl'>
My scope's id is {{$id}}. <br/> My parent scope's id is {{ $parent.$id }}.
<red-box>
My scope's id is {{$id}}. <br/> My parent scope's id is {{ $parent.$id }}.
</red-box>
<blue-box>
My scope's id is {{$id}}. <br/> My parent scope's id is {{ $parent.$id }}.
</blue-box>
</div>
</div>
In Fiddle #2, I simply add a tag in the content of one of the divs. This changes the scope hierarchy! Even though they are not nested, the scope of one directive is the parent of that of the other.
What's going on here? How could this change the parent-child relationship of the scopes?
In Html5 <p /> means <p>, because <p> is not a "self-closing tag".
So, this:
<p/>My parent scope's id is {{ $parent.$id }}.
will be interpreted by your browser like this:
<p>My parent scope's id is {{ $parent.$id }}.</p>
You may want to have a look at this question.

Directive with isolated scope unexpectedly accessing parent scopes

I have created a directive my-directive with isolated scope, but it looks like it is able to access property div1 of $rootScope and property div2 of its parent scope $scope of controller1.
What am I missing?
Javascript:
angular.module('app', [])
.controller('controller1', ['$scope',
function ($scope) {
}])
.directive('myDirective', [
function () {
return{
restrict: 'A',
replace: true,
scope: {
myDirective:'='
}
};
}]);
HTML:
<body>
<div id="1" ng-app="app" ng-init="div1='div1'">
<div id="2" ng-controller="controller1" ng-init="div2='div2'">
<div id="4" my-directive="value" ng-init="div4='div4'">
{{div4}}<br/>
{{div1}}<br/>
{{div2}}<br/>
</div>
</div>
</div>
</body>
Output:
div4
div1
div2
You should use transclude function inside the directive, otherwise that values will be bound to parent scope. Check this: http://pucksart.com/transclude-big-mistery/
The scope is isolated within the directive's template. Give your directive a template or write a link function and you will see that the scope is isolated. When you are access the directive scope's ancestors, this is happening outside the directive and then the parsed html is being 'transcluded' into your directive template, which is otherwise empty.
It is an interesting question though. I had not until now realized that omitting the template of a directive is effectively the same as including transclude: true, template: '<div ng-transclude></div>' in the directive definition.
Though you didn't include two of these properties, you actually did this:
.directive('myDirective', [
function () {
return{
restrict: 'A',
replace: true,
transclude: true,
template: '<div ng-transclude></div>',
scope: {
myDirective:'='
}
};
}]);
Also note myDirective: '=' should be myDirective: '#' because it is just a string.

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';
}
};
});

Resources