How can I make a click event inside ng-repeat only re-render the affected item (i.e., not run $apply)?

My sample code (down there) raises two issues:

  1. the "renderthis" function is called twice per-item when simply loading the page

  2. the main issue - when clicking the button I assume that after eval'ing the doit() expression, ng-click calls $apply on the scope, which causes the two DOM elements for each item to be re-rendered.. now, i understand that the scope for each item here is somehow bound to the parent scope or related to it by some weird isolation or transclusion or whatnot which are things i have yet to master.. is there a way to make ng-click only call $digest on the child scope of each item or something of this sort?

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 see this plnkr:

http://plnkr.co/edit/VkR82lbJ0Mi2z12RqBfW

After some ping-pong with @Mark regarding this (see below) it was suggested that I rephrase the question to something of this sort: "How can I make a click event inside an ng-repeat scope only re-render the affected item (i.e., not run $apply) " This is to emphasize, as Mark explained, that it's not so much possible to work around using "ng-click" itself since it issues a hard-coded "$apply" and this runs a digest loop etc.. (see more below)

I will accept my own answer which includes my own version of ng-click that uses $digest instead of $apply and thus does not trigger a re-rendering of all ng-repeat'ed items.

Here is the revised ng-click code (xng-click):

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 a plnkr to demo it:

http://plnkr.co/edit/3i7TCEkBtxCFw2ESeyaU.

is there a way to make ng-click only call $digest on the child scope of each item or something of this sort?

No. To achieve this functionality, you'd have to create your own directive.

ng-click triggers a $digest loop (see section "Runtime") which will (among other things) examine all $watches. Each set of {{}}s in a view sets up a $watch. This is why when you click you see the log()s from the renderthis function.

I don't think anything is re-rendered unless the $watches detect a change.

Regarding the "called twice per-item when the page loads":

(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 about Angular's digest cycle and dirty checking. To summarize, because Angular found something that changed on the first $digest cycle, it runs another one to make sure all models have stabilized (not changed).

You can try ngp-local-click instead of ng-click -> https://github.com/ansukla/ng-perf