Angular call controller method from directive template - javascript

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.

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?

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

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

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.

Using a template in a backbone view that includes the parent element

I am attempting to refactor my backbone views to have all HTML related markup in external templates. Ideally I would like to have the element that the view is attached to in the external template as well. At the moment I have:
The html template
<h3 class="pull-left">Search</h3>
<input id="customer-search-input" class="input-large search-query" placeholder="Cust #, Name, Suburb or Owner" />
<button id="customer-search-show-all" class="btn pull-right">Show All</button>
<span id="update-time" class ="pull-right"></span>
<table id="customer-search-results-table" class="tablesorter tablesorter-dropbox">
<thead>
<tr>
<th>Customer Number</th>
<th>Customer Name</th>
<th>Suburb</th>
<th>Owner</th>
<th>Phone Number</th>
</tr>
</thead>
<tbody id="customer-list-results">
</tbody>
</table>
And the backbone view that consumes the template:
define(['jquery','underscore', 'backbone', 'text!templates/customerSearch.html','text!templates/customerRow.html', 'jquery.tablesorter' ],
function($, _, Backbone, customerSearchTemplate, customerRow) {
// ... Other sub-views
var CustomerSearch = Backbone.View.extend({
id:'customer-search', // would prefer to have these
className: 'well', // in the template
initialize: function(){
this.$el.html(customerSearchTemplate);
this.customerSearchInput = this.$("#customer-search-input");
},
events: {
"click #customer-search-show-all": "showAll",
"keyup #customer-search-input": "search"
},
search: function(){
var filteredCustomers = this.collection.search(this.customerSearchInput.val(), ['id','companyName','suburb','businessContact']);
this.customerSearchResultsView = new CustomerSearchResultsView({collection: filteredCustomers});
this.customerSearchResultsView.render();
},
showAll: function() {
this.customerSearchResultsView = new CustomerSearchResultsView({collection: this.collection});
this.customerSearchResultsView.render();
}
});
return CustomerSearch;
});
Everything works but it would be great to be able to have the id and className as part of a wrapper div in the template. If I add this to the template then it appears correctly when rendered but is wrapped by another div by the backbone view.
I'm trying to decouple everything as much as possible.
Thanks!
Update 17 Oct 2012
Using the view.setElement method
var CustomerSearch = Backbone.View.extend({
template:_.template(customerSearchTemplate),
initialize: function(){
this.setElement(this.template());
},
// ...
});
with template
<div id="customer-search" class="well">
<h3 class="pull-left">Search</h3>
// ...
</div>
appears to work. Just wondering now if there is performance hit. Will report back.
You can wrap your template element within a script tag with an id.
<script id="custom-search" type="text/template">
<h3 class="pull-left">Search</h3>
<input id="customer-search-input" class="input-large search-query" placeholder="Cust #, Name, Suburb or Owner" />
<button id="customer-search-show-all" class="btn pull-right">Show All</button>
<span id="update-time" class ="pull-right"></span>
<table id="customer-search-results-table" class="tablesorter tablesorter-dropbox">
<thead>
<tr>
<th>Customer Number</th>
<th>Customer Name</th>
<th>Suburb</th>
<th>Owner</th>
<th>Phone Number</th>
</tr>
</thead>
<tbody id="customer-list-results">
</tbody>
</table>
</script>
And then, declare the following option in your view:
template : _.template($('#custom-search').html())
You will then be able to call :
this.$el.html(this.template());
in your initialize function. This will load the content of the script tag.
Why not 'wrap' your template in a parent div which includes the id / class (plus any other attribute you'd want to use)
<div id="customer-search" class="well"> ... </div>
and then use setElement to set the div as the View's el-ement.
(Note: I've never used the text plugin. But I see no reason you couldn't go with this method)
Also: more about setElement.

Categories

Resources