Tuesday, 21 May 2013

Implementing SignalR Stock Ticker using Angular JS - Part2

In last post, we implemented SignalR part of the stock ticker sample using Angular JS and modified the HTML page to use Angular’s directives. As promised earlier, we will finish the implementation in this post by incorporating UI changes using directives and filters.

Angular JS is designed with separation of concerns in mind. One of the best architectural features of Angular JS is, it makes it easier to separate HTML and JavaScript separated from each other while playing together at the same time. Features like directives and filters can be used to easily extend Angular’s expressions and behaviour of HTML elements without wasting a lot of time and energy.

In the original Stock Ticker sample, following changes are made while binding data on the page:

  • Change in stock value is prepended with a up or down symbol based on value
  • Percentage value is multiplied with 100 and % symbol is appended to it

It also has the following UI effects on the page:

  • jQuery Colour flash effect on rows of table and list items
  • List item keeps scrolling when the stock market is open
  • Buttons are enabled and disabled based on current market state


Creating filters to represent data more effectively

Let’s write a couple of filters to manipulate change and percent change while presenting on the screen:

app.filter('percentage', function () {
    return function (changeFraction) {
        return (changeFraction * 100).toFixed(2) + "%";
    }
});
 
app.filter('change', function () {
    return function (changeAmount) {
        if (changeAmount > 0) {
            return "▲ " + changeAmount.toFixed(2);
        }
        else if (changeAmount < 0) {
            return "▼ " + changeAmount.toFixed(2);
        }
        else {
            return changeAmount.toFixed(2);
        }
    }
});

As you see the above code, implementation of the filters is pretty straight forward. Value on which the filter has to act is passed into the filter function. All we need to do is, manipulate the value as needed and return it. Using these filters is very easy as well, following snippet demonstrates the usage:
<td>
    {{stock.Change | change}}
</td>
<td>
    {{stock.PercentChange | percentage}}
</td>

Creating directives to make UI better

The buttons Open market and Reset have to be enabled when market is closed and close market button has to be enabled when market is opened. We can simulate this behaviour using the Boolean property marketIsOpen inside expression ({{ }}).

To evaluate expressions inside a directive, we need $interpolate service. The directive also needs to watch for change of the value to immediately change state of the button. Following is the implementation of the directive:

app.directive('disable', function ($interpolate) {
    return function (scope, elem, attrs) {
        var exp = $interpolate(elem.attr('data-disable'));
        function updateDisabled() {
            var val = scope.$eval(exp);
            if (val == "true") {
                elem[0].disabled = 'disabled';
            }
            else {
                elem[0].disabled = '';
            }
        }
 
        scope.$watch(exp, function (value) {
            updateDisabled();
        });
    }
});

While using the directive, we should assign it with a Boolean expression.
<input type="button" id="open" value="Open Market" data-disable="{{marketIsOpen}}"
            data-ng-click="openMarket()" />
<input type="button" id="close" value="Close Market" data-disable="{{!marketIsOpen}}"
            data-ng-click="closeMarket()" />

To indicate change of a stock value, the corresponding row in the table should flash. Let’s write a directive to do this:
app.directive('flash', function ($) {
    return function (scope, elem, attrs) {
        var flag = elem.attr('data-flash');
        var $elem = $(elem);
 
        function flashRow() {
            var value = scope.stock.LastChange;
            var changeStatus = scope.$eval(flag);
            if (changeStatus) {
                var bg = value === 0
                            ? '255,216,0' // yellow
                            : value > 0
                            ? '154,240,117' // green
                            : '255,148,148'; // red
 
                $elem.flash(bg, 1000);
            }
        }
 
        scope.$watch(flag, function (value) {
            flashRow();
        });
    }
});

The directive can be applied on any element that needs the scroll effect. Following is a sample table row using this directive:
<tr data-ng-repeat="stock in stocks" data-flash="marketIsOpen">
 ...
 ...
</tr>

The final directive that we have to create will be used to make the stock list scrollable. Again, values will keep scrolling when the market is opened. Following is the implementation:
app.directive('scrollTicker', function ($) {
    return function (scope, elem, attrs) {
        var $scrollTickerUI = $(elem);
        var flag = elem.attr('data-scroll-ticker');
        scroll();
 
        function scroll() {
            if (scope.$eval(flag)) {
                var w = $scrollTickerUI.width();
                $scrollTickerUI.css({ marginLeft: w });
                $scrollTickerUI.animate({ marginLeft: -w }, 15000, 'linear', scroll);
            }
            else
                $scrollTickerUI.stop();
        }
 
        scope.$watch(flag, function (value) {
            scroll();
        });
    }
});

It is applied on the list as:
<ul data-scroll-ticker="marketIsOpen">
 ...
 ...
</ul>

Now open the new scroll ticker page on a browser. Open the original jQuery based implementation on another browser window and play with both the screens. Now both of them have same look and behaviour.


This sample demonstrates how to make best use of features of Angular JS with SignalR to create real-time data oriented applications. The beauty of this approach is, we have several independent components playing pretty well together. This approach eases organization of code and makes each component testable in isolation.

 Code covered in this series is available on Github. Feel free to fork the code if you think that any part of it can be improved!!


Note: If you have finished reading this series and want to use your learning in a project, make a note to check this post as well: A Better Way of Using ASP.NET SignalR With Angular JS. GitHub repo for the series contains a controller and a service that fulfill the same purpose in a better way, make sure to check the code

Happy coding!

No comments:

Post a Comment