简体   繁体   中英

Using a watch inside a link is causing an infinite digest cycle.

I'm trying to write a directive that associates a score with a color.

I've made an attempt already, and the Plunker is here . The directive itself is here:

.directive('scorebox', function () {
    function link ($scope, $elem, $attr) {
        var one = 1;
        $scope.$watch('[score,ptsPossible]', function (newValue) {
            pctScore = newValue[0] / newValue[1]
            if (pctScore <= 0.4) {
                rating = 'low';
            } else if (pctScore <= 0.6) {
                rating = 'med';
            } else if (pctScore <= 0.8) {
                rating = 'high';
            } else if (pctScore == 1) {
                rating = 'perfect';
            }

            $elem.removeClass();
            $elem.addClass('scorebox');
            $elem.addClass(rating);
            $elem.text(newValue[0] + "/" + newValue[1]);
        });
    };

    return {
        restrict: 'E',
        scope: {
            score: "=",
            ptsPossible: "="
        },
        link:link 
    }
}) 

I've got a couple of problems.

  1. First, it's pretty obvious to me that I'm not supposed to do a $watch inside a link function. I'm creating an infinite digest cycle, and that's not good. I'm still not sure why, though.
  2. I'm not manipulating the DOM correctly. Even though I'm calling $elem.removeClass() , it's not working--the element retains any classes it had before.

What is the right way to do this?

Just a suggestion to get things working:

  1. There's no need to $watch both score and ptsPossible since the latter never changes after the value is loaded from its corresponding attribute value. You also have access to scope variables inside the $watch callback function.
  2. That's unusual as I would've expected your removeClass() to work as well. You could instead try removeAttr('class') here in the meanwhile.

Here's a Plunker with the suggested changes.

You need to use $watchCollection and not $watch . You are passing in an array to $watch which is how $watchCollection expects.

  1. As @miqid said, no need to $watch both score and ptsPossible, since you only want to react when score changes (at least in this situation you are presenting).

  2. The problem here, is you are using jqLite's removeClass function instead of jQuery's . If jQuery is not included before Angular in the code, Angular will instead use jqLite functions, which is like a smaller, much simpler version of jQuery. It is also, slightly different. jQuery's removeClass() , will remove all classes is no parameter is passed. jqLite will not do the same, it will just remove those classes that you pass as parameter.

You never included jQuery at all, so that's what's happening. Here is the edited Plunker . You can check jQuery is now included in the top, and everything works as expected. And also the $watch is much simpler.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM