Hy,
I created a directive for a simple use of tables in my application.
I'm struggling to get an ng-repeat to work.
The problem is that my directive will tell the table from what controller it will get the list with the data for the ng-repeat and this seems not to work, i could use another directive to create an ng-repeat but i don´t want to change the way the developer can use the angular directives.
So if anyone already struggled with this problem please share how you resolve it.
this is the markup.
<body ng-app="List">
<div my-table pl-emptytext="No data found!">
<table>
<thead>
<tr>
<th pl-sort="Id"> Id </th>
<th pl-sort="Name"> Name </th>
<th pl-sort="Roles"> Roles </th>
<th pl-sort="Claims"> Claims </th>
</tr>
</thead>
<tbody>
<tr pl-obj="user">
<td>{{user.Id}}</td>
<td>{{user.Name}}</td>
<td>
<span ng-repeat="role in user.Roles">
{{role.RoleType}}
</span>
</td>
<td>
<span ng-repeat="claim in user.Claims">
{{role.ClaimType}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</body>
This is the directive myTable
angular
.module('List')
.directive('myTable', [
'$compile',
function($compile) {
return {
restriction: 'AE',
scope: {
plEmptytext: '#'
},
transclude: true,
replace: true,
template: '<div class="container-fluid">' +
'<div class="row">' +
'<div class="col-lg-12">' +
'<div class="alert alert-warning" ng-cloak ng-show="ListIsEmpty">' +
'{{plEmptytext}}' +
'</div>' +
'<div ng-transclude></div>' +
'</div>' +
'</div>' +
'</div>',
link: function(scope, element) {
// Do something else, bind events, etc ...
var table = element.find('table');
$compile(table)(scope);
}
};
}
])
.directive('table', [
function() {
return {
link: function(scope, element, attrs) {
attrs.$addClass("table");
attrs.$addClass("table-striped");
}
};
}
])
.directive('thead', [
function() {
return {
link: function(scope, element, attrs) {
}
};
}
])
.directive('th', [
function() {
return {
link: function(scope, element, attrs) {
if (attrs.plSort) {
attrs.$set("ngClick", "resort('" + attrs.plSort + "')");
}
}
};
}
])
.directive('tbody', [
function() {
return {
link: function(scope, element, attrs) {
attrs.$set("ng-controller", "listCtrl");
}
};
}
])
.directive('tr', [
function() {
return {
link: function(scope, element, attrs) {
if (attrs.plObj) {
attrs.$set("ngRepeat", attrs.plObj + " in List");
}
}
};
}
]);
And this is the listCtrl
angular.module('List', [])
.controller('listCtrl', ['$scope', '$timeout',
function($scope, $timeout) {
$timeout(function() {
$scope.List = [{
Id: '1',
Name: 'teste1',
Roles: [{
Id: 1,
RoleType: '1'
}, {
Id: 2,
RoleType: '2'
}],
Claims: [{
Id: 1,
CalimType: '1'
}, {
Id: 2,
ClaimType: '2'
}]
}, {
Id: '1',
Name: 'teste1',
Roles: [{
Id: 2,
RoleType: '2'
}],
Claims: [{
Id: 2,
ClaimType: '2'
}]
}];
}, 1000)
}
]);
The problem is when i try to call the ng-repeat on this 2 td it dosen´t work
<td>
<span ng-repeat="role in user.Roles">
{{role.RoleType}}
</span>
</td>
<td>
<span ng-repeat="claim in user.Claims">
{{role.ClaimType}}
</span>
</td>
Anyone can point me in the direction of the problem? Already look to different things and different solutions from different people, yet it seems that no one had face my problem.
Best,
Plunker
First of all, your pl-obj directive is literally just an ng-repeat. So use <tr ng-repeat="user in List">.
Second of all, your controller doesn't appear to be hooked up. You're trying to hook it up using a strange directive, but you may be doing it in a way Angular doesn't understand (properties of the attrs object are camelCased, not hyphen-ated). Just use an ng-controller attribute on your table element: <table ng-controller="listCtrl">.
Once you're using more standard Angular in this way, I expect your problem will be easier to troubleshoot.
EDIT: Here's a Plunker with the changes I recommended (and some corrected typos). https://plnkr.co/edit/iEEtBycevB3Wh6eHFEAI?p=preview. Seems to work fine now.
EDIT 2: If you want to use the same controller for all <tbody> (I don't recommend this, but it's something you can do), use this in your directive:
function() {
return {
controller: 'listCtrl',
...
};
}
Related
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.
We are using Angular 1.3.15
I have an element directive like
(function () {
var controller = function ($scope, $timeout) {
this.$timeout = $timeout;
$scope.$watch("vm.spinning", this.throttle.bind(this));
};
controller.prototype = {
throttle: function (spinning, oldValue) {
if (spinning === oldValue) return;
this.$timeout.cancel(this.timeout);
if (spinning) {
this.timeout = this.$timeout(this.setSpinning.bind(this), 100);
} else {
this.setSpinning();
}
},
setSpinning: function() {
this.spinningThrottled = this.spinning;
}
};
myapp.controller("SpinnerController", ["$scope", "$timeout", controller]);
myapp.directive("spinner", function () {
return {
restrict: "E",
scope: {
spinning: "=",
},
replace: true,
controller: "SpinnerController",
controllerAs: 'vm',
bindToController: true,
templateUrl: "Views_App/Components/Spinner.html"
};
});
myapp.directive("spinnerGrid", function () {
return {
restrict: "E",
scope: {
spinning: "=",
},
replace: true,
controller: "SpinnerController",
controllerAs: 'vm',
bindToController: true,
templateUrl: "Views_App/Components/SpinnerGrid.html"
};
});
})();
The spinner grid template
<tr data-ng-if="vm.spinningThrottled">
<td colspan="100">
<i class="fa fa-spinner fa-spin"></i> Please wait...
</td>
</tr>
Usage
<table>
<thead>...</thead>
<tbody>
<spinner-grid spinning="searching"></spinner-grid>
<tr ng-repeat="...">..</tr>
</tbody>
</table>
For somereason the content of the directive is moved outside of the table?
edit: The question might be a duplicate, but K.Toress answer is not since it utilizes EA restriction
I think its a normal behavior of the table, if there is a div or any other tag (excluding <tr>) inside a table without inside of a <td> it will remove that and place it on the table.
In your case when html rendering time (which is before the angular executes its scripts) html detects there is a unappropriated tag inside the table which is not inside a td it will remove and place on top of the table, Then when the angular execution time the directive element is not inside the table instead it will top of the table. that's why its render on top of the table.
Please check you can do something like this,
<tbody>
<tr data-ng-if="vm.spinningThrottled"><spinner-grid spinning="searching"></spinner-grid></tr>
<tr ng-repeat="...">..</tr>
</tbody>
SpinnerGrid.html
<td colspan="100">
<i class="fa fa-spinner fa-spin"></i> Please wait...
</td>
or check this one
directive as a attribute of the <tr>
<tbody>
<tr spinner-grid spinning="searching"></tr>
<tr ng-repeat="...">..</tr>
</tbody>
change the directive definition to support the directive as a attribute. restrict: "EA".
myapp.directive("spinnerGrid", function () {
return {
restrict: "EA",
scope: {
spinning: "=",
},
replace: true,
controller: "SpinnerController",
controllerAs: 'vm',
bindToController: true,
templateUrl: "Views_App/Components/SpinnerGrid.html"
};
});
I have a directive as follows,
var myApp = angular.module('myApp', []);
myApp.directive("checkTextCombo", [
"$compile", function($compile) {
return {
restrict: "E",
replace: true,
scope: {
source: '#',
},
link: function(scope, element, attrs) {
scope.rows = eval('(' + scope.source + ')');
},
templateUrl: "../templates/check-text-combo/check-text-combo.html",
controller: function($scope){
$scope.$watch('$scope.rows', function() {
console.log($scope.rows);
});
}
};
}
]);
And a template:
<div ng-repeat="row in rows">
<input type="checkbox" ng-checked="row.checked"/> <label value="{{row.label}}">{{row.label}}</label><input type="textbox" value="{{row.value}}" ng-disabled="!row.checked"/>
</div>
index.html consists of
<check-text-combo source="[{'value':'varsh','label':'name','checked': true}, {'value':'bij','label':'name','checked': false}]"></check-text-combo>
My problem is that, I can a create a template with ng-repeat, but after binding, when I change something in the template, it doesn't change elsewhere. How can I get the modified value?
Got it working after adding ng-model
<div ng-repeat="row in rows">
<input type="checkbox" ng-checked="row.checked" ng-model="row.checked"/> <label value="{{row.label}}">{{row.label}}</label><input type="textbox" value="{{row.value}}" ng-model="row.value" ng-disabled="!row.checked"/>
</div>
You should to change your $watch definition:
$scope.$watch('rows', function() {
// ^
// Here
// rows instead of $scope.rows
console.log($scope.rows);
}, true);
// ^
//and here
// third parameter to true - observed variable is an Object
I'm trying to put together a reusable but custom table-type control using directives and tansclude in AngularJS and I'm running into a wall with not being able to customize the content that goes into the table's columns using {{syntax}}.
I've tried many different variations but have been unable to get it working. I'd include code but I don't think I am on the right track yet?
What I want to achieve:
data:
mymodel.items = [{ number: 1, name: 'John'}, {number: 2, name: 'Bob'}]
html:
<grid items="mymodel.items">
<column title="#">#{{item.number}}</column>
<column title="Name"><b>Hello {{item.name}}</b></column>
</grid>
template:
<table>
<thead>
<th ng-repeat="column in columns">{{column.title}}</th>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td ng-repeat="column in columns">{{columnValue(item, column.content)}}</th>
</tr>
</tbody>
</table>
expected output:
<table>
<thead>
<th>#</th>
<th>Name</th>
</thead>
<tbody>
<tr>
<td>#1</td>
<td>Hello John</td>
</tr>
<tr>
<td>#2</td>
<td>Hello Bob</td>
</tr>
</tbody>
</table>
I can't seem to be able to figure out how to transclude the content in "grid" such that it can be interpolated.
You can manually create the template:
Here is a plunker: http://plnkr.co/edit/C7HTRYZ4Hs1uOC9PXuza?p=preview
app.directive('grid', function() {
return {
restrict: 'E',
replace:true,
scope: {
items: "="
},
template: function(tElm, tAttrs) {
var td = "", th = "";
angular.forEach(tElm.find('column'), function(column){
th = th + "<th>" + column.title + "</th>";
td = td + "<td>" + column.innerHTML + "</td>";
});
var template = '<table>' +
'<thead>' + th + '</thead>' +
'<tbody>' +
'<tr ng-repeat="item in items">'+ td +'</tr>' +
'</tbody>' +
'</table>';
return template;
}
};
});
You can also build it with more directives:
Here is a demo plunker: http://plnkr.co/edit/a3xOSS6Un6XE9b1hQhvw?p=preview
One directive for saving a copy of transcluded columns (must run first):
app.directive('grid', function() {
return {
priority: 1200,
restrict: 'E',
compile: function(tElm, tAttrs) {
tAttrs.columns = tElm.find('column');
tElm.empty();
},
controller: function($attrs) {
this.columns = $attrs.columns;
}
};
});
Other directive for creating an isolated scope and replacing with a template:
app.directive('grid', function() {
return {
priority: 1100,
restrict: 'E',
scope:{
items: "="
},
templateUrl: 'grid.html'
};
});
Now I came up with this template:
<table>
<thead title-transclude></thead>
<tbody>
<tr ng-repeat="item in items" row-transclude></tr>
</tbody>
</table>
And two different directives to clone the headers and the contents. Inside these directives you have a reference to the transcluded columns via the grid controller:
app.directive('titleTransclude', function($compile) {
return {
require: '^grid',
link: function(scope, elm, attr, grid) {
var clones = [];
angular.forEach(grid.columns, function(col) {
var th = document.createElement('th');
th.innerHTML = col.title;
clones.push(th);
});
elm.append(clones);
$compile(clones)(scope);
}
};
});
app.directive('rowTransclude', function($compile) {
return {
require: '^grid',
link: function(scope, elm, attr, grid) {
var clones = [];
angular.forEach(grid.columns, function(col) {
var td = document.createElement('td');
td.innerHTML = col.innerHTML;
clones.push(td);
});
elm.append(clones);
$compile(clones)(scope);
}
};
});
I have been trying to figure out how to generate a table using a directive and an array of data. Basically I want to be able to structure my html like this:
<div ng-init="myData = [name: 'John', age: 22]"></div>
<dynamic-table data="myData">
<field title="column 1" class="myClass" expression="{{object.name}}" />
<field title="column 2" class="myClass" expression="{{object.age}}" />
</dynamic-table>
From that html, I want my directive to generate the following HTML:
<table>
<tr>
<th>column 1</th>
<th>column 2</th>
</tr>
<tr>
<th>John</th>
<th>22</th>
</tr>
</table>
The problem is that I need the expression {{object.name}} to be evaluated inside the directive rather than prior to begin passed in to the directive. I have a jsfiddle here demonstrating the problem. I would also be interested to hear about any other ways to accomplish this. I've tried it using the compile directive function but eventually you run into the same thing with the expression. Of course, I could do this using ng-repeats, but my directive will eventually do more than just this.
Here is the directive from the fiddle:
var appModule = angular.module('app', [])
.directive('dynamicTable', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
template:
'<table>' +
'<tr>' +
'<th ng-repeat="field in fields">{{field.title}}</th> ' +
'</tr>' +
'<tr ng-repeat="object in data" ng-transclude>' +
'<td ng-repeat="field in fields">' +
'{{field.expression}}' +
'</td>' +
'</tr>' +
'</table>',
scope: {
data: '='
},
controller: function ($scope) {
$scope.fields = [];
}
};
}).directive('field', function () {
return {
restrict: 'E',
require: '^dynamicTable',
link: function (scope, element, attrs, controller) {
var exists = false;
for (var idx = 0; idx < scope.fields.length; idx++) {
if (scope.fields[idx].title === attrs.title) {
exists = true;
break;
}
}
if (!exists) {
scope.fields.splice(0, 0, { title: attrs.title, 'class': attrs.class, expression: attrs.expression });
}
}
};
});