AngularJS - controller method doesn't get called on ngClick - no error - javascript

I try to call the method removePlayer(playerId) if a button gets clicked. But, the method doesn't get called, or at least the statements inside it aren't firing, because I put a console.log() statement at the top.
The console is empty, so I'm really clueless. Here is my code:
Controller:
function appController($scope) {
$scope.players = [];
var playercount = 0;
$scope.addPlayer = function(playername) {
$scope.players.push({name: playername, score: 0, id: playercount});
playercount++;
}
function getIndexOfPlayerWithId(playerId) {
for (var i = $scope.players.length - 1; i > -1; i--) {
if ($scope.players[i].id == playerId)
return i;
}
}
$scope.removePlayer = function(playerId) {
console.log("remove");
var index = getIndexOfPlayerWithId(playerId);
$scope.players.slice(index, 1);
}
}
appController.$inject = ['$scope'];
HTML:
...
<table id="players">
<tr ng-repeat="player in players">
<td>{{player.name}}</td>
<td>{{player.score}}</td>
<td><button ng-click="removePlayer({{player.id}})">Remove</button></td>
</tr>
</table>
...

You shouldn't be using curly braces ({{ }}) in the ng-click expression. You should write:
<button ng-click="removePlayer(player.id)">Remove</button>

ng-repeat creates a new scope, so it doesn't know what removePlayer is. You should be able to do something like this:
<table id="players">
<tr ng-repeat="player in players">
<td>{{player.name}}</td>
<td>{{player.score}}</td>
<td><button ng-click="$parent.removePlayer({{player.id}})">Remove</button></td>
</tr>
</table>
See https://groups.google.com/forum/?fromgroups=#!topic/angular/NXyZHRTAnLA

As stated, ng-repeat creates it's own scope, and the outer controller scope is not available. But since in JS you are using true objects write something like this:
<tr ng-repeat="player in players">
<td>{{player.name}}</td>
<td>{{player.score}}</td>
<td><button ng-click="player.removePlayer()">Remove</button></td>
</tr>
Beforehand, somewhere on your controller initialization you can assing the "removePlayer" function to each of your player object and naturally code in anything you want, thus accessing outer controller indirectly.

Related

Angular 2 set boolean at end of iteration using *ngFor

I am loading some data via rest-endport to safe it in an array. In an *ngFor directive i am currently iterating through all that data. So everthing is working.
Is there any possible way in Angular to e.g. set a boolean whenever the iteration of the array is finished (so that i can fire another function on complete)?
-- Edit--
On the ngOnInit lifecycle method i am retreiving the data:
ngOnInit() {
this.restService.getSomeBooks(5000).subscribe(buch => {
this.buecher = buch;
this.fetched = true;
})
}
After that - in HTML - i am iterating over that data:
<table>
<thead>some table-heads</thead>
<tbody>
<tr style="text-align: center" *ngFor="let buchforTable of buecher">
<td>{{buchforTable.author}}</td>
<td>{{buchforTable.erscheinungsdatum}}</td>
<td>{{buchforTable.isbn.toString()}}</td>
<td>{{buchforTable.verlag}}</td>
<td>{{buchforTable.uuid}}</td>
</tr>
</tbody>
</table>
The *ngFor-iteration should set a boolean variable whenever it is done.
As Alex Po already mentioned, trackBy is working. As you can see in the following snippet, with trackBy it is possible to handle events based on the current index of the iteration.
<tbody>
<tr style="text-align: center" *ngFor="let buchforTable of buecher; trackBy: trackByFn">
<td>{{buchforTable.author}}</td>
<td>{{buchforTable.erscheinungsdatum}}</td>
<td>{{buchforTable.isbn.toString()}}</td>
<td>{{buchforTable.verlag}}</td>
<td>{{buchforTable.uuid}}</td>
</tr>
</tbody>
To measure the time of the rendering process of all array-objects the trackBy-function would look like this (array contains 5000 objects here -> index 0 to 4999):
trackByFn(index){
if(index == 0)
this.renderStart = performance.now();
if(index == 4999) {
var renderStopp = performance.now();
var timeToRender = renderStopp - this.renderStart;
}
}

AngularJS and XML, how to render it?

I am working along DB guys, they are sending me the data thru XML, and depending the kind of element they specify is what I need to display in the view.
The code you will see is a dynamic table
<table>
<thead>
<tr>
<th ng-repeat="column in cols">
<span>{{column}}</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in cols"
ng-init="isXX = column.indexOf('XX') === 0">
<span ng-if="!isXX">{{row[column]}}</span>
<button ng-if="isXX" class="btn btn-xs btn-blue"
ng-click="fillOpen()">
{{column.substring(3).replace('_', ' ')}}
</button>
</td>
</tr>
</tbody>
</table>
and here is what I have in the controller
ReportsFactory.pendingBets(reportParam).then(function(data) {
if (data.length) {
gridInfo = _.forEach(data, function(item) {return item;});
$scope.rows = gridInfo;
$scope.cols = Object.keys($scope.rows[0]);
}
}
as you can see here I have this ng-init
ng-init="isXX = column.indexOf('XX') === 0" where I am telling the app, if the property I am receiving comes with XX at the index, then display a button <button ng-if="isXX" ng-click="fillOpen()">...</button> but so far, I have some more props coming with XX at the beginning, so I need to do it more dynamic.
This is how my view looks so far
what I need to know, is how to read that XML, this is the XML printed in the Nodejs terminal
[{ BET: 57635034,
CUSTOMER: 181645,
SPORT: 'NFL',
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>',
XX_VIEW: '<element><element_type>BASIC_DROPDOWN</element_type><element_call>callThisFunction()</element_call><element_content><li>1</li><li>2</li><li>3</li><li>4</li></element_content></element>',
XX_CANCEL: '<element><element_type>BASIC_CHECKBOX</element_type><element_call/><element_content>1</element_content></element>'
}]
so, the first says
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>'
WAGER_ACTION_BUTTON should be a button
the second one says
BASIC_DROPDOWN that should be a dropdown and so on, so, how should I do in order to display the proper HTML element depending on what the XML says ?
Any suggestions ?
if I understood you correctly you want to dynamically render the xml or html content to your view... I assume that element and element type are directive you have or something.
use
ngBindHtml
e.g:
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="question.question.body"></span>
</div>
or you might need to use the trustAsHtml function
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="trustAsHtml(question.question.body)"></span>
</div>
$scope.trustAsHtml = function (val) {
return $sce.trustAsHtml(val);
};
this will take your string xml (html) code and render it...
you could always build a personalize directive and use $compile as well like:
app.directive('ngHtmlCompile',function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.ngHtmlCompile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
and in the code just call the ng-html-compile... no need for $sce

Dynamic ng-click does not call function

I wish to make a dynamic table in AngularJS, but the problem is ng-click does not call the function.
Here is the fiddle : fiddle
Here is the code :
General template :
<div class="box">
<dynamic-table></dynamic-table>
</div>
Directive template :
<table class="table table-striped">
<thead>
<tr>
<th ng-repeat="column in columns" ng-bind="column.label"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="content in data">
<td ng-repeat="column in columns">
<!-- Problem is here (column[function] displays 'displayCategory') -->
<a href ng-click="column[function]()">{{ content[column.field] }}</a>
</td>
</tr>
</tbody>
</table>
Directive code :
app.directive('dynamicTable', function () {
return {
restrict: 'E',
templateUrl:'/template/Directive/dynamic-table.html',
scope: true,
link: ['$scope', function($scope) {
$scope.updateCategory = function() {
console.log("WOW");
};
}]
};
});
When I display : column[function], it shows updateCategory. I don't understand why when I click on it, the function is not launched...
Have you got an idea ?
That's because column[function] returns a string, not a reference to the function itself. You should call the function directly, like:
<td ng-repeat="column in columns">
<!-- Problem is here (column[function] displays 'displayCategory') -->
<a href ng-click="updateCategory (column)">{{ column.field }}</a>
</td>
and inside the directive to have something like:
controller: ['$scope', function($scope) {
$scope.updateCategory = function(columnData) {
console.log(columnData.field);
};
}]
Check demo: JSFiddle.
First of all, you link function declaration is not correct:
link: ['$scope', function($scope) {
$scope.updateCategory = function() {
console.log("WOW");
};
}]
It is the format of controller function. Change it to:
link: function($scope) { ... }
Angular will do the injection for you.
Secondly, specify a dispatcher function on the scope. Inside the dispatcher, determine which function to call:
$scope.dispatcher = function (column) {
var fn = column.function;
fn && angular.isFunction($scope[fn]) && $scope[fn]();
};
And specify ng-click="dispatcher(column)" in the HTML.
Please see this fiddle as maybe it will suit your needs.
http://jsfiddle.net/tep78g6w/45/
link:function(scope, element, attrs) {
scope.updateCategory = function() {
console.log("WOW");
};
scope.doSomething = function(func) {
var test = scope.$eval(func);
if(test)
test();
}
}
}
Also, link function has parameters that are sent to it, this is not a place to use DI. Please see in the fiddle the correct approach. As far as the dynamically calling the function, I went with different approach and it works. The approach you took is not going to work because you need a way for the string to be a function, it needs to have a reference to a function.

How to bind a ko.obersavableArray that is nested in an object

I have my knockout page hub, and I need a ko.obeservableArray nested in a ko.observable object, this is where I define them:
function IncomeDeclarationHub() {
//data comes from a ajax call.
self.myIncomeDeclarationViewModel = ko.observable(new IncomeDeclarationViewModel(data));
}
function IncomeDeclarationViewModel(data) {
var self = this;
self.retentionAmount = ko.observable();
self.taxableMonth = ko.observable();
self.incDecDetGroViewModels = ko.observableArray();
if (data != null) {
var arrayLenght = data.IncDecDetGroViewModels.length;
for (var i = 0; i < arrayLenght; i++) {
var myObject = new IncomeDecDetGroViewModel(data.IncDecDetGroViewModels[i]);
self.incDecDetGroViewModels.push(myObject);
}
}
}
And this is my HTML code:
<span class="label">
Retention Amount
</span>
<input data-bind="value: myIncomeDeclarationViewModel.retentionAmount" />
<table>
<tbody data-bind="foreach: myIncomeDeclarationViewModel.incDecDetGroViewModels">
...
</tbody>
</table>
Ok so the thing is that incDecDetGroViewModels never gets populated, I used to have that ko.obersableArray outside the object, and it worked fine, now that I inserted it in my object myIncomeDeclarationViewModel is not populating the html table. Do I need to call it in a different way at the data-bind
myIncomeDeclarationViewModel is an observable, so you have to unwrap it to access it's properties. Add parenthesis to unwrap it (access the observable's underlying value) like this:
<span class="label">
Retention Amount
</span>
<input data-bind="value: myIncomeDeclarationViewModel().retentionAmount" />
<table>
<tbody data-bind="foreach: myIncomeDeclarationViewModel().incDecDetGroViewModels">
...
</tbody>
</table>
Here's a working jsFiddle based on your example
JsFiddle
well previously you can access just becoz it is in scope but right now you done some nesting so you just need to some looping in your view part to get that .
Something like this may be :
<table data-bind="foreach:myIncomeDeclarationViewModel">
<tbody data-bind="foreach:$data.incDecDetGroViewModels">
...
</tbody>
</table>
You can also ContainerLess foreach if you looking for something different like :
<!-- ko foreach:myIncomeDeclarationViewModel -->
//your table code
<!--/ko-->
I hope this solves the riddle .

ng-class directive call executed twice

Simple html:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="customerSelectedClass(customer)">
<td>
{{customer.Name}}
</td>
</tr>
</table>
In my controller - two functions to select customer and return proper class to highlight a table row:
$scope.customerSelectedClass = function (customer) {
if (customer == $scope.selectedCustomer) {
console.log('returing info for ' + customer.Name);
return "info";
}
return "";
};
$scope.selectCustomer = function (customer) {
console.log('selecting ' + customer.Name);
$scope.selectedCustomer = customer;
}
I noticed that when I click on a customer link, customerSelectedClass function executes twice. selectCustomer function on ng-click directive executes once, as it should. Angular is only included once on the page. I wonder if this is a bug in Angular or something that I am doing wrong?
Behind the scenes, angular is setting up a $watch on the function that is resolving the class name. Because angular uses dirty checking to see if there has been a change, this method will be called twice during the $digest cycle. This is ok.
I would suggest that you don't add this code the the controller though, because if you are managing many css classes, you could be adding a lot of unnecessary code. Try something like this instead:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="{'info': customer == selectedCustomer}">
<td>
{{customer.Name}}
</td>
</tr>
</table>
Then, there is no need for a controller function customerSelectedClass. This will only add the info class if the right-hand side of the : resolves to true. And there is no problem resolving the correct customer in the ng-repeat.
Hope this helps.

Categories

Resources