How to include <tr> element in angular directive (transclude)? - javascript

I want to include <tr> and <td> and apparently I can't do that with directive. It keeps ignoring <td> or <td> as if they don't exists. here's what I trying to accomplish:
<my-table>
<tr>
<td>hello</td>
<td>world</td>
</tr>
</my-table>
Here's the javascript:
angular.module('app', [])
.controller('Controller', ['$scope', function($scope) {
}])
.directive('myTable', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'my-table.html'
};
});
my-table.html :
<table ng-transclude>
</table>
the code above resulted in:
<my-table class="ng-isolate-scope"><table ng-transclude="">
hello <-- no <tr> nor <td> here just plain text
world
</table></my-table>
example : PLUNKR

It's not a transclude problem. It's a problem with invalid html, because <tr> without a table is invalid. So angular gets from a browser text, not DOM elements. So you need to have <table> tag inside an html:
<my-table>
<table>
<tr>
<td>hello</td>
<td>world</td>
</tr>
</table>
</my-table>
Then you'll be able to get access to tbody element created by a browser along with tr's in link function and process it:
link: function(scope,element,attrs,ctrls,transclude) {
var html = transclude();
element.find('table').append(html[1].firstElementChild);
}
or use ng-transclude in your template as you did. However, I may presume that you'll want to reuse the transcluded part later, so accessing it in link function makes more sense to me.

Adding into my comment earlier, you can achieve somewhat similar like this this if you wan to use ng-trasclude
angular.module('app', [])
.controller('Controller', ['$scope', function($scope) {
}])
.directive('myTable', function() {
return {
restrict: 'E',
transclude: true,
scope: {
'close': '&onClose'
},
templateUrl: 'my-dialog-close.html'
};
});
template
index.html
<my-table>
<table>
<tr>
<td>hello</td>
<td>world</td>
</tr>
</table>
</my-table>
Plunker : https://plnkr.co/edit/u85h0sJL50k2gESfI6RT?p=preview

Related

Custom directive inside ng-repeat

I have this custom directive.
I call my directive like bellow, inside a ng-repeat.
selectedMealCalc.calculated_foods as 'items', is an array of objects
<!-- DIRECTIVE -->
<div ng-repeat="option in [0,1,2,3,4]">
<meal-option option="{{option}}"
items="selectedMealCalc.calculated_foods"
selectedmealcalc="selectedMealCalc"></meal-option> </div>
<!-- DIRECTIVE -->
Then I created this directive in angularjs.
'use strict';
angular.module('nutriApp').directive('mealOption', ['$compile', function($compile) {
return {
restrict: 'E',
templateUrl: 'views/checkins/meal-options.html',
scope: {
option: "#",
items: "=",
selectedmealcalc: "="
},
controller: ['$scope', 'Food', function($scope, Food) {
$scope.sumFood = {};
$scope.summerizeOption = function(foods) {
if(foods.length > 0){
$scope.sumFood = Food.summerize(foods);
}
return $scope.sumFood;
};
}]
};
}]);
And this HTML directive.
<div class="row" ng-init="filteredItems = ( items | filter: { food_option: option } )" ng-controller="CheckinsPlansCtrl">
<div class="col-md-12" ng-show="filteredItems.length > 0">
Opção {{ option }}
<table class="table table-calculo table-striped">
<thead>
<tr>
<th>Alimento</th>
<th>Quantidade</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="foodCalculation in filteredItems track by $index">
<td>{{foodCalculation.food.name}}</td>
<td>{{foodCalculation.gram_amount}} g</td>
</tr>
</tbody>
</table>
</div>
</div>
When I update the selectedMealCalc.calculated_foods the custom directive is not updating.
I have to close the modal and open again in my page to saw the new line.
As this comment Custom directive inside ng-repeat in this post.
I removed the ng-init because ng-init: "Only to initialize a property on the scope. I would recommend not to use it." as this another answer Does ng-init watch over change on instantiated property like ng-model does?

Angular call controller method from directive template

I create simple project with angular and I use directive to create a simple grid like this code :
my directive :
app.directive('dpGrid',()=>{
return{
restrict:"E",
scope:{
items:"="
}
templateUrl: 'template/dbgrid.directive.html'
}
});
my controller :
app.controller('mainCtrl',($scope)=>{
$scope.data=[{fname:"david",lname:"orman",age:24,id:"234"}];
$scope.update=(id)=>{
console.log("ID :",id);
};
});
my directive template :
<table class="table">
<thead class="thead-inverse">
<tr>
<th>#</th>
<th>fname</th>
<th>lname</th>
<th>age</th>
<th>opt</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items" >
<th>{{$index}}</th>
<td>{{item.fname}}</td>
<td>{{item.lname}}</td>
<td>{{item.age}}</td>
<td><a class="btn btn-danger" ng-click="update(item.id)">update</a></td>
</tr>
</tbody>
</table>
and I use directive like this :
<dp-grid items="data" ></dp-grid>
I want call update() method from directive template, but dont call update() method when click on update btn
You simply need to specify the controller your directive should use, then access it in template.
return {
restrict:"E",
scope:{
items:"="
},
controller: 'mainCtrl',
templateUrl: 'template/dbgrid.directive.html'
}
Then you will be able to access the function in the templates. If you are accessing it from a child isolate scope, you may need to access it using $parent.

Angular ng-transclude scope in repeat

I just started to study AngularJS and tries to implement customized table directive with the multiple slots transclude.
And faced situation that scope not transferred to transclude. There is a lot of solutions in other StackOverflow questions, but all of them works only when in directive template ng-repeat appears for top element, but that is not my case.
At least i can't adopt all that solutions.
Simplified version.
Directive:
<span>
<div>Some pagination</div>
<div style="display: inline"><input type="text" placeholder="Search"/></div>
<div style="display: inline">Some filters</div>
<table>
<tbody>
<tr ng-repeat="line in lines" ng-transclude="row">
</tr>
</tbody>
</table>
<div>Some pagination again</div>
</span>
Using of directive:
<my-table>
<row>
<td>{{line.col1}}</td>
<td>{{line.col2}}</td>
</row>
</my-table>
Full example with the script on Plunkr:
https://plnkr.co/edit/rg43ZdPMGHLBJCTLOoLC
Any advice very appreciated.
The simplest and probably cleanest way to directly reference the $scope object created by the ng-repeat in a transcluded template is through the $parent property:
<my-table>
<td>{{$parent.line.col1}}</td>
<td>{{$parent.line.col2}}</td>
</my-table>
The $parent property of the $scope created for a transcluded template points to the $scope of the target template into which such template is ultimately transcluded (in this case, the ng-repeat), even though such transcluded $scope is not a child of the target $scope in the usual sense as a result of the transclusion. See this wonderful blog post for a more complete discussion of this.
Working plunkr: https://plnkr.co/edit/LoqIMiQVZKlTt5epDnZF?p=preview.
You need to use $transclude function manually and create new child scope for each line. Other than that you need to pass lines to directive if you are using isolated scope (and you are using it).
Your linking function should look something like this:
link: function($scope, $element, $attrs, controller, $transclude) {
var tbody = $element.find('tbody');
$scope.$watch('lines', function (lines) {
tbody.empty();
lines.forEach(function (line) {
var childScope = $scope.$new();
childScope.line = line;
$transclude(childScope, function (content) {
tbody.append('<tr>');
tbody.append(content);
tbody.append('</tr>');
}, null, 'row');
});
});
}
Plunker: https://plnkr.co/edit/MLNZOmoQyMazgIpluMqO?p=preview
But that is bad idea anyway, cos it is hard to create table this way. As you can see child of is not elements. You would have to do a little bit of DOM manipulation to make it work.
i see you don't really need to use attribute
so code look more simple and clean:
<body ng-controller="tableCtrl">
<h1>Table test</h1>
<my-table lines="lines"></my-table>
</body>
your template:
<span>
<div>Some pagination</div>
<div style="display: inline"><input type="text" placeholder="Search"/></div>
<div style="display: inline">Some filters</div>
<table>
<tbody>
<tr ng-repeat="line in lines">
<td>{{line.col1}}</td>
<td>{{line.col2}}</td>
</tr>
</tbody>
</table>
<div>Some pagination again</div>
</span>
and angular directive:
angular.module('myApp', [])
.directive("myTable", function() {
return {
restrict: 'E',
transclude: true,
scope: {
lines:'=lines',
api: '#'
},
templateUrl: "template.html",
};
})
.controller("tableCtrl", ['$scope', function($scope) {
$scope.lines = [
{col1: "testCol1", col2: "testCol2"},
{col1: "testCol11", col2: "testCol21"}
];
}]);
working example in plunkr: https://plnkr.co/edit/iMxRoD0N3sUXqmViHAQh?p=preview

How to pass parameters in nested controllers in Angular JS

I have a situation where I get some data from a REST API in a controller, I render that data using ng-repeat. Then in that loop, I need to run another controller, pass it data from earlier controller, do some operations on it and then again run an ng-repeat on it.
When I do it, "Inspect Element" shows value kept in parent controller's parameter. But the value which is being passed to the nested controller is actually the variable name.
Source code:
HTML:
<div class="checkbox" ng-repeat="bird in birds">
<table>
<tr>
<td>
<a ng-href="/birds/{{bird.Image}}" rel="shadowbox"><img ng-src="/birds/{{bird.Image}}" height="200" width="200"></img></a>
<div ng-controller="imageController" model="{{ bird.AdditionalImages }}">More Images: {{ imageString }}
<div ng-repeat="image in images">
<a ng-href="/birds/{{image}}" rel="shadowbox[{{ bird.Image }}]">a</a>
</div>
</div>
</td>
<td>
<table>
<tr>
<td>
<b>{{ bird.CommonName }}</b>
</td>
</tr>
<tr>
<td>
Spotted at: {{ bird.SpottedAt }}
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
JavaScript (for nested controller):
anekchidiya.controller('imageController', function($scope, $attrs) {
$scope.imageString = $attrs.model;
console.log("images: " + $scope.imageString);
});
You can perform it by passing your scope into a directive, and you will create an isolated scope.
For example :
Controller
(function(){
function Controller($scope) {
$scope.data = [{
name: 'john',
age: '26'
}, {
name: 'paul',
age: '24'
}, {
name: 'titi',
age: '32'
}];
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Directive
(function(){
function customDirective() {
return{
restrict: 'AE',
template: '<h3>Age : {{age}}</h3>',
scope: {
age: '='
}
};
}
angular
.module('app')
.directive('customDirective', customDirective);
})();
And you can call your directive into the ngRepeat for example, by passing some data :
HTML
<body ng-app="app" ng-controller="ctrl">
<div ng-repeat="item in data">
<h2>Name : {{item.name}}</h2>
<custom-directive age="item.age"></custom-directive>
</div>
</body>
So, typical usage of an isolated scope , it is in a directive that creates a complete component, a widget, etc ...
So, you will be able to build some custom components, and to pass specific data.

angularjs: how does the replace attribute works for directive ?

I'm trying to figure out how the 'replace' attribute work for directive. I've run into a scenario when setting it to true was causing my code to break.
The directive:
angular.module('cdt.dm.directives').directive('referenceFiles', [
function () {
return {
restrict: 'E',
templateUrl: 'app/dm/views/templates/referenceFiles/referenceFiles.html',
scope: {
job: '='
},
link: function (scope, element, attr) {
scope.deleteReferenceFile = function (id) {
scope.job.references.splice(id, 1);
}
}
}
}]);
the referenceFiles.html template:
<div class="grid-action-filter" popover-placement="left" popover-template="app/dm/views/templates/referenceFiles/simple.html">
<span class="badge" style="cursor:pointer" >{{job.references.length}} added</span>
the simple.html template used by the popover directive:
<span>{{job.references.length}} reference files</span>
<table ng-repeat="ref in job.references">
<tr>
<td>{{ref.name}}</td>
<td>
<button class="btn grid-button btn-danger" ng-click="deleteReferenceFile($index);"><i class="fa fa-trash-o"></i></button>
</td>
</tr>
</table>
If I set replace to true in the referenceFiles directive, then the deleteReferenceFile method won't be found on the scope when clicking the button. Instead I have to call it this way:
$parent.$parent.$parent.$parent.deleteReferenceFile($index)
needless to say, it's ugly...
If I remove the replace attribute of the directive, then everything works fine.
Could someone explain this behaviour ?

Categories

Resources