I'm implementing a navigation directive that should have a elements for each nav item:
<navigation title="My Web Page">
Home
About
</navigation>
How Can I get access to those anchors? Accessing the element's children in link() only returns the template's children, not the 'a''s that I'm looking for.
.directive('navigation', function () {
return {
template: template,
restrict: 'E',
replace: 'true',
scope: {
title: '#'
},
link: function postLink(scope, element, attrs) {
// This only looks in the directive's template
console.log($(element).find('a'));
}
};
});
What am I missing? I'm looking forward to attatch an array of the anchors in the directive's scope and iterate trough them within the template.
In order to move the original content within the new template you need to use the transclude property. When translude is set to true the directive will delete the original content but also make it available for reinsertion within your template through the ng-translude directive. See example below.
Without transluding the orginal data the anchor tags are removed and that is why your link function cannot find them.
.directive('navigation', function () {
return {
template: '<div>Tansclude data here: <span ng-translude></span></div>',
restrict: 'E',
replace: true,
transclude: true,
scope: {
title: '#'
},
link: function postLink(scope, element, attrs) {
console.log($(element).find('a'));
}
};
});
I am a bit confused where your nav-tems are coming from, but I'll give it a go
I'm assuming your nav elements are defined in a controller that is parent to the directive
function myCtrl ($scope){
$scope.navArray=[{title: 'Link1', href: 'www.example.com'}, {...}];
}
you would them have to declare the array as an attribute in your directive
<navigation nav="navArray"></navigation>
and two-way-bind it to the scope of your directive
.directive('navigation', function () {
return {
template: '<div><a ng-repeat="link in nav" href="link.href">{{link.title}}</a></div>',
restrict: 'E',
replace: 'true',
scope: {
nav: '='
},
link: function postLink(scope, element, attrs) {
}
};
});
Remember, you want to stay away from DOM manipulation in your link function. Instead, I recommend using ng-repeat in your template, and make sure the array of nav items is passed to your directive's scope.
Edit: See Fiddle http://jsfiddle.net/nicolasmoise/8YQPh/3/
Related
I have a directive defined as such:
angular.module("main.directives").directive("todo", function() {
return {
restrict: "A",
scope: {
todo: "=entity"
},
replace: false,
templateUrl: "todo.html",
link: function(scope, element, attributes) {
}
};
});
which I use like this from templates:
<div todo entity="todoData"></div>
todoData comes from a controller or some other the local scope. Anyway it all works like a charm, so that's cool!
My question is the following: How do I have to modify the directive definition so that it also works with a markup of this type:
<div todo="todoData"></div>
As you can see the data is now passed in as the value of the attribute marking the directive. Just like ng- directives do:
<p ng-repeat="bit in data"></p>
<p ng-click="whatever()"></p>
How can that be achieved?
Thanks
Replace
scope: {
todo: "=entity"
},
by
scope: {
todo: "=todo"
},
or simply
scope: {
todo: "="
},
When you write an attribute directive in angularjs you might want to have it fed by an attribute value.
For example, something like this:
<div my-attribute="somevalue"></div>
How then do you create a new scope that takes that in? It's not obvious. Any here's how you do it:
app.directive('myAttribute', function() {
return {
restrict: 'A',
scope: {
myAttribute: '='
},
template: '<div style="font-weight:bold">{{ myAttribute | number:2}}</div>'
};
});
The trick to notice is that the "self attribute" because of the name of the attribute in camel case.
Here is the Reference to This Answer!
you must eval the value of the attribute inself. The isolate scope is not one of my favorites kind of scopes for a directive. Instead you can use, scope = true, to inherit from the parent controller. This will allow you to use all the variable exposes on the parents scopes.
in your case.
angular.module("main.directives").directive("todo", function() {
return {
restrict: "A",
scope: true,
replace: false,
templateUrl: "todo.html",
link: function(scope, element, attributes) {
scope.todo = scope.$eval(attributes[todo]);
}
};
});
now your todo directive could be used. Like any other ng- directive.
example:
<div todo="getTodoList()"></div>
<div todo="[{description:'hahahha'}]"></div>
I've written a directive without the transclude option.
But now it would be nice when I could activate the transclude function/option when calling the directive with another attribute or something else if possible.
If that's not possible the only Way I see is, to copy the directive and add the Transclude in the second one, but then I've doubled my code whtat I'm not willing to do.
any Ideas how to optionally activate the transclude in Angular 1.2.x
Edit:
alternate problem is also that I need to set the ng-transclude in my directive Template because its a big one and only a few rows can be replaced by the transclusion content.
You could conditionally modify a template to include ng-transclude in the compile: function.
.directive('foo', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
templateUrl: 'foo.html',
compile: function (element, attrs) {
if (attrs.bar !== undefined) {
element.find('.may-transclude-here')
.attr('ng-transclude', '');
}
return function postLink(scope, element, attrs, controllers) {
scope.listEntries = ['apple', 'banana', 'tomato'];
};
}
}
})
and a html template:
<div class="foo">
<h4>Directive title</h4>
<div class="may-transclude-here" ng-repeat="item in listEntries">
Original content: {{item}}
</div>
<span>blah blah blah</span>
</div>
but contents that are transcluded via ng-transclude will not bind with a scope of each item created by ng-repeat. In case you also need the binding, here is the modified version of ng-transclude that do the correct scope binding.
.directive('myTransclude', function () {
return {
restrict: 'EAC',
link: function(scope, element, attrs, controllers, transcludeFn) {
transcludeFn(scope, function(nodes) {
element.empty();
element.append(nodes);
});
}
};
});
Plunker example: http://plnkr.co/edit/8lncowJ7jdbN0DEowdxP?p=preview
hope this helps.
How do I get the input element from within the directive before the template overwrites the contents?
html
<div xxx>
<input a="1" />
</div>
js
app.directive('xxx', function(){
return {
restrict: 'A',
template: '<p></p>',
replace: true, //if false, just leaves the parent div, still no input
compile: function(element, attrs) {
console.log(element);
return function (scope, iElement, iAttrs) {
}
}
};
});
i am on angular 1.0.x, I cannot pass in optional scope parameters with the '=?' syntax and i want to be able to override a portion of the default template of the directive in a very flexible way. instead of adding a scope variable or attribute everytime that I just plan on passing through the directive, I want to be able to supply the whole element to be used.
edit
the input must retain the scope of the directive, and not the parent.
edit
I am trying to include a partial template inside a directive that will overwrite a piece of the actual template. The piece I am including therefore needs to have access to the directive's scope and not the parent's.
Update
It seems if I do not provide a template or a template URL and instead replace the contents manually using the $templateCache I can have access to the inner elements. I want to let angular handle the template and the replacement though and just want to be able to access the contents in the directive naturally before they get replaced.
Solution
Plunkr
html
<body ng-controller="MainCtrl">
<div editable="obj.email">
<input validate-email="error message" ng-model="obj.email" name="contactEmail" type="text" />
</div>
</body>
js
app.controller('MainCtrl', function($scope) {
$scope.obj = {
email: 'xxx'
};
});
app.directive('editable', function($log){
return {
restrict: 'A',
transclude: true,
template: '<div ng-show="localScopeVar">{{value}}<div ng-transclude></div></div>',
scope: {
value: '=editable'
},
link: function(scope) {
scope.localScopeVar = true;
}
};
});
app.directive('validateEmail', function($log){
return {
restrict: 'A',
require: 'ngModel',
scope: true,
link: function(scope, el, attrs, ctrl) {
console.log(attrs['validateEmail']);
}
};
});
I believe you're looking for the transclude function (link is to 1.0.8 docs). You can see what's going on with:
app.directive('xxx', function($log){
return {
restrict: 'A',
transclude: true,
compile: function(element, attrs, transclude) {
$log.info("every instance element:", element);
return function (scope, iElement, iAttrs) {
$log.info("this instance element:", element);
transclude(scope, function(clone){
$log.info("clone:", clone);
});
}
}
};
});
I'm have this code:
<div ng-repeat="param in item.parameters">
<rating param_item="{{param}}"></rating>
</div>
how can i pass the given attribute to the directive template?
Personally, I like creating child scope for element directives:
http://plnkr.co/edit/Dz7fcZaT4KdRDa869rwm?p=preview
app.directive('rating', function() {
return {
restrict: 'E',
scope: {
paramItem: '#'
},
link: function(scope, element, attr){
console.log(scope.paramItem);
}
}
});
There are a couple other ways too, depending on how you expect your directive to work.
I created a very simple directive which displays a key/value pair. I would like to be able to automatically hide the element if the transcluded content is empty (either zero length or just whitespace).
I cannot figure out how to access the content that gets transcluded from within a directive.
app.directive('pair', function($compile) {
return {
replace: true,
restrict: 'E',
scope: {
label: '#'
},
transclude: true,
template: "<div><span>{{label}}</span><span ng-transclude></span></div>"
}
});
For example, I would like the following element to be displayed.
<pair label="My Label">Hi there</pair>
But the next two elements should be hidden because they don't contain any text content.
<pair label="My Label"></pair>
<pair label="My Label"><i></i></pair>
I am new to Angular so there may be a great way handle this sort of thing out of the box. Any help is appreciated.
Here's an approach using ng-show on the template and within compile transcludeFn checking if transcluded html has text length.
If no text length ng-show is set to hide
app.directive('pair', function($timeout) {
return {
replace: true,
restrict: 'E',
scope: {
label: '#'
},
transclude: true,
template: "<div ng-show='1'><span>{{label}} </span><span ng-transclude></span></div>",
compile: function(elem, attrs, transcludeFn) {
transcludeFn(elem, function(clone) {
/* clone is element containing html that will be transcludded*/
var show=clone.text().length?'1':'0'
attrs.ngShow=show;
});
}
}
});
Plunker demo
Maybe a bit late but you can also consider using the CSS Pseudo class :empty.
So, this will work (IE9+)
.trancluded-item:empty {
display: none;
}
The element will still be registered in the dom but will be empty and invisible.
The previously provided answers were helpful but didn't solve my situation perfectly, so I came up with a different solution by creating a separate directive.
Create an attribute-based directive (i.e. restrict: 'A') that simply checks to see if there is any text on all the element's child nodes.
function hideEmpty() {
return {
restrict: 'A',
link: function (scope, element, attr) {
let hasText = false;
// Only checks 1 level deep; can be optimized
element.children().forEach((child) => {
hasText = hasText || !!child.text().trim().length;
});
if (!hasText) {
element.attr('style', 'display: none;');
}
}
};
}
angular
.module('directives.hideEmpty', [])
.directive('hideEmpty', hideEmpty);
If you only want to check the main element:
link: function (scope, element, attr) {
if (!element.text().trim().length) {
element.attr('style', 'display: none;');
}
}
To solve my problem, all I needed was to check if there were any child nodes:
link: function (scope, element, attr) {
if (!element.children().length) {
element.attr('style', 'display: none;');
}
}
YMMV
If you don't want to use ng-show every time, you can create a directive to do it automatically:
.directive('hideEmpty', ['$timeout', function($timeout) {
return {
restrict: 'A',
link: {
post: function (scope, elem, attrs) {
$timeout(function() {
if (!elem.html().trim().length) {
elem.hide();
}
});
}
}
};
}]);
Then you can apply it on any element. In your case it would be:
<span hide-empty>{{label}}</span>
I am not terribly familiar with transclude so not sure if it helps or not.
but one way to check for empty contents inside the directive code is to use iElement.text() or iElement.context object and then hide it.
I did it like this, using controllerAs.
/* inside directive */
controllerAs: "my",
controller: function ($scope, $element, $attrs, $transclude) {
//whatever controller does
},
compile: function(elem, attrs, transcludeFn) {
var self = this;
transcludeFn(elem, function(clone) {
/* clone is element containing html that will be transcluded*/
var showTransclude = clone.text().trim().length ? true : false;
/* I set a property on my controller's prototype indicating whether or not to show the div that is ng-transclude in my template */
self.controller.prototype.showTransclude = showTransclude;
});
}
/* inside template */
<div ng-if="my.showTransclude" ng-transclude class="tilegroup-header-trans"></div>