ng-click in a ng-repeat within a directive - angularjs

I have a directive with a template to show a table of persons (name, nationality, dates and a button Details). When the button Details is clicked and alert is displayed. The problem is that I don't know how to make the ng-click of the button to work in the ng-repeat inside the directive. They told me to use the following configuration for the directive, using the show function from the controller for the ng-click:
angular.module('app', []).directive('persons', function() {
return {
restrict: 'E',
scope: {
data: '=',
action: '&'
},
template: '...'
};
});
The controller:
angular.module('app', []).controller('labController', [function() {
var vm = this;
vm.persons = [{
name: 'Mark Twatin',
nationality: 'American',
dates: '1835-1910'
}, {
name: 'A. A. Milne',
nationality: 'English',
dates: '1882-1956'
}, {
name: 'Ernest Hemingway',
nationality: 'American',
dates: '1899-1961'
}, {
name: 'Charles Dickens',
nationality: 'English',
dates: '1812-1870'
}, {
name: 'Jane Austen',
nationality: 'English',
dates: '1775-1817'
}];
vm.show = show;
function show(person) {
alert('Show details for: ' + person.name);
}
}]);
So I added the following template:
<table class="table">
<thead>
<th>Name</th>
<th>Nationality</th>
<th>Dates</th>
<th></th>
</thead>
<tbody>
<tr ng-repeat="person in data">
<td>{{person.name}}</td>
<td>{{person.nationality}}</td>
<td>{{person.dates}}</td>
<td>
<input
type="button"
ng-click="action(person)"
value="Details"
class="btn btn-primary"
/>
</td>
</tr>
</tbody>
</table>
And the HTML:
<body ng-app="app">
<div class="container" ng-controller="labController as vm">
<h1>Directives</h1>
<persons data="vm.persons"></persons>
</div>
</body>
So, how can I make the button Details work?
I've created the following fiddle: http://jsfiddle.net/6xyztpxt/3/

You need to change your call inside of the directive for the ngClick.
In a directive, if you want to call a bound function with parameters, you need to pass it an object with to parameters.
Change the ng-click in the template to this: ng-click="action({person: person})"
That means, call the action bounded function with a parameter person that has the value of the current person in the ngRepeat.
Then you need to bind your controller show function to the directive like so:
<persons data="vm.persons" action="vm.show(person)"></persons>
That means, bind the vm.show function as the action to the directive, and when is called pass a parameter person to it.
I have updated your fiddle: http://jsfiddle.net/gmm8a06q/1/

I've made some changes to your fiddle. I've changed the directive scope for action to '=', so you can access directly to the controller method "show".
scope: {
data: '=',
action: '='
},
http://jsfiddle.net/6xyztpxt/6/

Related

Angular isolate scope confusions - functions are undefined

I'm trying to create a tree-like structure with angular directives:
profileApp.directive('adminList', function() {
return {
restrict: 'E',
replace: true,
scope: {
doc: '='
},
template: `
<div class="admin-list" ng-repeat="sub in doc.subs">
<table>
<tbody>
<tr>
<td>
<input type="number" ng-model="sub.sort" ng-change="updateSort(sub, doc.subs)"/>
</td>
</tr>
</tbody>
</table>
<admin-list doc="sub"></admin-list>
</div>
`,
link: function(scope) {
scope.updateSort = function(sub, array) {
console.log('sub', sub);
console.log('array', array);
array.forEach(function() {
});
};
}
};
});
Now when I change my input nothing happens. If I do this "by hand" everything is fine.
Also I need to declare updateSort() in directive, because its' arguments are available only there.

What triggers a function call in other column when ngmodel changes in first column?

Consider the snippet:
JS
var mod = angular.module('module', []);
mod.controller('controller', function($scope) {
$scope.items = [{
id: 1,
label: 'aLabel',
subItem: {
name: 'aSubItem'
}
}, {
id: 2,
label: 'bLabel',
subItem: {
name: 'bSubItem'
}
}]
$scope.getValue = function(ngmodel) {
// some code goes here...
}
});
HTML
<body ng-controller='controller'>
<div>
<table>
<tr ng-repeat='count in counter'> // 5 times
<td>
<select ng-options="item.id as item.label for item in items"
ng-model="selected[$index]">
</select>
</td>
<td>
{{getValue(1)}}
</td>
<td>
</td>
</tr>
</table>
</div>
</body>
As soon as I select some value from the dropdown (select tag) in the first column, I notice that the function in the second column is triggered? What is the reason for this? What exactly is happening behind the scenes?
The reason is you are doing it inside ng-repeat
<tr ng-repeat = 'count in counter'>
You need to pass the object on the controller, in order to do some action on the same.
{{getValue(obj)}}

AngularJS directive with ngTransclude not showing {{bound}} content

I'm attempting to create an Angular directive that creates a standardised structure of a table that I wish to use around my application.
I want to specify the structure of the tr when I declare the directive in HTML so that I can have different layouts depending on the data that is passed in. However, I can't seem to get the content of ng-transclude to actually render.
Plunker: Example
I'd like the following:
<custom-table table="data">
<td>
{{row.Username}}
</td>
<td>
{{row.FirstName}}
</td>
<td>
{{row.LastName}}
</td>
</custom-table>
to be injected into the within the template.
How do I get the {{row.Username}} etc tags to resolve within the ng-transclude in the angular directive?
Edit1: I think this is a similar question that I've just found, although most top voted answer seems to recommend avoid using table, tr, td etc within directives :\
This doesn't answer your question, but I think it is a more generic way of doing what you want.
First pass the list of columns you want to display to your directive:
<custom-table table="data" columns="columns">
</custom-table>
In your controller:
app.controller('MainCtrl', function($scope) {
$scope.data = [
{Username: "Test1", FirstName: "Bill", LastName: "Jones"},
{Username: "Test2", FirstName: "Sophie", LastName: "O'Grady"},
{Username: "Test3", FirstName: "Tim", LastName: "Cross"}
];
$scope.columns = ["Username", "FirstName", "LastName"]
});
In your directive:
app.directive('customTable', ['$compile', function($compile){
return {
restrict: 'E',
templateUrl: 'tableTemplate.html',
scope: {
table: '=',
columns: '='
}
};
}]);
And finally change your template to:
<div>
<table>
<thead>
<tr>
<th ng-repeat="col in columns">{{ col }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in table">
<td ng-repeat="col in columns">
{{ row[col] }}
</td>
</tr>
</tbody>
</table>
</div>
And here's the updated plunker: http://plnkr.co/edit/dYwZWD2jB2GsmnvmuSbT
I found a work-around which solves the problem for me.
I've updated the plunker with a working example. I had to create a directive:
app.directive('myTransclude', function() {
return {
compile: function(tElement, tAttrs, transclude) {
return function(scope, iElement, iAttrs) {
transclude(scope.$new(), function(clone) {
iElement.append(clone);
});
};
}
};
});
I found the issue here within the comments.
I also had to update the directive so it uses a CSS/div based table rather than using an actual HTML table.

Angular update object in array

I wanna update an object within an objects array. Is there another possibility than iterating over all items and update the one which is matching? Current code looks like the following:
angular.module('app').controller('MyController', function($scope) {
$scope.object = {
name: 'test',
objects: [
{id: 1, name: 'test1'},
{id: 2, name: 'test2'}
]
};
$scope.update = function(id, data) {
var objects = $scope.object.objects;
for (var i = 0; i < objects.length; i++) {
if (objects[i].id === id) {
objects[i] = data;
break;
}
}
}
});
There are several ways to do that. Your situation is not very clear.
-> You can pass index instead of id. Then, your update function will be like:
$scope.update = function(index, data) {
$scope.object.objects[index] = data;
};
-> You can use ng-repeat on your view and bind object properties to input elements.
<div ng-repeat="item in object.objects">
ID: <input ng-model="item.id" /> <br/>
Name: <input ng-model="item.name" /> <br/>
</div>
Filters that help in finding the element from the array, can also be used to update the element in the array directly.
In the code below [0] --> is the object accessed directly.
Plunker Demo
$filter('filter')($scope.model, {firstName: selected})[0]
Pass the item to your update method. Take a look at sample bellow.
function MyCtrl($scope) {
$scope.items =
[
{name: 'obj1', info: {text: 'some extra info for obj1', show: true}},
{name: 'obj2', info: {text: 'some extra info for obj2', show: false}},
];
$scope.updateName = function(item, newName){
item.name = newName;
}
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app>
<table ng-controller="MyCtrl" class="table table-hover table-striped">
<tr ng-repeat="x in items">
<td> {{ x.name }}</td>
<td>
Update
<div ng-show="showUpdate" ><input type="text" ng-model="someNewName"> <input type="button" value="update" ng-click="updateName(x, someNewName); showUpdate = false;"></div>
</td>
</tr>
</table>
</body>
Going off your plunker, I would do this:
Change
Edit
to be
Edit
Then use the array index within your $scope.selectSubObject method to directly access your desired element. Something like this:
$scope.selectSubObject = function(idx) {
$scope.selectedSubObject = angular.copy(
$scope.selectedMainObject.subObjects[idx]
);
};
If however, you only have the id to go off of, then you can use the angular filterService to filter on the id that you want. But this will still do a loop and iterate over the array in the background.
See angularjs documentation for ngrepeat to see the variables that it exposes.

$compile does not compile directive template with ngRepeat

After hour of powergoogle I can not imagine why directive`s template does not compile.
So this is my partial view html: (see the ngRepeat)
<appheader currenttab="currentTab"></appheader>
<div class="l-c-r-panes">
<div class="l-pane">
<renters></renters>
</div>
<div class="c-pane">
<div class="form-header" style="position: relative;">
<input class="form-control filter-above-table" type="text" ng-model="filter.$" custom-search />
<table ng-table="invoiceGrid" class="table" id="invoice-table">
<tr ng-repeat="invoice in $data"
ng-click="invoiceGridRowClick(invoice, $data)"
ng-class="{'active': invoice.$selected}" invoice-info-tooltip="invoice">
<td class="invoice-num-column" data-title="'Счет'" sortable="'Invoice'" >{{invoice.Invoice}}</td>
<td data-title="'Арендатор'" sortable="'Renter'">{{invoice.Renter}}</td>
<td class="invoice-sum-column" data-title="'Сумма по счёту'" sortable="'InvoiceSum'">{{invoice.InvoiceSum}}</td>
<td class="invoice-sum-column" data-title="'Оплата (сумма)'" sortable="'PaySum'">{{invoice.PaySum}}</td>
</tr>
</table>
</div>
</div>
<div class="r-pane">
<tasks></tasks>
</div>
</div>
<script type="text/ng-template" id="invoiceTooltipTemplate">
<div ng-repeat="employee in employees">
<div>{{employee.Post}}</div>
<div>{{employee.Name}}</div>
<div>{{employee.Phone}}</div>
<div>{{employee.Info}}</div>
<div>
</script>
And that is my invoiceInfoTooltipDirective:
//require qtip2
angular.module('invoiceModule')
.directive('invoiceInfoTooltip', ['$compile', function ($compile) {
return {
restrict: 'A',
scope: {
invoice: '=invoiceInfoTooltip'
},
link: function (scope, el, attrs) {
if (scope.invoice) {
var tooltipTitle = scope.invoice.Renter;
var tooltipText = '';
scope.employees = scope.invoice.RenterInfo.Employees;
tooltipText = $compile($('#invoiceTooltipTemplate').html())(scope);
el.qtip({
overwrite: true,
content: {
title: tooltipTitle,
text: tooltipText
},
style: {
classes: 'qtip-light invoice-qtip c-invoice-table-tooltip'
},
show: {
event: 'click',
solo: true
},
hide: {
fixed: true,
leave: true,
event: null
},
position: {
my: 'top center',
target: 'mouse',
adjust: {
mouse: false
}
}
});
}
}
};
}]);
directive use template #invoiceTooltipTemplate located in partial view
If this template do not have ngRepate, $compile in directive works fine. But I need iterate some content in template and want to use ngRepeat.
No console errors. Nothing.
If temlate does not compile it return this jquery obj:
[comment, jquery: "2.1.1", constructor: function, selector: "", toArray: function, get: function…]
0: comment
baseURI: null
childNodes: NodeList[0]
data: " ngRepeat: employee in employees "
firstChild: null
jQuery21106483948081731796: undefined
lastChild: null
length: 33
localName: null
namespaceURI: null
nextElementSibling: div.ng-scope
nextSibling: div.ng-scope
nodeName: "#comment"
nodeType: 8
nodeValue: " ngRepeat: employee in employees "
ownerDocument: document
parentElement: null
parentNode: document-fragment
previousElementSibling: null
previousSibling: null
textContent: " ngRepeat: employee in employees "
__proto__: Comment
length: 1
__proto__: Object[0]
I had similar situation in my project, but there`re no ngTable directive with ngRepeat. And it works fine.
Template always must have root element. So easy mistake and I got it..
So template looks like this. And it works.
<script type="text/ng-template" id="invoiceTooltipTemplate">
<div>
<div ng-repeat="employee in employees">
<div>{{employee.Post}}</div>
<div>{{employee.Name}}</div>
<div>{{employee.Phone}}</div>
<div>{{employee.Info}}</div>
</div>
</div>
</script>

Resources