can we create a custom directive set as an element with required parameters , so that if these params are not provided by who ever would like to use it... then the directive must not work ???
**JS:**
app.directive('customDirective', function() {
return {
restrict: 'E',
scope : {
data : "=data", ...
} ,
templateUrl: function(element, attr) {
// HTML file path
},
....
}
});
the case is now even if these params are not passed , the directive still works and injects the html in the view .
This is a generic question about directive not related to this specific case only .
You could add your own validation in the link function
app.directive('customDirective', function() {
return {
restrict: 'E',
scope : {
data : "=data", ...
} ,
templateUrl: function(element, attr) {
// HTML file path
},
link: function(scope, element, attrs){
if(!data){
element.remove();
}
}
}
});
I'm not sure if there's a more official way though
Related
I am stuck with this for quite a long time.
In a directive, I would like to create another directive on the fly, based on a function. Instead of having 4 directive declarations, I would prefer to create a new directive in each 'tab' directive, that is to say each time a tab attribute is set in a DOM element.
Here is a part of the code (config is a factory that is use to configure some stuff) :
.directive('tab', function(config) {
return {
require: '^panelHandler',
restrict: 'A',
scope: true,
link: function(scope, elem, attrs, ctrl) {
ctrl.addPane(scope);
scope.select = function() {
ctrl.select(scope);
};
},
};
})
.directive('page1', directiveConfigurer('page1.html'))
.directive('page2', directiveConfigurer('page2.html'))
.directive('page3', directiveConfigurer('page3.html'))
.directive('page4', directiveConfigurer('page4.html'));
function directiveConfigurer(fileName) {
newDirective.$inject = ['config'];
return newDirective;
function newDirective(config) {
var directive = {
restrict: 'E',
scope: true,
templateUrl: config.filesDirectory + fileName,
};
return directive;
}
}
Thanks for your help.
EDIT :
Config...
angular.module('appLogic', ['socket-factory', 'data-factory', 'panelHandler-module'])
.factory('config', function() {
return {
filesDirectory : '../../templates/pages/',
fieldsNumber : 5,
};
});
and what I need...
link: function(scope, elem, attrs, ctrl) {
ctrl.addPane(scope);
//.directive('page' + number, directiveConfigurer(name))
scope.select = function() {
ctrl.select(scope);
};
},
If the directives are essentially the same, except for the template url, then you can just create a single directive and provide the concrete url path as an attribute:
<page src="page1.html">
To do that, use a function for templateUrl property of the directive definition object:
.directive("page", function(){
return {
templateUrl: function(tElem, tAttr){
return "/base/path/" + tAttr.src;
},
//...
};
});
Is there a way to pass configuration object into custom directive which defined as a attribute-directive?
I've got an object in Controller that I want to send to directive:
$scope.configObject = {
count: 5,
mode: 'fast',
getData: myService.getData // function of external service that avaliable in controller
}
In my View I declare directive:
<div class='list-class' my-list='configObject'></div>
Directive looks like:
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var config = angular.fromJson(attrs.myList);
}
}
i've tried to get config object using angular.getJson - but it doesn't work for functions (it's possible to get only count and mode). Is .getJson() the incorrect way to get config?
Also (I guess it's not even possible) - is there a way to get config object avoiding accessing to
attrs.myList
directly? I mean if I change initializing of directive from
.directive('myList', function() { ... }) to
.directive('myCustomList', function() { ... })
shall I change accessing to
attrs.myCustomList
because view would look like
<div class='list-class' my-custom-list='configObject'></div>
you can pass it using isolate scope if you want
return {
restrict: 'A',
scope: { config : '=myList' }
link: function(scope, elem, attrs) {
//access using scope.config
}
}
or as already answered you can parse it from attrs
$parse(attrs["myList"])(scope);
and yes if you change the directive to myCustomList, you will have to change the code
scope: { config : '=myCustomList' }
or
$parse(attrs["myCustomList"])(scope);
You can use $parse service to fetch the config object.
(function(){
var directiveName = 'myList';
angular.module('YourModule').directive(directiveName,['$parse',function($parse){
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var config = $parse(attrs[directiveName])(scope);
}
};
}]);
})();
You can $eval the attribute
link: function(scole, element, attrs) {
var config = scope.$eval(attrs['myList']);
}
I define $items array in a page controller:
$scope.items = [{id:1,type:apple},{id:2,type:banana},{id:3,type:mango}]
Then I iterate over this array from a page template:
<div ng-repeat="item in items">
<my-item item-type="{{item.type}}"></my-item>
</div>
myItem is a directive defined as follows:
function () {
function resolveTemplate(element, attrs) {
var itemType = '';
if (itemType === 'apple') {
return 'apple.html';
} else {
return 'default.html';
}
}
return {
restrict: 'E',
templateUrl: resolveTemplate,
link: function (scope, element, attrs) {
// nothing here
}
};
}
I've tried to use:
scope: {
'itemType': '#'
}
But it appears that I can call attrs.itemType only from link() and not from resolveTemplate() function (where the unprocessed {{item.type}} is returned).
So what will be a correct way to dynamically choose a template in this situation?
From angularjs docs: https://docs.angularjs.org/guide/directive
Note: You do not currently have the ability to access scope variables
from the templateUrl function, since the template is requested before
the scope is initialized.
You can however have access to some angularjs service in your resolveTemplate function. So if possible, you can pull in the itemType from the service instead. Your directive would look something like below:
app.directive('myDirective', function(templateResolveService){
function resolveTemplate(element, attrs) {
var itemType = templateResolveService.getItemType();
if (itemType === 'apple') {
return 'apple.html';
} else {
return 'default.html';
}
}
return {
restrict: 'E',
templateUrl: resolveTemplate,
link: function (scope, element, attrs) {
// nothing here
}
};
}
Say I have a directive like such:
<my-directive>This is my entry!</my-directive>
How can I bind the content of the element into my directive's scope?
myApp.directive('myDirective', function () {
return {
scope : {
entry : "" //what goes here to bind "This is my entry" to scope.entry?
},
restrict: "E",
template: "<textarea>{{entry}}</textarea>"
link: function (scope, elm, attr) {
}
};
});
I think there's much simpler solution to the ones already given. As far as I understand, you want to bind contents of an element to scope during initialization of directive.
Given this html:
<textarea bind-content ng-model="entry">This is my entry!</textarea>
Define bind-content as follows:
directive('bindContent', function() {
return {
require: 'ngModel',
link: function ($scope, $element, $attrs, ngModelCtrl) {
ngModelCtrl.$setViewValue($element.text());
}
}
})
Here's a demo.
I may have found a solution. It relies on the transclude function of directives. It works, but I need to better understand transclusion before being sure this is the right way.
myApp.directive('myDirective', function() {
return {
scope: {
},
restrict: 'E',
replace: false,
template: '<form>' +
'<textarea ng-model="entry"></textarea>' +
'<button ng-click="submit()">Submit</button>' +
'</form>',
transclude : true,
compile : function(element,attr,transclude){
return function (scope, iElement, iAttrs) {
transclude(scope, function(originalElement){
scope.entry = originalElement.text(); // this is where you have reference to the original element.
});
scope.submit = function(){
console.log('update entry');
}
}
}
};
});
You will want to add a template config to your directive.
myApp.directive('myDirective', function () {
return {
scope : {
entry : "=" //what goes here to bind "This is my entry" to scope.entry?
},
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "E",
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
myApp.controller('fooController', function($scope){
$scope.foo = "BLAH BLAH BLAH!";
});
And then use your directive like this:
<div ng-controller="fooController">
<!-- sets directive "entry" to "foo" from parent scope-->
<my-directive entry="foo"></my-directive>
</div>
And angular will turn that into:
<div>THIS IS MY ENTRY</div>
Assuming that you have angular setup correctly and are including this JS file onto your page.
EDIT
It sounds like you want to do something like the following:
<my-directive="foo"></my-directive>
This isn't possible with ELEMENT directives. It is, however, with attribute directives. Check the following.
myApp.directive('myDirective', function () {
return {
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "A",
scope : {
entry : "=myDirective" //what goes here to bind "This is my entry" to scope.entry?
},
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
Then use it like this:
<div my-directive="foo"></div>
This will alias the value passed to my-directive onto a scope variable called entry. Unfortunately, there is no way to do this with an element-restricted directive. What is preventing it from happening isn't Angular, it is the html5 guidelines that make what you are wanting to do impossible. You will have to use an attribute directive instead of an element directive.
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>