directive not working inside <tr> that is ng-repeat bound - angularjs

I have a table where the rows are repeated via ng-repeat.
I am trying to create a template that generates columns <td> for each row <tr>
app.directive("customtd", function(){
return {
restrict: 'E',
template: "<td>{{position.Name}}</td><td>{{position.Code}}</td>",
replace: true,
scope: {
position: '='
}
}
});
<table>
<tr ng-repeat="p in positions">
<customtd position="p"></customtd>
</tr>
</table>
The issue is my custom td template is not rendered at all.
Here I intend to replace <customtd> with n number of <td>s - which will be decided based on number of properties on my data object, but at the moment I am just trying to get a simple directive working that will output two columns.
MYPLUNKER : shows an instance of this issue and the directive code.

As pointed out in comments the template of a directive should have single root element. So I would suggest you to move the tr element to the template of the directive, like this: http://plnkr.co/edit/YjLEDSGVipuKTqC2i4Ng?p=preview

As Pavlo wrote, you can move the tr element to the template for the directive. Another option is to use a td element and directive that replaces your td with the template that you want to use.
<table>
<tr ng-repeat="p in positions">
<td replace-me template="mytemplate.html" position="p"></td>
</tr>
</table>
Directive replaceMe
.directive("replaceMe", ["$compile", '$http', '$templateCache', function ($compile, $http, $templateCache) {
return {
restrict: 'A',
scope: {
position: "="
},
link: function (scope, element, attrs) {
function getTemplate(template) {
$http.get(template, {cache: $templateCache}).success(function (templateContent) {
element.replaceWith($compile(templateContent)(scope));
});
}
scope.$watch(attrs.template, function () {
if (attrs.template) {
getTemplate(attrs.template);
}
});
}
}
}]);
mytemplate.html
<td>{{position.Name}}</td>
<td>{{position.Code}}</td>
<td another-my-directive></td>
plunker

Related

Custom table header directives not behaving as expected

I am putting together a directive that will wrap a transcluded table and will be used with another directive to prepend checkboxes to the first cell in a table row to allow for multi selections of rows, plus a header checkbox cell for toggle select\deselect-all functionality.
I have started with a table header directive, here is what I have come up with so far:
DIRECTIVES
angular
.module('app.components')
.directive('myTable', myTable);
function myTable() {
var myTable = {
restrict: 'E',
transclude: true,
scope: {
someProperty: '='
},
template: '<div class="myTable">' +
'<md-toolbar class="md-menu-toolbar">' +
'<div class="md-toolbar-tools">' +
'OTHER CONTENT HERE...'
'</div>' +
'</md-toolbar>' +
'<ng-transclude></ng-transclude>' +
'</div>',
controller: controller,
controllerAs: 'vm',
bindToController: true
};
return myTable;
function controller($attrs) {
var self = this;
self.enableMultiSelect = $attrs.multiple === '';
}
}
angular
.module('app.components')
.directive('myTableHead', myTableHead);
myTableHead.$inject = ['$compile'];
function myTableHead($compile) {
var myTableHead = {
restrict: 'A',
require: ['myTableHead', '^^myTable'],
controller: controller,
controllerAs: 'vm',
bindToController: true,
link: link
};
return myTableHead;
function link(scope, element, attrs, ctrls) {
var self = ctrls.shift(),
tableCtrl = ctrls.shift();
if(tableCtrl.enableMultiSelect){
element.children().prepend(createCheckbox());
}
self.toggleAll = function () {
console.log('toggleAll');
};
function createCheckbox() {
var checkbox = angular.element('<md-checkbox>').attr({
'aria-label': 'Select All',
'ng-click': 'vm.toggleAll()'
});
return angular.element('<th class="md-column md-checkbox-column">').append($compile(checkbox)(scope));
}
}
function controller() {
}
}
HTML
<my-table multiple>
<table>
<thead my-table-head>
<tr>
<th></th>
<th></th>
...
</tr>
</thead>
<tbody>
<tr ng-repeat="thing in things">
<td>{{thing.propA}}</td>
<td>{{thing.propB}}</td>
</tr>
</tbody>
</table>
</my-table>
I am encountering an issue where as soon as I have added the my-table-head directive to my thead element of the table being transcluded in, no rows from the ng-repeat get rendered.
The checkbox header cell does actually render just fine, but I also am finding when clicking it, the toggleAll function its bound to (defined in my link function) is not firing as I dont see any console log messages appearing.
I can't figure out why, can anyone point out what I am doing wrong with my approach above?

Having multiple suffix for same directive (angularjs)

Let say we have the following directive:
app.directive('testList', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
//do stuff
}
}
});
I would like to call this directive from multiple element in html
<button test-list-add></button>
<table test-list="listObject">
<tr ng-repeat="list in testList">
<td test-list-click>list.item</td>
</tr>
</table>
is it possible to get into the link function for each attribute starting with test-list (test-list-add, test-list, test-list-click)?
Thank you
You want to pass attributes to your directive and doing some action, depending on the attribute:
app.directive('testList', function () {
return {
restrict: 'A',
scope: {
'add': '#', // Text
'click': '&' // function
},
link: function (scope, element, attrs) {
// scope.add - your Text you passed
// scope.click(); - calls your function you passed
}
}
});
<button test-list add="test"></button>
<table test-list="listObject">
<tr ng-repeat="list in testList">
<td test-list click="myFunc()">list.item</td>
</tr>
</table>
You can't repeat a directive (what you obvis trying to do).
If you want to make multiple suffix for a directive, you have to declare them one by one:
app.directive('testListAdd', function () {...});
app.directive('testListclick', function () {...});
<test-list-add></test-list-add>
<test-listclick></test-listclick>

Angular directive for table as parent and theader and tbody as child

I´m trying to do a parent directive with 2 child directives.
The main reason to do this is to force that all the tables in the application use the same "template".
My problem here is that i need to copy the Dom inside the directives.
So my main directive is the table directive, and the frist child is the thead and the second child is the tbody.
This is the markup
<div my-table >
<my-thead>
<tr>
<th ng-click="alert('Id')"> Id </th>
<th ng-click="alert('Name')"> Name </th>
</tr>
</my-thead>
<my-tbody>
<tr ng-repeat ="item in List">
<td>{{item.Id}}</td>
<td>{{item.Name}}</td>
</tr>
</my-tbody>
</div>
The directive my-table wil generate all the code necessary for the struct of a table while the dir my-thead and my-tbody will create the tags thead and tbody and give them to do parent div.
My problem remains on copying the inner markup of my-thead and my-tbody since they are tr, any suggestion?
Next i present to you the most recent code i get so far with a plunker
myTable
angular
.module('App')
.directive('myTable', ['$compile',
function($compile){
var tableSettings = {};
return {
restriction :'AE',
scope : {},
replace : false,
link : function(scope,element){
var divContainerFluid = angular.element('<div>');
var divRowTable = angular.element('<div>');
var divColTable = angular.element('<div>');
divContainerFluid.addClass('container-fluid');
divRowTable.addClass('Row');
divColTable.addClass('col-lg-12');
var divTable = angular.element('<div>');
var table = angular.element('<table>');
table.addClass('table');
table.addClass('table-striped');
//add from thead
table.append(tableSettings.tHead);
//add from tbody
table.append(tableSettings.tBody);
divTable.append(table);
divColTable.append(divTable);
divRowTable.append(divColTable);
divContainerFluid.append(divRowTable);
$compile(divContainerFluid)(scope);
element.append(divContainerFluid);
},
controller: function () {
this.receiveTHead = function (tHead) {
tableSettings.tHead = tHead;
}
this.receiveTBody = function (tBody) {
tableSettings.tBody = tBody;
}
}
}
}]);
myHead
angular
.module('App')
.directive('myThead', [
function() {
return {
restriction: "E",
scope: true,
require: "^myTable",
replace: true,
transclude: true,
template:'<thead class="form-class" ng-cloak>' +
'</thead>',
link: function(scope, element, attrs, myTableCtrl, transclude)
{
transclude(function(clone){
element.append(clone);
});
myTableCtrl
.receiveTHead(element);
}
};
}
]);
myBody
angular
.module('App')
.directive('myThead', [
function() {
return {
restriction: "E",
scope: true,
require: "^myTable",
replace: true,
transclude: true,
template:'<thead class="form-class" ng-cloak>' +
'</thead>',
link: function(scope, element, attrs, myTableCtrl, transclude)
{
transclude(function(clone){
element.append(clone);
});
myTableCtrl
.receiveTHead(element);
}
};
}
]);
Plunker
Thanks, for the help.
UPDATE
missing listCtrl
angular.module('App',[])
.controller('listCtrl',['$scope','$timeout',
function($scope,$timeout){
$timeout(function () {
$scope.List = [ {Id : '1', Name : 'Name1'}, {Id : '2', Name :'Name2'}];
}, 1500);
}
]);
UPDATE
I recreated the problem from a different perspective. But the body doesn't get populated with the info in the scope.List, any ideas?
<div my-table>
<table>
<thead>
<tr>
<th ng-click="resort('Id')"> Id </th>
<th ng-click="resort('Name')"> Name </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in List">
<td>{{user.Id}}</td>
<td>{{user.Name}}</td>
</tr>
</tbody>
</table>
</div>
myTable
angular
.module('App')
.directive('myTable', [
'$compile',
function($compile) {
return {
restriction: 'AE',
//scope: {
// plEmptytext: '#'
//},
transclude: true,
template:
'<div class="container-fluid">' +
'<div class="row">' +
'<div class="col-lg-12">' +
'<div ng-transclude></div>' +
'</div>' +
'</div>' +
'</div>',
link: function(scope, element) {
var table = element.find('table');
table.addClass('table');
table.addClass('table-striped');
var tHead = element.find('thead');
var tBody = element.find('tbody');
tBody.attr('ng-controller', 'listCtrl');
$compile(tHead)(scope);
$compile(tBody)(scope);
$compile(table)(scope);
}
};
}
]);
the listCtrl is the same
Plunker 2
SOLUTION:
For the ones who might come here my solution has to add to the directive tBody return{ controller: 'listCtrl' ...
A different approach that might still provide your desired "one template for all" goal would be writing an html template file, and referencing it whenever a table is used by declaring an ng-include. Similar to the old server-side includes, it gives a way to inject a block of html anywhere, anytime, without all the overhead of a directive.
We've actually stepped back from making so many directives and started using includes more when there isn't any work to be done in the directive.
Angular documentation on ngInclude
Sounds like you need a "Multi-slot Transclusion".
This is available in angular versions > 1.5
https://docs.angularjs.org/api/ng/directive/ngTransclude
For the ones who might come here my solution has to add to the directive tBody
return{ controller: 'listCtrl' ...
For me it was the solution I was looking for.

can ng-hide invoke a function in Angular

My table looks like this, with more than one tbody:
<tbody>
<tr class='group-header' ng-click="collapseDetail($event)"> ... </tr>
<tr class='detail' ng-hide="groupIsCollapsed()">...</tr>
<tr class='group-footer'> ... </tr>
</tbody>
<tbody>
<tr class='group-header' ng-click="collapseDetail($event)"> ... </tr>
<tr class='detail' ng-hide="groupIsCollapsed($event)">...</tr>
<tr class='group-footer'> ... </tr>
</tbody>
In my collapseDetail() function I toggle a class collapsed on the tbody.
And so I would like to have the detail row hidden only if the parent tbody hasClass('collapsed`).
Is that legal? What I have isn't working:
$scope.collapseDetail = function (e) {
var targ = angular.element( e.currentTarget );
$scope.$apply( function(targ){
targ.parent().toggleClass('collapsed');
});
}
$scope.groupIsCollapsed = function (e) {
if (e == undefined) return false;
var targ = angular.element( e.currentTarget );
return targ.parent().hasClass('collapsed');
}
To give a neater solution had to work on creating a directive for you, as over here you require an isolated scope on individual tbody, so that they can show/hide the detail.
Using Angular Directive has many advantages like
having isolated scope (as mentioned above)
Reducing html markup
NO DOM manipulation in controller ( a strict no taking angular perspective into account, all DOM manipulations to be done only in
directive making it more maintainable)
HTML Code:
<table>
<tbody rendered key="assasa" val="tgtrtghrt"></tbody>
<tbody rendered key="fsfgsd" val="teeger"></tbody>
</table>
Controller Code for this question:
angular.module('t', [])
//You can see that nothing is in the controller now
.controller('test', function ($scope) {});
Directive Code:
.directive('rendered', function ($compile) {
return {
restrict: 'EA',
replace: false,
scope: {
key: '#',
val: '#'
},
link: function (scope, element, attrs) {
var ele = "<tr ng-init='collapseTbody = false;' class='group-header' ng-click='collapseTbody=!collapseTbody'><td>{{key}}</td></tr><tr class='detail' ng-hide='collapseTbody'><td>{{val}}</td></tr>";
scope.$watch('key', function () {
element.html(ele);
$compile(element.contents())(scope);
});
},
}
});
Working Fiddle
More on Angular Directives

Creating a custom repeater with ng-repeat

I want to create a custom repeater directive and pass the expression to the ng-repeat inside of the directive template.
The reason for this is to provide a cleaner interface in the html, as I am also including other directives "under-the-hood".
http://jsfiddle.net/DeanIconWeb/Cg9RC/1/
Here is my html template:
<tr custom-repeater="person in people">
<td>{{person.name}}</td>
<td>{{person.gender}}</td>
<td>{{person.age}}</td>
</tr>
Here is my directive:
app.directive("customRepeater", function(){
return {
priority : 2000,
restrict: 'A',
replace : true,
transclude : true,
scope : {
ngRepeatExp : "#customRepeater"
},
template : "<tr ng-repeat='{{ngRepeatExp}}' ng-class-even=\"'even'\" ng-transclude></tr>"
}
});
In trying to make this work I kept getting the "Template must have one root element" error.
I did eventually do the following, but it's not what I really want.
<tr ng-repeat="person in people" custom-repeater>
<td>{{person.name}}</td>
<td>{{person.gender}}</td>
<td>{{person.age}}</td>
</tr>
Directive
app.directive("customRepeater", function($compile){
return {
priority : 2000, //must be compiled before the ng-repeat (priority 1000)
restrict: 'A',
compile : function(tElem, tAttrs){
tElem.attr("ng-class-even", "'even'" );
}
}
});
You could use the $compile service to do something like this (add your other attributes/directives as you see fit before you consume the service):
http://jsfiddle.net/mQS4f/
app.directive("customRepeater", function ($compile) {
return {
//priority: 2000,
//restrict: 'A', // This is implicit
//replace: true,
//transclude: true,
//template: "<tr ng-repeat='{{ngRepeatExp}}' ng-class-even=\"'even'\" ng-transclude></tr>"
link: function (scope, element, attrs) {
attrs.$set('ng-repeat', attrs.customRepeater);
element.removeAttr('custom-repeater');
$compile(element)(scope);
}
}
});

Resources