I want to create a directive that organizes a displays data grouped by date. I also want to be able to specify a directive that will display the individual rows. In a perfect world it would look something like this (but nice and pretty)
Friday, Oct 28
[some directive html]
[some directive html]
[some directive html]
Saturday, Oct 29
[some directive html]
Sunday, Oct 30
[some directive html]
[some directive html]
...
This obviously doesn't work, so if you have a better approach please tell me, but I was hoping to be able to do something along these lines:
app.directive('dateOrganized', [function(){
return {
template:
'<div>' +
'<div ng-repeat="organizedDate in organizedDate">' +
'<div>{{organizedDate.date | date}}</div>' +
'<div ng-repeat="item in organizedDate.items">' +
'{{rowDirectiveHtml}}' +
'</div>' +
'</div>' +
'</div>',
scope: {
organizedDates: '=',
rowDirectiveHtml: '='
}
...
};
}])
app.directive('itemRow', [function(){
return {
template: '<div>{{item.data}}</div>',
scope: {
item: '='
}
};
}]);
then use it like this:
<div data-organized organized-dates="stuff" row-directive-html="<div item-row item=\"item\" />" />
I know this is super ugly (and doesn't work, but I'm sure I could get it working with a few tweaks) so what I am really asking, is there a better way to do this?
This question is more complicated than might appear, so let's break it down.
What you are building is a directive that accepts a partial template - <div item-row item="item" /> - and that template uses (or linked against a scope with) an inner variable - item - that is not defined in the outer scope by the user; its meaning is defined by your directive and your user "discovers" it by reading the documentation of your directive. I typically name such "magic" variables with a prefixed $, e.g. $item.
Step 1
Instead of passing a template as an HTML-as-string via attribute binding, pass it as contents and transclude that content. Transcluding allows you to bind the transcluded content against an arbitrary scope:
<foo>
<div>my item is: {{$item}}</div>
</foo>
.directive("foo", function(){
return {
scope: {},
transclude: true,
template: "<h1>I am foo</h1><placeholder></placeholder>",
link: function(scope, element, attrs, ctrls, transclude){
scope.$item = "magic variable";
transclude(scope, function(clonedContent){
element.find("placeholder").replaceWith(clonedContent);
});
}
};
});
The above will place the template <div>my item is: {{$item}}</div> (could be any template you specify) where the directive foo decides, and will link against a scope that has $item defined.
Step 2
But the added complexity of your directive is that it uses ng-repeat, which by itself accepts a template, and the template your directive receives needs to be used as a template of ng-repeat.
With just the approach above, this would not work, since by the time link runs, ng-repeat will have already transcluded its own content before you had a chance to apply yours.
One way to address that is to manually $compile the template of foo instead of using the template property. Prior to compiling, we will have a chance to place the intended template where needed:
.directive("foo", function($compile){
return {
scope: {},
transclude: true,
link: function(scope, element, attrs, ctrls, transclude){
scope.items = [1, 2, 3, 4];
var template = '<h1>I am foo</h1>\
<div ng-repeat="$item in items">\
<placeholder></placeholder>\
</div>';
var templateEl = angular.element(template);
transclude(scope, function(clonedContent){
templateEl.find("placeholder").replaceWith(clonedContent);
$compile(templateEl)(scope, function(clonedTemplate){
element.append(clonedTemplate);
});
});
}
};
});
Demo
Related
Hello I'm new to AngularJS and I think I misunderstood something. Im trying to inject dynamically a directive inside another directive template.
For instance I have 2 directives "a-tag" and "b-tag" and I would like to add one of these 2 directives inside another directive "container".
I have something like this:
<body ng-app="myApp">
<container item="a-tag" a-color="#f00"></container>
</body>
I declared my "container" directive to get the item attribute (a-tag, b-tag, or any other directive) and inject it.
angular.module("Container", []).directive("container", function($compile){
return {
restrict: "EA",
scope: {
item: "#"
},
link: function(scope, element, attrs){
var template = '<div id="container">';
var item = '';
if(scope.item !== undefined){
item = '<' + scope.item;
item += ' ></' + scope.item + '>';
}
template += item + '</div>';
element.html(template);
$compile(element.contents())(scope);
}
};
});
It is working but i dunt know how to broadcast to my child directive (a/b,etc.) his attributes (for instance a-color="#f00" like used in the first piece of code).
My child directives look like this:
angular.module("A", []).directive("aTag", function(){
return {
restrict: "EA",
templateUrl: "template/a.html",
replace: true,
link: function(scope, element, attrs){
element.css("color", attrs.bColor);
}
};
});
It is a simple example. Actually I designed a modal popup (my container) and I would like to use it for several things such as displaying a form, a loader (my directives a-tag, b-tag, etc).
Any idea is welcome.
Ty,
RĂ©mi
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.
I have a directive whose data is being received via an api call. The directive itself works fine, the problem arises (I believe) because the directive is loaded before the api call finishes. This results in the whole shebang just not working. Instead of my expected output, I just get {{user}}.
My directive looks like this:
app.directive('myDirective', function() {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '=',
},
template: '<tbody style="background-color: red;" ng-bind-html="renderHtml(listing_html)"></tbody>',
controller: ['$scope', '$http', '$sce',
function($scope, $http, $sce) {
$scope.listing_html += "<td>{{user.name}}</td>"
$scope.renderHtml = function(html_code) {
return $sce.trustAsHtml(html_code);
};
}
],
link: function(scope, iElement, iAttrs, ctrl) {
scope.$watch('ngModel', function(newVal) {
// This *is* firing after the data arrives, but even then the
// {{user}} object is populated. And the `user in ngModel` doesn't
// run correctly either.
console.log(scope.ngModel);
scope.listing_html = "<tr ng-repeat='user in ngModel'><td>{{user}}</td></tr>"
})
}
};
});
And my html is simply
<table my-directive my-options='{"Name": "name", "Email": "email"}' ng-model='userData'></table>
I've created a plunker with a ton of comments to hopefully help explain the issue.
This question is very similar to this one, with the key distinction of that solution not working. Adding ng-cloak to mine just makes it not display.
It may also be worth noting that I've been using this as reference on the way to construct a directive.
I think you're making this a bit more complicated that it needs to be. If you're going to try to insert dynamic HTML with Angular expressions in them, you need to use the $compile service to compile them first (this hooks up the directives, etc, in that dynamic HTML to Angular). With that said, I don't think you need to do that for what you're trying to accomplish.
Take a look at this updated plunk: http://plnkr.co/edit/RWcwIhlv3dMbjln4dOyb?p=preview
You can use the template in the directive to produce the dynamic changes you need. In my example, I've used ng-repeat to repeat over the users provided to the directive, and also to the options provided to the directive. ng-repeat does the watching, so as soon as the data provided to the directive via ng-model is updated, the ng-repeats reflect those changes.
<tbody style="background-color: red;">
<tr><th ng-repeat="option in myOptions">{{option.name}}</th></tr>
<tr ng-repeat="user in ngModel">
<td ng-repeat="option in myOptions">{{user[option.value]}}</td>
</tr>
</tbody>
The options I defined in the main controller like this.
$scope.tableOptions = [
{"name": "Name", "value": "name"},
{"name": "Email", "value": "email"}
];
You could add other properties to this that are used by the directive, such as display order, etc. You could even remove an item from the options dynamically and that data would then be removed from the output table.
Let me know if this helps, or if I've misunderstood what you were trying to accomplish.
I am not 100% sure, but I believe that ngBindHtml will not help you in this case.
ngBindHtml is for displaying some "normal" HTML, but you want to display some Angular, magic HTML.
For that you need to $compile the HTML to something that is Angular-aware and link the compiled HTML to a scope.
I used the following approach (with apparently good results):
controller: function ($scope, $element, $compile) {
var html = createTmpl(angular.fromJson($scope.myOptions));
$scope.$watch('ngModel', function (newVal) {
var elem = angular.element(html); // Creating element
var linkingFn = $compile(elem); // Compiling element
linkingFn($scope); // Linking element
$element.html(''); // Removing previous content
$element.append(elem); // Inserting new content
// The above is purposedly explicit to highlight what is
// going on. It's moe concise equivalent would be:
//$element.html('').append($compile(html)($scope));
});
where createTmpl() is defined to take into account myOptions and return the appropriate template for creating a table with a header-row (based on the keys of myOptions) and data-rows with the properties defined as myOptions's values:
function createTmpl(options) {
// Construct the header-row
var html = '<tr>';
angular.forEach(options, function (value, key) {
html += '<th>' + key + '</th>';
});
html += '</tr>\n';
// Construct the data-rows
html += '<tr ng-repeat="user in ngModel">';
angular.forEach(options, function (value, key) {
html += '<td>{{user' + value + '}}</td>';
});
html += '</tr>\n';
// Return the template
return html;
}
See, also, this short demo.
Of course, this is for demonstration purposes only and does not handle everything a production-ready app should (e.g. accounting for errors, missing properties, changes in myOptions and whatnot).
UPDATE:
I had very strong competion, so I did a slight modification of the code above in order to support nested properties. E.g. given an object with the following structure:
user = {
name: 'ExpertSystem',
company: {
name: 'ExpertSystem S.A.',
ranking: 100
}
};
we can have the company name displayed in a column of our table, just by defining myOptions like this:
myOptions='{"Company name": "company.name"}
So I have a a custom directive, named e.g custom as below:
app.directive('custom', function()
{
return {
restrict: 'A',
scope: { itemSelector: '=custom', gutter: '=' },
link: function(scope, element, attrs){
console.log("IS: " + scope.itemSelector);
console.log("GUTTER: " + scope.gutter);
}
}
}
invoked via HTML like the below
<div custom="item" gutter="10"><!--content--></div>
Can anyone suggest why scope.gutter === '10' yet scope.itemSelector === undefined?
Is it possible to obtain the value of the directives defining attribute this way?
Thanks
I guess item is not defined in the parent scope of your directive. You have two solutions explained in the following post (AngularJS Directive Passing String)
Either you wanted item to be passed as a string and add single quotes around it (by default it's evaluated as an angular expression)
<div custom="'item'" gutter="10"><!--content--></div>
Or change your directive configuration so that it considers the custom attribute as a string :
scope:
{ itemSelector: '#custom', gutter: '=' }
Hope this helps
What I am trying to do is make a function so I can change the height of my ng-grid column width. That is irrelevant besides the fact that the scope from my controller needs to communicate with the scope in my directive.
.directive('getWidth', function(){
return{
controller: 'QuotesCtrl',
link: function(scope){
scope.smRowHeight = function(the value i want){
scope.theRowHeight = the value i want;
}
}
}
})
And I just want to be able to go into my html and say hey for this div I want the height 20
<div getWidth = '20'></div>
I have looking around and I couldn't find anything doing with this exact thing. and by the way, in my QuotesCtrl i initialized the row height like so
$scope.theRowHeight;
Any suggestions?
Try something like this:
.directive('getWidth', function(){
return{
controller: 'QuotesCtrl',
link: function(scope){
console.log(scope.theRowHeight);
},
scope: {
'theRowHeight': '='
}
}
});
Markup:
<div the-row-height="20"></div>
Directives are amazing! You can pass in what is called an isolate scope, and with that you can pass in values as strings or references to your controller scope. There are 3 options on the isolate scope that you should look into. = # & See the link below the example to the docs.
Here is a working JSFiddle
.directive('getHeight', function(){
return{
scope: {
"rowHeight": '='
},
controller: 'QuotesCtrl',
link: function(scope){
scope.smRowHeight = function(the value i want){
scope.theRowHeight = the value i want;
}
}
}
})
You would need to update your html to pass in the new scope value.
<div get-height row-height='20'></div>
More Info on Directives