Статьи

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


В моем
последнем посте мы увидели, как использовать
ASP.NET SignalR и
AngularJS вместе на примере типа «Hello World». Теперь, когда у нас есть некоторое представление о том, как использовать обе технологии вместе, мы рассмотрим более сложный сценарий, чтобы заставить фреймворки работать лучше вместе.


Я предполагаю, что вы уже видели образец тикера SignalR Stock . Если нет, загрузите его с GitHub или добавьте пакет NuGet в существующее веб-приложение ASP.NET. Убедитесь, что вы потратили некоторое время, чтобы запустить образец хотя бы один раз в нескольких браузерах и взгляните на код, прежде чем продолжить.

Я надеюсь, что вы ознакомились с кодом как на серверной, так и на клиентской стороне примера биржевого тикера. Мы не будем вносить никаких изменений в код сервера и макет страницы, но мы перепишем часть JavaScript, используя возможности AngularJS. Поскольку существует много кода для преобразования на стороне клиента, давайте сделаем это в серии из двух частей:

  1. Создание пользовательского сервиса для связи с концентратором на сервере и использование сервиса в контроллере (этот пост)
  2. Выполнение изменений пользовательского интерфейса на странице, таких как включение / отключение кнопок, прокрутка значений запасов в списке и добавление эффекта анимации к значениям в таблице и списке (следующий пост)


Сделайте копию файла StockTicker.html и дайте ему имя по вашему выбору. Добавьте в проект два файла JavaScript, controller.js и factory.js. Мы скоро добавим скрипт в эти файлы. Измените справочный раздел сценариев на странице, включив в него следующие файлы сценариев:


<script src="../../bundles/jquery"></script>
<script src="../../bundles/jquerycolor"></script>
<script src="../../Scripts/jquery.signalR-1.0.1.js"></script>
     
<script type="text/javascript" src="../../Scripts/angular.js"></script>
<script type="text/javascript" src="factory.js"></script>
<script type="text/javascript" src="controller.js"></script>

Давайте начнем реализовывать часть SignalR внутри пользовательского сервиса. Чтобы освободить контроллер от каких-либо действий, кроме предоставления данных для представления и обработки событий представления, мы создаем пользовательский сервис для обработки логики связи концентратора. Сервис отвечает за:

  1. Создание объектов, необходимых для общения
  2. Настройка клиентских функций для прокси-сервера и реагирования при открытии, закрытии или сбросе рынка или при обновлении стоимости акций
  3. Запуск соединения с концентратором
  4. Получение текущих значений акций и текущих рыночных статусов акций после начала подключения
  5. Открытие, закрытие или сброс рынков по требованию

Ниже приведены модуль и фабрика, которая обрабатывает функции, упомянутые выше:

var app = angular.module('app', []);
app.value('$', $);
app.factory('stockTickerData', ['$', '$rootScope', function ($, $rootScope) {
    function stockTickerOperations() {
        //Objects needed for SignalR
        var connection;
        var proxy;
  
        //To set values to fields in the controller
        var setMarketState;
        var setValues;
        var updateStocks;
  
 //This function will be called by controller to set callback functions
        var setCallbacks = function (setMarketStateCallback, setValuesCallback, updateStocksCallback) {
            setMarketState = setMarketStateCallback;
            setValues = setValuesCallback;
            updateStocks = updateStocksCallback;
        };
  
        var initializeClient = function () {
            //Creating connection and proxy objects
            connection = $.hubConnection();
            proxy = connection.createHubProxy('stockTicker');
  
            configureProxyClientFunctions();
  
            start();
        };
  
        var configureProxyClientFunctions = function () {
            proxy.on('marketOpened', function () {
                //set market state as open
                $rootScope.$apply(setMarketState(true));
            });
  
            proxy.on('marketClosed', function () {
                //set market state as closed
                $rootScope.$apply(setMarketState(false));
            });
  
            proxy.on('marketReset', function () {
                //Reset stock values
                initializeStockMarket();
            });
  
            proxy.on('updateStockPrice', function (stock) {
                $rootScope.$apply(updateStocks(stock));
            });
        };
  
        var initializeStockMarket = function () {
            //Getting values of stocks from the hub and setting it to controllers field
            proxy.invoke('getAllStocks').done(function (data) {
                $rootScope.$apply(setValues(data));
            }).pipe(function () {
                //Setting market state to field in controller based on the current state
                proxy.invoke('getMarketState').done(function (state) {
                    if (state == 'Open')
                        $rootScope.$apply(setMarketState(true));
                    else
                        $rootScope.$apply(setMarketState(false));
                });
            });
        };
  
        var start = function () {
            //Starting the connection and initializing market
            connection.start().pipe(function () {
                initializeStockMarket();
            });
        };
  
        var openMarket = function () {
            proxy.invoke('openMarket');
        };
  
        var closeMarket = function () {
            proxy.invoke('closeMarket');
        };
  
        var reset = function () {
            proxy.invoke('reset');
        };
  
        return {
            initializeClient: initializeClient,
            openMarket: openMarket,
            closeMarket: closeMarket,
            reset: reset,
            setCallbacks: setCallbacks
        }
    };
  
    return stockTickerOperations;
} ]);

Нам нужен контроллер, чтобы начать работу. Контроллер будет иметь следующие компоненты:

  1. Массив для хранения текущих значений запасов и логическое значение для хранения текущего состояния рынка
  2. Сеттеры для присвоения значений полям 
  3. Функция для изменения значения записи в массиве запасов
  4. Функции для обработки операций открытия, закрытия и сброса при нажатии соответствующей кнопки
  5. Установите обратные вызовы для службы и попросите службу отключить связь


Ниже приведен код в контроллере:

var StockTickerCtrl = function ($scope, stockTickerData) {
  
    $scope.stocks = [];
    $scope.marketIsOpen = false;
  
    $scope.openMarket = function () {
        ops.openMarket();
    }
  
    $scope.closeMarket = function () {
        ops.closeMarket();
    }
  
    $scope.reset = function () {
        ops.reset();
    }
  
    function assignStocks(stocks) {
        $scope.stocks = stocks;
    }
  
    function replaceStock(stock) {
        for (var count = 0; count < $scope.stocks.length; count++) {
            if ($scope.stocks[count].Symbol == stock.Symbol) {
                $scope.stocks[count] = stock;
            }
        }
    }
  
    function setMarketState(isOpen) {
        $scope.marketIsOpen = isOpen;
    }
  
    var ops = stockTickerData();
    ops.setCallbacks(setMarketState, assignStocks, replaceStock);
    ops.initializeClient();
}

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

<div data-ng-app="app" data-ng-controller="StockTickerCtrl">
        <h1>
            ASP.NET SignalR Stock Ticker Sample</h1>
        <input type="button" id="open" value="Open Market" data-ng-click="openMarket()" />
        <input type="button" id="close" value="Close Market" data-ng-click="closeMarket()" />
        <input type="button" id="reset" value="Reset" data-ng-click="reset()" />
 
        <h2>
            Live Stock Table</h2>
        <div id="stockTable">
            <table border="1">
                <thead>
                    <tr>
                        <th>Symbol</th>
                        <th>Price</th>
                        <th>Open</th>
                        <th>High</th>
                        <th>Low</th>
                        <th>Change</th>
                        <th>%</th>
                    </tr>
                </thead>
                <tbody>
                    <tr class="loading" data-ng-show="stocks.length==0">
                        <td colspan="7">
                            loading...
                        </td>
                    </tr>
                    <tr data-ng-repeat="stock in stocks">
                        <td>
                            {{stock.Symbol}}
                        </td>
                        <td>
                            {{stock.Price | number:2}}
                        </td>
                        <td>
                            {{stock.DayOpen | number:2}}
                        </td>
                        <td>
                            {{stock.DayHigh | number:2}}
                        </td>
                        <td>
                            {{stock.DayLow | number:2}}
                        </td>
                        <td>
                            {{stock.Change}}
                        </td>
                        <td>
                            {{stock.PercentChange}}
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
        <h2>
            Live Stock Ticker</h2>
        <div id="stockTicker">
            <div class="inner">
                <ul>
                    <li class="loading" data-ng-show="stocks.length==0">loading...</li>
                    <li data-ng-repeat="stock in stocks" data-symbol="{{stock.Symbol}}"><span class="symbol">
                        {{stock.Symbol}}</span> <span class="price">{{stock.Price | number:2}}</span><span
                            class="change">{{stock.Change}} ({{stock.PercentChange}})</span>
                    </li>
                </ul>
            </div>
        </div>
    </div>

Откройте эту страницу в браузере и оригинальную страницу биржевого индикатора в другом окне браузера и играйте с помощью кнопок. Вы увидите, что оба экрана имеют одинаковые данные в любой момент времени. Единственным отличием будет состояние кнопок, их цвет и список прокрутки. Мы исправим их в следующем посте.


Удачного кодирования!