0

In my view I want to display a computed property multiple times, if I do {{ctrl.Compute()}} multiple times, then compute function get called multiple times.

I have created this plunkr demo to simply my question http://plnkr.co/edit/TcMcJUipLKk94dthnivU

Controller

app.controller('MainController',function(){
  var ctrl= this;
  var list = [];

  ctrl.count = function(){
    console.log('Invoked');
    return list.length
  }


  ctrl.add = function(){
    list.push(1)
  }

});

View

 <body ng-controller="MainController as ctrl">
    <button ng-click="ctrl.add()">Add</button>
    <br>
    List Size: {{ctrl.count()}} <br>
    List Size: {{ctrl.count()}} <br>
    List Size: {{ctrl.count()}} <br>
    List Size: {{ctrl.count()}} <br>
  </body>

In view, you can see I am calling {{ctrl.count()}} four times and that means computation is happening four times. How can I do computation only once and display a value multiple times.?


Please don't suggest, ideas like, make array part of controller like ctrl.list, then in view use {{ctrl.list.length}}. This idea might for this eg but wont work where a complex computation is required.

Praveen Prasad
  • 31,561
  • 18
  • 73
  • 106
  • 1
    set the counter as variable in controller `$scope.arraylength = ctrl.count()`and display it as usual. Then you can call the count() function using watcher or on specific actions, depending on your use case... – Karl Adler Sep 21 '15 at 10:00
  • `computation is happening four times` No i think it just call the function 4 times. I think `js` not going to calculate it. its a property of the array object which is prototypically inherit from the `Array`. correct me if i wrong :). https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length – Kalhan.Toress Sep 21 '15 at 10:03
  • As i said for this demo `count ()` is simple returning length of array, but in real app I want to perform a complex computation in that function. If from from views i call a function multiple times computation would happen multiple times. – Praveen Prasad Sep 21 '15 at 10:04
  • @abimelex: I dont want to use it that way, still I tried your code but it doesn't work – Praveen Prasad Sep 21 '15 at 10:09
  • @PraveenPrasad. have you found any solutions? – Ramesh Rajendran Sep 21 '15 at 11:07
  • @ramesh i might use memorization technique – Praveen Prasad Sep 21 '15 at 11:15

4 Answers4

0

Maybe something like this:

var savedCount = ctrl.count();
ctrl.count = function(){
    console.log('Invoked');
    return list.length
}

$scope.$watch("list", function() {savedCount = ctrl.count()});
tdebroc
  • 1,436
  • 13
  • 28
0

Check Plunker. Use a new variable on the scope like:

ctrl.counter = ctrl.count();
$scope.$watch(function () {
  return list;
}, function () {
  ctrl.counter = ctrl.count();
}, true);

On the HTML:

List Size: {{ctrl.counter}} <br>
List Size: {{ctrl.counter}} <br>
List Size: {{ctrl.counter}} <br>
List Size: {{ctrl.counter}} <br>

The $watch function watches every change of the value list, and call the callback function every time.

Joy
  • 9,430
  • 11
  • 44
  • 95
0

You can use a technique called memoization.

In computing, memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

You can use a popular library like lodash or write it on your own (not encouraged).

You can find a usage example similar to your use case here talking about filters that are calculation heavy, similar to using binding to calculation functions.

masimplo
  • 3,674
  • 2
  • 30
  • 47
  • yes I was also thinking of using memoization, but I am sure there must be some simple solution to this. – Praveen Prasad Sep 21 '15 at 10:13
  • I have used it in quite a few cases and find it straight forward to implement in contrast to playing with watchers that you have to be awfully careful about their performance, when they are fired, if they are going to cause a chain reaction etc. – masimplo Sep 21 '15 at 10:29
  • I have an intuition that $watchers are not efficient, may be because i read it somewhere. That's why i was planning to use memorization. Do you also mean that $watcher are inefficient? – Praveen Prasad Sep 21 '15 at 11:13
  • This is a long subject, but long story short, ALL active watchers are called in every digest cycle, so if you have a heavy calculation in even one of them, it can degrade the whole application performance. Watchers are to be used when needed but a lot of care must be taken as it is not always apparent how ofter they are called or what toll they will take in the digest cycle. Bad usage of watchers is one of the reasons Angular2 switched to observables instead of dirty checking – masimplo Sep 21 '15 at 15:00
-1

You can do it by using bindHtmlUnsafe directive, and also Your count object is should be a $scope object, then it's possible. please just copy paste my answer.

    app.controller('MainController',function($scope){
     $scope.list = [];  

      $scope.context = '<br/> List Size: ' +
       $scope.list.length + ' <br/>List Size: ' + $scope.list.length + 
      '<br/>List Size: ' + $scope.list.length + ' <br/> List Size: ' + 
        $scope.list.length + ' <br/>';

     $scope.add = function () {
        $scope.list.push(1);
        $scope.context = '<br/> List Size: ' + $scope.list.length + ' <br/>List  
        Size: ' + $scope.list.length + '<br/>List Size: ' + $scope.list.length + 
       '<br/> List Size: ' + $scope.list.length + ' <br/>';
    }    
  });

app.directive('bindHtmlUnsafe', function ($compile) {
    return function ($scope, $element, $attrs) {

        var compile = function (newHTML) { // Create re-useable compile function
            newHTML = $compile(newHTML)($scope); // Compile html
            $element.html('').append(newHTML); // Clear and append it
        };

        var htmlName = $attrs.bindHtmlUnsafe; // Get the name of the variable 
        // Where the HTML is stored

        $scope.$watch(htmlName, function (newHTML) { // Watch for changes to 
            // the HTML
            if (!newHTML) return;
            compile(newHTML);   // Compile it
        });

    };
});

then

<body ng-controller="MainController">
      <button ng-click="add()">Add</button>
            <div bind-html-unsafe="context"></div>
  </body>

This is the one optional solution for your question :)

please see in Plunker how can i solve this issue with your angular version codes.

Ramesh Rajendran
  • 37,412
  • 45
  • 153
  • 234
  • keep in mind, that html-unsafe is called like this because it's not safe. – Karl Adler Sep 21 '15 at 10:54
  • You are trying to solve this particular problem, which is not my concern. I want to learn a concept, which your solution is far away from. I still appreciate your efforts. – Praveen Prasad Sep 21 '15 at 11:09