Better granularity in Angular directive restriction - javascript

Is there a way to write a directive that just applies to a specific element + attribute + attribute value?
My very first intention would be to have separate directives, for modularity and maintenance purposes, but I'm afraid that's not possible as I get an error from Angular telling me there are multiple directives matching the element.
So my scenario is as follows: I want to write my own input elements, e.g.
<input type="time-picker">
<input type="date-picker">
so I did
app.directive('input', function () {
return {
restrict: 'E',
templateUrl: function ($element, $attrs) {
if ($attrs.type === 'date-picker' || $attrs.type === 'time-picker') {
return $attrs.type + '.html';
}
},
controller: function ($scope, $element, $attrs) {
if ($attrs.type === 'date-picker') {
console.log('date-picker');
}
else if($attrs.type === 'time-picker') {
console.log('time-picker');
}
}
}
});
This works well as long as there are no other input elements in the page.
If I put
<input type="time-picker">
<input type="date-picker">
it works fine. Now if I add
<input type="text">
the whole page hangs.
See my fiddle here: http://jsfiddle.net/pWc3K/8/

If you change your html to this:
<input type="text" time-picker> <input type="text" date-picker>
Then you could wire up your directives based on those attributes like so:
app.directive('timePicker', function(){
return {
restrict: 'A',
...
}
});
app.directive('datePicker', function(){
return {
restrict: 'A',
...
}
});
Putting time-picker/date-picker in an input type for an element isn't really valid. If you read up on the angular docs for directives you'll find a whole list of the different things you can associate on. The four ones are:
E - Element name: <my-directive></my-directive>
A - Attribute: <div my-directive="exp"> </div>
C - Class: <div class="my-directive: exp;"></div>
M - Comment: <!-- directive: my-directive exp -->

Try and understand In AngularJs custom directives created can have following restrictions:
The restrict option is typically set to:
'A' - only matches attribute name
'E' - only matches element name
'C' - only matches class name
‘M’ – only comments
These restrictions can all be combined as needed:
for eg:
'AEC' - matches either attribute or element or class name

Related

Angular nested directive only the first child is rendered [duplicate]

This question already has answers here:
AngularJS - html under my directive is not showing
(2 answers)
Closed 6 years ago.
I have a parent directive which includes two children directives, first and second. I noticed that only the first child is rendered. Also, if I put some arbitrary HTML markup before the first one, it's all rendered but if I put them after that then they will not show up. Why is this?
See the jsfiddle:
<!-- index.html -->
<div ng-app="myApp">
<my-parent-dir />
</div>
<!-- main.js -->
var app = angular.module("myApp", []);
app.directive("myParentDir", function() {
return {
restrict: 'E',
template: '<my-first-child /> <my-second-child />'
};
});
app.directive("myFirstChild", function() {
return {
restrict: 'E',
template: '<input type="text" placeholder="first">',
};
});
app.directive("mySecondChild", function() {
return {
restrict: 'E',
template: '<input type="text" placeholder="second">',
};
});
Try to use it like this:
var app = angular.module("myApp", []);
app.directive("myParentDir", function() {
return {
restrict: 'E',
template: '<my-first-child></my-first-child> <my-second-child></my-second-child>'
};
});
From the angular issues in github:
self-closing or void elements as the html spec defines them are very
special to the browser parser. you can't make your own, so for your
custom elements you have to stick to non-void elements ().
this can't be changed in angular.
Self defined tags are no leaf tags so you will have to use:
template:'<my-first-child></my-first-child> <my-second-child></my-second-child>'
Since you're using custom tags, you need to close the tag, because HTML spec does not allow self closing tags.
template: '<my-first-child></my-first-child> <my-second-child></my-second-child>'
JSFiddle

How to loop over directives, compiling, and attaching to DOM?

I have a number of directives that I would like to compile and attach to the DOM. For example:
mod.controller("ctrl, ["$scope", "$compile", function($scope, $compile) {
$scope.tools = [
{
title: "foo",
directive: $compile("<foo-bar></foo-bar>")($scope)
},
{
title: "qux",
directive: $compile("<qux-bar></qux-bar>")($scope)
}
...
];
Then in HTML:
<div ng-repeat="tool in tools">
<div class="tool">
<h3>{{tool.title}}</h3>
{{tool.directive}}
</div>
</div>
I would like each directive to be compiled and injected into the DOM. But nothing happens. I expect because I am calling $compile too late. Is there a better way to do this?
FWIW, if I compile the directive and "manually" append it to the DOM, it works:
$('body').append($compile('<foo-bar></foo-bar>')($scope));
You cannot do it this way; the {{...}} bindings do not accept elements. They can be made to accept HTML, but this HTML is static - uncompiled.
If you want dynamic directives, you have to do it yourself. One option is with an auxiliary directive, e.g. the container-directive below:
<div class="tool" container-directive>
<h3>{{tool.title}}</h3>
<placeholder style="display: none"></placeholder>
</div>
It takes the tool from its context, $compiles it, and replaces the dummy placeholder element. Suppose the tools are defined as:
this.tools = [
{ title: 'foo', directive: 'foo-bar' },
{ title: 'qux', directive: 'qux-bar' }
];
Then a very simple implementation would be:
app.directive('containerDirective', function($compile) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.find('placeholder')
.replaceWith($compile('<' + scope.tool.directive + '></' + scope.tool.directive + '>')(scope));
}
};
});
See a fiddle: http://jsfiddle.net/kxj60cbo/
This code demonstrates the general idea. It definitely will need some adjustment to fit your needs. E.g. the directive is tightly coupled with the name of the iteration variable - tool - maybe using isolated scope would be better.
I wouldn't inject I would just qualify what to show
<div ng-repeat="tool in tools">
<div class="tool">
<h3>{{tool.title}}</h3>
<foo-bar ng-if="tool.title = 'foo'"></foo-bar>
<qux-bar ng-if="tool.title = 'qux'"></qux-bar>
</div>
</div>

AngularJS directive restrict

I made some AngularJS directives in the past week and they all work but this one just won't work and I don't know what I am doing wrong..
This is the directive I'm talking about:
app.directive('idleCheck', [function () {
return {
restrict: 'I',
link: function (scope, elem, attrs) {
ifvisible.setIdleDuration(5);
ifvisible.on("idle", function () {
var div = document.getElementById('fullscreenWrap');
div.style.cursor = 'none';
stream.pause();
});
ifvisible.on("wakeup", function () {
var div = document.getElementById('fullscreenWrap');
div.style.cursor = 'auto';
stream.resume();
});
}
}
}]);
This is my HTML code where I call the directive:
<div id="fullscreenWrap" idle-check>
...
</div>
Do you see anything wrong in the code ?
Or do you know why it isn't working ?
You need to change restrict field to 'A'.
The restrict option is typically set to:
'A' - only matches attribute name 'E' - only matches element name 'C'
- only matches class name These restrictions can all be combined as needed:
'AEC' - matches either attribute or element or class name
Angular directive
The restrict options available are: 'E','A','C','M'
One of EACM restricts the directive to a specific directive declaration style.
You can even use Multiple restrictions on same directive restrict: 'AC'
If you don't restrict any, the defaults (elements and attributes) are used.
E - Element name (default): <my-directive></my-directive>
A - Attribute (default): <div my-directive="exp"></div>
C - Class: <div class="my-directive: exp;"></div>
M - coMment: <!-- directive: my-directive exp -->
For Example:
ng-if is restricted to 'A'. so it can be used only as attribute, you can't use as comment or element
Here's the angularjs code for ngIf
var ngIfDirective = ['$animate', function($animate) {
return {
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A', // --> This means restricting to Attribute
Without knowing what error it is actually giving your issue is most likely your directive declaration.
There is no restrict: I. Angular only supports three values for this:
A - only matches attribute name
E - only matches element name
C - only matches class name
You can but any combination of the three though to support multiple cases.
Docs: https://docs.angularjs.org/guide/directive#template-expanding-directive
It states the info at the bottom of the template expanding directive section.

Dynamically Create and Load Angular Directive

In my application i have a list of custom directive names.
$scope.data =["app-hello","app-goodby","app-goodafter"];
each name in this array is one directive that im created.
var app = angular.module('app',[]).controller('mainCtrl',function($scope){
$scope.data =["app-hello","app-goodby","app-goodafter"];
}).directive('appHello',function(){
return {
restrict:'EA',
template:'<h1>Hello Directive</h1>'
};
}).directive('appGoodbye',function(){
return {
restrict:'EA',
template:'<h1>GoodBye</h1>'
};
}).directive('appGoodafter',function(){
return{
restrict:'EA',
template:'<h1>Good Afternoon</h1>'
};
});
now i want to load directive with ng-repeat in the view for example because i used EA restrict for directive can create directive in ng-repeat like this :
<div ng-repeat="d in data" >
<div {{d}}></div>
</div>
but this way it doesn't work. so the real question is if i have list of directive how to load this directive with ng-repeat.for this scenario i create a jsbin .
thanks.
You need a "master" directive that $compiles the HTML (optionally containing directives) into an Angular-aware template and then links the compiled element to a $scope:
app.directive('master', function ($compile) {
return {
restrict: 'A',
link: function postLink(scope, elem, attrs) {
attrs.$observe('directive', function (dirName) {
if (dirName) {
var compiledAndLinkedElem =
$compile('<div ' + dirName + '></div>')(scope);
elem.html('').append(compiledAndLinkedElem);
}
});
}
};
});
<div master directive="{{dir}}" ng-repeat="dir in ['dir1', 'dir2', 'dir3']"></div>
See, also, this short demo.
You can do it in this way:
Directive:
app.directive('compile',function($compile){
return{
restrict:'A',
template: '<div></div>',
link:function(scope,elem,attrs){
scope.name = attrs.compile;
elem.children('div').attr(scope.name,'');
$compile(elem.contents())(scope);
}
};
});
HTML:
<div ng-repeat="d in data" compile="{{d}}">
</div>
Jsbin: http://jsbin.com/wofituye/4/edit
I actually prefer to create templates, that just contain the directive. Then you can use ng-include this then enables you to easily pass scope variables into the dynamically chosen directives too.
Here is my widget code fore example:
<div ng-repeat="widget in widgets track by $index" ng-include="widget.url" class="widget-container" ng-class="widget.widget_type.config.height +' ' + widget.widget_type.config.width">
</div>
Then I set the widget.url to a template containing just the right directive.
I can then in my directive do this:
<custom-widget ng-attr-widget="widget"></custom-widget>
Then I have access to the dynamic variable too, so I can access configuration specifics too, without having to dynamically generate HTML strings and compile them. Not a perfect solution, but personally I used to use the other approach mentioned, and discovered that this fit my needs much better.

Data from directive not displaying within ng-repeat

I have broken this problem down into it's simplest form. Basically I have a directive that, for the demo, doesn't yet really do anything. I have a div with the directive as an attribute. The values within the div, which come from an object array, are not displayed. If I remove the directive from the div, they are displayed OK. I am clearly missing something really obvious here as I have done this before without any problems.
Here's the Plunk: http://plnkr.co/edit/ZUXD4qW5hXvB7y9RG6sB?p=preview
Script:
app.controller('MainCtrl', function($scope) {
$scope.tooltips = [{"id":1,"warn":true},{"id":2,"warn":false},{"id":3,"warn":true},{"id":4,"warn":true}];
});
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
}
};
});
HTML
<div ng-repeat="tip in tooltips" class="titlecell" cm-tooltip="true">
A div element: {{ tip.id }}
</div>
<br><br>
Just to prove it works without the directive:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.id }}
</div>
There is a hack to make it working in earlier versions of angular by making use of transclusion, like that:
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
transclude: true,
template : '<div ng-transclude></div>'
};
});
PLNKR
As by Beyers' comment above and below, the behaviour the question is about no longer exists in at least 1.2.5
To be clearer; this has nothing to do with ng-repeat, you can remove it and there still will be no tip ( or tooltips ).
See this question on what the = and other configs mean and what it is doing for you.
Basically for your situation when you use = the scope of the directive will be used in the underlying elements, you no longer have your controller's scope. What this means for you is that there is no {{ tip.id }} or not even tip. Because the directive doesn't supply one.
Here's a plunker that demonstrates what you can do with it.
Basically all i did was
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
link: function($scope){ // <<
$scope.tip = { id: 1 }; // <<
} // <<
};
});
This creates the tip object on the scope so it has an id.
For your situation you would probably just not use = and look at this question for your other options depending on what you want.
In my opinion this isn't the way to go.
I would use Objects.
JS code:
function tooltip(id,warn){
this.id = id;
this.warn = warn;
}
tooltip.prototype.toString = function toolToString(){
return "I'm a tooltip, my id = "+this.id+" and my warn value = "+this.warn;
}
$scope.tooltips = [new tooltip(1,true),new tooltip(2,false),new tooltip(3,true),new tooltip(4,true)];
HTML:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.toString() }}
</div>

Categories

Resources