1

I have the following setup

HTML:

<span star score="{{resto.rating}}"></span>

A controller that downloads data and sets it in resto and the directive below. My problem is that the link function is being called before the score expression has been interpolated, so I only ever get 1 star.

angular.module 'afmnewApp'
.directive 'star', () ->
    return {
        scope: 
            score: '@'
        template : """
            <ul class="rating">
                <li ng-repeat="star in stars" ng-class="star">
                    *
                </li>
            </ul>
        """,
        link : (scope, elem, attrs) ->
            scope.stars = []
            for x in [1..scope.score]       # should it be attrs?
                scope.stars.push({'fill'})
    }
Simon H
  • 20,332
  • 14
  • 71
  • 128
  • a call to $scope.$digest() can udpate the view, you might need to call this function after you download the data – sss Sep 08 '14 at 08:14
  • Thanks, but that didn't work - I got an error to the effect that a $digest was already underway – Simon H Sep 08 '14 at 08:16
  • as a bad workaround (can be use for dev purposes), you can test with $scope.$$phase or use $evalAsync... – sss Sep 08 '14 at 08:20

1 Answers1

1

I had a similar issue on Friday :-). I managed to find my own solution. In your case it would be :

angular.module 'afmnewApp'
.directive 'star', () ->
    return {
        template : """
            <ul class="rating">
                <li ng-repeat="star in stars" ng-class="star">
                    *
                </li>
            </ul>
        """,
        scope:{ 
            score: '@score'
        },
        link: function (scope, element, attrs) {
            scope.$watch('score', function (newValue, oldValue) {
                if (newValue !== undefined) {
                    scope.stars = []
                    for x in [1..scope.score]
                        scope.stars.push({'fill'})
                }
            });
        }
    };
});

So in the link you add a watch on your variable, so when it changes the watch will fire populating your array.

Edit based on Jesus comment below:

I checked about the difference between $watch and $observe. Here is a nice SO post describing it. It seems that for checking attribute changes in DOM objects (as in your case the score attribute of your span object) you should use $observe. For anything else you should use $watch. So in this case the solution should better be:

angular.module 'afmnewApp'
.directive 'star', () ->
    return {
        template : """
            <ul class="rating">
                <li ng-repeat="star in stars" ng-class="star">
                    *
                </li>
            </ul>
        """,
        scope:{ 
            score: '@score'
        },
        link: function (scope, element, attrs) {
            attrs.$observe('score', function (newValue, oldValue) {
                if (newValue !== undefined) {
                    scope.stars = []
                    for x in [1..scope.score]
                        scope.stars.push({'fill'})
                }
            });
        }
    };
});
Community
  • 1
  • 1
Giannis Paraskevopoulos
  • 18,261
  • 1
  • 49
  • 69
  • Even when your answer is technically correct, I suggest using an $observer instead of $watch here. – Jesus Rodriguez Sep 08 '14 at 08:52
  • I have to admit that my Angular dev experience is little. I will check out the $observer and update accordingly. – Giannis Paraskevopoulos Sep 08 '14 at 08:56
  • @JesusRodriguez Thanks for your comment. I did some research and have updated my answer. – Giannis Paraskevopoulos Sep 08 '14 at 11:57
  • I marked this as the accepted answer because it does the job, but I can't help thinking there must be a better way, and that this must be a common enough situation to warrant the angular team ensuring better ordering of compilation? So I have unmarked 'accepted' for the time being – Simon H Sep 09 '14 at 13:44