Search code examples
javascriptangularjscryptojs

Why would a hash computation using CryptoJS cause a $rootScope:infdig error in Angular?


I have a simple page that shows the hash of a string as someone types it into the page. I found that the page had a JavaScript error

Error: [$rootScope:infdig] http://errors.angularjs.org/1.2.26/$rootScope/infdig?p0=10&p1=%5B%5B%22sha1…75651%2C1080464653%2C-772792499%5D%2C%5C%22sigBytes%5C%22%3A20%7D%22%5D%5D

A very simplified version of the page is

<html lang="en">
<head>
    <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
    <script>
        function MyCtrl($scope) {
            $scope.sha1 = function(pwd) {
                return CryptoJS.SHA1(pwd);
            };
        }
    </script>
</head>
<body>
    <div class="app" ng-app ng-controller="MyCtrl">
        <span ng-bind="sha1('bar')"></span>
    </div>
</body>
</html>

which is available as Plunker http://plnkr.co/edit/vmBtH8B2EKsdcfZVGlMH.

What I am trying to do in the original page is recalculate the hash as someone types into the form field, and the input field definition looked like this

<input id="password" ng-model="password" type="text" placeholder="Password">

and the ng-bind is really ng-bind="sha1(password)", but the simple static case in the Plunker exhibits the same behavior.

I gather that the infdig error has to do with too many $digest cycles, but I don't see how that would happen here. It looks like the hash computation triggers the error, because returning a static string from the sha1 function causes no error.


Solution

  • Providing ng-bind="sha1('bar')" makes the digest cycle unstable, everytime sha1 function returns a different object (reference is different) and your digest cycle has to run again to stabilize it and every digest cycle again evaluates the ng-bind function expression and it goes on till it reaches the max limit set (10). You can also easily replicate this issue by just doing return [] in your scope method. This is just a side effect of not so good practice of binding a function expression to ng-bind as it runs every digest cycle, if at all used it should be carefully evaluated.

    One simple solution is to bind ng-change/ng-blur event on your password or any other trigger and just bind ng-bind to a property instead of a function expression.

    angular.module('app',[])
    .constant('Crypto', window.CryptoJS);
    function MyCtrl($scope, Crypto) {
       $scope.encrypt = function() {
         $scope.encrypted = Crypto.SHA1($scope.password);
       };
     }
    <html lang="en" ng-app="app">
    
    <head>
      <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha1.js"></script>
      <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
    
    </head>
    
    <body>
      <div class="app" ng-app ng-controller="MyCtrl">
        <input id="password" ng-model="password"  type="password" placeholder="Password" ng-change="encrypt()">
        <span ng-bind="encrypted"></span>
      </div>
    </body>
    
    </html>

    For better usage of DI i have placed crpto in a constant and inject it where needed.