Search code examples
javascriptangularjsangularjs-directiveng-class

AngularJS directive - ng-class in ng- repeat should it be a $watcher to toggle style?


I am currently implementing a spike to further my understanding on angular directives etc.

The premise is to create a FX watch list on a number of currency pairs.

My data feed is set up for my price updates via socket.io.

The stumbling block that i have is being able to change the css dependent on price change ie up arrow for up, down arrow for down.

I feel a watcher function is what i need but struggled on where to start so was looking for some sort of expression in ng-class to do the job ... but the method not only started to look like a $watcher it was also flawed as saving the previous price to scope on my directive meant there was only ever one old value not one for each price.

There for my question is : Is the solution with ng-class or in setting up a $watcher function ?

Heres my code ...

HTML template

<div ng-repeat="rate in rates" ng-click="symbolSelected(rate)">
        <div class="col-1-4">
            {{rate.symbol}}
        </div>
        <div class="col-1-4">
            <span ng-class='bullBear(rate.bidPoint)' ></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
        </div>

        <div class="col-1-4">
            <span ng-class='bullBear(rate.offerPoint)' ></span> {{rate.offerBig}}<span class="point">{{rate.offerPoint}}</span>
        </div>

        <div class="col-1-4">
            {{rate.timeStamp | date : 'hh:mm:ss'}}
        </div>

    </div>

My directive currently looks like this ... as noted this will not work and the bullBear method was starting to look like a $watcher function.

.directive('fxmarketWatch', function(fxMarketWatchPriceService){

        return {

            restrict:'E',
            replace:'true',
            scope: { },

            templateUrl:'common/directives/fxMarketWatch/marketwatch.tpl.html',

            controller : function($scope, SYMBOL_SELECTED_EVT,fxMarketWatchPriceService){

                $scope.symbolSelected = function(currency){
                    $scope.$emit(SYMBOL_SELECTED_EVT,currency);
                }

                $scope.bullBear = function(newPrice){


                    if ($scope.oldPrice> newPrice ){

                           return ['glyphicon glyphicon-arrow-down','priceDown'];
                     }
                     else if ($scope.oldPrice > newPrice ){

                           return ['glyphicon glyphicon-arrow-up','priceUp'];
                     }

                }


                $scope.$on('socket:fxPriceUpdate', function(event, data) {

                    $scope.rates  =  data.payload;

                });
            }

        }

    })

Solution

  • I will recommend you to use both ng-class and $watcher. The two can actually compliment each other:

    UPDATE: To make the code works with ng-repeat, we need to migrate all of CSS classes logic to another controller:

    app.controller('PriceController', function($scope) {
        // we first start off as neither up or down
        $scope.cssBid = 'glyphicon';
        $scope.cssOffer = 'glyphicon';
    
        var cssSetter = function(newVal, oldVal, varName) {
            if (angular.isDefined(oldVal) && angular.isDefined(newVal)) {
                if (oldVal > newVal) {
                    $scope[varName] = 'glyphicon glyphicon-arrow-down priceDown';
                } else if (newVal > oldVal) {
                    $scope[varName] = 'glyphicon glyphicon-arrow-up priceUp';
                } else {
                    $scope[varName] = 'glyphicon';
                }
            }
        };
    
        // watch for change in 'rate.bidPoint'
        $scope.$watch('rate.bidPoint', function(newVal, oldVal) {
            cssSetter(newVal, oldVal, 'cssBid');
        });
        // watch for change in 'rate.offerPoint'
        $scope.$watch('rate.offerPoint', function(newVal, oldVal) {
            cssSetter(newVal, oldVal, 'cssOffer');
        });
    });
    

    Next, we bind this PriceController onto ng-repeat div. By doing so, Angular will create one controller instance for each rate in rates. So this time rate.bidPoint and rate.offerPoint should be available for $watch-ing:

    <div ng-repeat="rate in rates" ng-click="symbolSelected(rate)" ng-controller="PriceController">
        <div class="col-1-4">
            <span ng-class='cssBid'></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
        </div>
    
        <div class="col-1-4">
            <span ng-class='cssOffer'></span> {{rate.offerBig}}<span class="point">{{rate.offerPoint}}</span>
        </div>
    </div>
    

    Now, directive's controller will be much shorter than before:

    controller: function($scope, SYMBOL_SELECTED_EVT, fxMarketWatchPriceService){
        $scope.symbolSelected = function(currency) {
            $scope.$emit(SYMBOL_SELECTED_EVT, currency);
        }
    
        $scope.$on('socket:fxPriceUpdate', function(event, data) {
            $scope.rates = data.payload;
        });
    }