How can I make the click event inside the ng-repeat only re-render the affected element (i.e. not run $ apply)?
My example code (below) has two problems:
-
the "renderthis" function is called twice on an element on simple page load
-
The main problem is when the button is clicked, I am assuming that after eval'ing the doit () expressions in the scope, ng-click calls $ applications are applied, which results in two DOM elements for each element being overwritten, rendered .. now I understand that the scope for each item here is somehow tied to the parent scope, or tied to it by some weird isolation or transexclusion or something I still have to master .. is there a way to make ng -click only call $ digest in content areas for each element or something like that?
here is my code:
HTML:
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in items">
<div>
<span>{{item.title}}</span>
<span>{{renderthis()}}</span>
<span>{{item.number}}</span>
<button ng-click="doit()">CLIQUE!</button>
</div>
</li>
</ul>
</body>
JS:
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.doit = function(){
console.log('doing it for item with title --> ', this.item.title);
}
$scope.renderthis = function(){
console.log('rendering this for item with title -->', this.item.title);
return '|';
}
$scope.items = [{title: 'hello', number: 1}, {title: 'shalom', number: 42}];
});
Or check out this plnkr:
source to share
After some ping-pong with @Mark regarding this (see below) it was suggested to rephrase the question to something like this: "How can I make a click event inside an ng-repeat scope only re-accept the affected element (i.e. not fire $ apply) " . This is to emphasize, as Mark explained, that there isn't much to get around by using "ng-click" as it will hard-coded "$ apply" and that starts the digest loop and so on. (see below)
I'll settle for my own answer, which includes my own version of ng-click that uses $ digest instead of $ apply and therefore does not cause all ng-repeat'ed elements to be re-rendered.
Here is the edited ng-click (xng-click) code:
var xngEventDirectives = {};
angular.forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup'.split(' '),
function(name) {
var directiveName = directiveNormalize('xng-' + name);
xngEventDirectives[directiveName] = ['$parse', function($parse) {
return function(scope, element, attr) {
var fn = $parse(attr[directiveName]);
element.bind(name.toLowerCase(), function(event) {
fn(scope, {$event:event});
scope.$digest();
});
};
}];
}
);
And here is plnkr for demonstration:
source to share
Is there a way to make ng-click only call $ digest in the child area for each element or something?
Not. To achieve this functionality, you need to create your own directive.
ng-click starts the $ digest loop (see Runtime section), which will (among other things) check all clocks. Each set of {{}} s in the view sets $ watch. This is why you see log () s from renderthis function when pressed.
I don't think anything has been re-rendered unless the clock detects a change.
Regarding "twice called on page load":
(Since watchExpression can execute multiple times per $ digest cycle when a change is detected, be prepared for multiple calls to your listener.) - Scope # $ watch API doc
@Artem has a nice explanation of the Angular digest loop and dirty file checking . To summarize, because Angular found something that changed in the first $ digest loop, it runs another one to make sure all models are stabilized (not changed).
source to share
You can try ngp-local-click instead of ng-click -> https://github.com/ansukla/ng-perf
source to share