Question

Here's a plunker:

http://plnkr.co/edit/hJsZFCBZhFT5rRo1lgSZ?p=preview

It's a simple directive that is supposed to show a given number of dots and highlight a subset of them based on the value, min, and max given, similar to a star rating. It works great when on its own (as you can see in the last row of the plunkr), but not in an ngRepeat. The problem is the ngClass on the directive's own ngRepeat seems to fail, even though the expression used in the ngClass seems to be evaluating correctly outside of the ngClass. I've been tearing my hair out over this for the last few hours, and I just can't figure it out.

Here's the actual code:

HTML:

<div ng-repeat="row in [1,2,3,4,5]">
  <match-dots value="{{ $index+1 }}" min="0" max="10">{{ isDotActive($index) }}</match-dots>
</div>

JS:

angular.module('app', [])
.directive('matchDots', function() {
    return {
        restrict: 'EA',
        replace: true,
        transclude: true,
        template:
            "<span class='match-dots-group'>" +
              "<span class='match-dots-dot' ng-repeat='dot in dotsList()' ng-class='{ active: isDotActive($index) }' ng-transclude></span>" +
            "</span>",
        scope: {
            dots: '@',
            min: '@',
            max: '@',
            value: '@'
        },
        link: function(scope, elem) {
            // save the previous class applied so we can remove it when the class changes
            var previousClass = '';

            /**
             * set the class for the dots-group based on how many dots are active (n-active)
             */
            scope.setDotsClass = function() {
                if(previousClass != '') {
                    elem.removeClass(previousClass);
                }
                previousClass = "active-"+scope.numActiveDots();
                elem.addClass(previousClass);
            }
        },
        controller: function($scope) {

            $scope.dots = angular.isUndefined($scope.dots)?5:parseInt($scope.dots);
            $scope.min = angular.isUndefined($scope.min)?0:parseInt($scope.min);
            $scope.max = angular.isUndefined($scope.max)?100:parseInt($scope.max);

            /**
             * create a list of numbers
             * @returns {Array}
             */
            $scope.dotsList = function() {
                var arr = [];
                for(var i=1; i<=$scope.dots; i++) {
                    arr.push(i);
                }
                return arr;
            };

            /**
             * should the given dot be lit up?
             * @param {number} dot
             * @returns {boolean}
             */
            $scope.isDotActive = function(dot) {
                return dot < $scope.numActiveDots();
            };

            /**
             * How many dots are active?
             * @returns {number}
             */
            $scope.numActiveDots = function() {
                var activeDots = Math.ceil(($scope.value-$scope.min) / (($scope.max-$scope.min) / $scope.dots));
                return isNaN(activeDots)?0:activeDots;
            };

            // make sure the value is an number
            $scope.$watch('value', function(newValue) {
                $scope.value = parseFloat(newValue);
                $scope.setDotsClass();
            });

            // make sure the number of dots is actually a positive integer
            $scope.$watch('dots', function(newDots) {
                if(angular.isUndefined(newDots) || !newDots || newDots < 1) {
                    $scope.dots = 5;
                } else {
                    $scope.dots = parseInt(newDots);
                }
                $scope.setDotsClass();
            });

            // make sure the min is not greater than the max
            $scope.$watch('max', function(newMax) {
                if(angular.isUndefined(newMax)) {
                    $scope.max = newMax = 100;
                }
                if(angular.isString(newMax)) {
                    $scope.max = parseFloat(newMax);
                } else if(newMax < $scope.min) {
                    $scope.max = $scope.min;
                    $scope.min = newMax;
                }
                $scope.setDotsClass();
            });

            // make sure the max is not less than the min
            $scope.$watch('min', function(newMin) {
                if(angular.isUndefined(newMin)) {
                    $scope.min = newMin = 0;
                }
                if(angular.isString(newMin)) {
                    $scope.min = parseFloat(newMin);
                } else if(newMin > $scope.max) {
                    $scope.min = $scope.max;
                    $scope.max = newMin;
                }
                $scope.setDotsClass();
            });
        }
    }
});
Was it helpful?

Solution

The first thing that looked suspicious to me is that you were reassigning the $scope.dots property and your numActiveDots function, quite often was ending up with NaN. That property is passed into your directive as a string, then you are parsing/reassigning it as number. I tried renaming your parsed value to $scope.dotsinternal. That seems to make it work.

http://plnkr.co/edit/STwuts2D169iBLDoTtE2?p=preview

Change this:

$scope.dots = angular.isUndefined($scope.dots)?5:parseInt($scope.dots);

to this:

$scope.dotsinternal = angular.isUndefined($scope.dots)?5:parseInt($scope.dots);

then updated all the references to $scope.dots to $scope.dotsinternal.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top