Angular: infinite digest loop in filter

前端 未结 3 1976
别跟我提以往
别跟我提以往 2020-12-11 06:05

I\'m writing a custom Angular filter that randomly capitalizes the input passed to it.

Here\'s the code:

angular.module(\'textFilters\', []).filter(\         


        
相关标签:
3条回答
  • 2020-12-11 06:41

    The problem is that the filter will produce a new result every time it is called, and Angular will call it more than once to ensure that the value is done changing, which it never is. For example, if you use the uppercase filter on the word 'stuff' then the result is 'STUFF'. When Angular calls the filter again, the result is 'STUFF' again, so the digest cycle can end. Contrast that with a filter that returns Math.random(), for example.

    The technical solution is to apply the transformation in the controller rather than in the view. However, I do prefer to transform data in the view with filters, even if the filter applies an unstable transformation (returns differently each time) like yours.

    In most cases, an unstable filter can be fixed by memoizing the filter function. Underscore and lodash have a memoize function included. You would just wrap that around the filter function like this:

    .filter('myFilter', function() {
      return _memoize(function(input) {
        // your filter logic
        return result;
      });
    });
    
    0 讨论(0)
  • 2020-12-11 06:46

    Since digest will continue to run until consistent state of the model will be reached or 10 iterations will run, you need your own algorithm to generate pseudo-random numbers that will return the same numbers for the same strings in order to avoid infinite digest loop. It will be good if algorithm will use character value, character position and some configurable seed to generate numbers. Avoid using date/time parameters in such algorithm. Here is one of possible solutions:

    HTML

    <h1>{{ 'Hello Plunker!' | goBananas:17 }}</h1> 
    

    JavaScript

    angular.module('textFilters', []).
      filter('goBananas', function() {
        return function(input, seed) {
          seed = seed || 1;
          (input = input.split('')).forEach(function(c, i, arr) {
            arr[i] = c[(c.charCodeAt(0) + i + Math.round(seed / 3)) % 2 ? 'toUpperCase' : 'toLowerCase']();
          });
          return input.join('');
        }
      });
    

    You can play with seed parameter to change a bit an algorithm. For example it may be $index of ngRepeat

    Plunker: http://plnkr.co/edit/oBSGQjVZjhaIMWNrPXRh?p=preview

    0 讨论(0)
  • 2020-12-11 07:04

    An alternative, if you want the behaviour to be truly random, is to do deal with the randomness only once during linking by creating a seed, and then use a seeded random number generator in the actual filter:

    angular.module('textFilters', []).filter('goBananas', function() {
      var seed = Math.random()
      var rnd = function () {
        var x = Math.sin(seed++) * 10000;
        return x - Math.floor(x);
      }
    
      return function(input) {
    
        var str = input;
        var strlen = str.length;
    
        while(strlen--) if(Math.round(rnd())) {
          str = str.substr(0,strlen) + str.charAt(strlen).toUpperCase() + str.substr(strlen+1);
        }
    
        return str;
      };
    });
    
    0 讨论(0)
提交回复
热议问题