Статьи

Реализация биржевого тикера SignalR с использованием AngularJS: Part2


В
последнем посте мы реализовали
часть
SignalR примера биржевого тикера с использованием
AngularJS и изменили HTML-страницу, чтобы использовать директивы Angular. Как и было обещано ранее, мы завершим реализацию в этом посте, включив изменения пользовательского интерфейса, используя директивы и фильтры.

AngularJS разработан с учетом разделения интересов. Одна из лучших архитектурных особенностей AngularJS заключается в том, что она облегчает отделение HTML и JavaScript друг от друга, когда они играют вместе. Такие функции, как директивы и фильтры, можно использовать для простого расширения выражений Angular и поведения элементов HTML, не теряя много времени и энергии.


В исходном образце Stock Ticker при привязке данных на странице были сделаны следующие изменения:

  • Изменению стоимости акции предшествует символ вверх или вниз на основе значения
  • Процентное значение умножается на 100 и к нему добавляется символ «%».


Он также имеет следующие эффекты пользовательского интерфейса на странице:

  • jQuery цветовой эффект вспышки на рядах таблиц и элементов списка
  • Элемент списка продолжает прокручиваться, когда фондовая биржа открыта
  • Кнопки включены и отключены в зависимости от текущего состояния рынка


Создание фильтров для более эффективного представления данных

Давайте напишем пару фильтров для управления изменениями и процентами изменений при представлении на экране:

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);
        }
    }
});

Как видно из приведенного выше кода, реализация фильтров довольно проста. Значение, с которым должен действовать фильтр, передается в функцию фильтра. Все, что нам нужно сделать, это манипулировать значением по мере необходимости и возвращать его. Использование этих фильтров также очень просто — следующий фрагмент демонстрирует использование:

<td>
    {{stock.Change | change}}
</td>
<td>
    {{stock.PercentChange | percentage}}
</td>

Создание директив для улучшения пользовательского интерфейса

Кнопки с надписью «открытый рынок» и «сброс» должны быть включены, когда рынок закрыт, и кнопка «закрыть рынок» должна быть включена, когда рынок открыт. Мы можем смоделировать это поведение, используя логическое свойство marketIsOpen внутри выражения ( {{}} ) .

Чтобы оценить выражения внутри директивы, нам нуженсервис $ interpolate . Директива также должна следить за изменениями в значении, чтобы немедленно изменить состояние кнопки. Ниже приведена реализация директивы:

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();
        });
    }
});

При использовании директивы мы должны назначить ее с логическим выражением.

<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()" />

Чтобы указать на изменение стоимости акции, соответствующая строка в таблице должна мигать. Давайте напишем директиву для этого:

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 the same look and behavior.

This sample demonstrates how to make the best use of the features of AngularJS with SignalR to create real-time data-oriented applications. The beauty of this approach is that we have several independent components playing pretty well together. This approach makes it easier to organize the code and makes each component testable in isolation.

The 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!

Happy coding!