Implementing a SignalR Stock Ticker Using AngularJS: Part1
Join the DZone community and get the full member experience.
Join For Free
I assume that you have already seen the SignalR Stock ticker sample. If not, download it from GitHub or add the NuGet Package to an existing ASP.NET web application. Make sure that you take some time to run the sample at least once on multiple browsers and have a glance at the code before you proceed any further.
I hope you had a look at the code on both the server and the client side of the stock ticker sample. We will not make any modification to the server code and the layout of the page, but we will rewrite the JavaScript part using features of AngularJS. Since there is a lot of client-side code to convert, let’s do it in a two-part series:
- Creating a custom service to communicate with the hub on the server and using the service in a controller (this post)
- Performing UI changes on the page, like enabling/disabling buttons, scrolling stock values in a list, and adding animation effect to values in a table and list (next post)
Make a copy of the StockTicker.html file and give it a name of your choice. Add two JavaScript files, controller.js and factory.js, to the project. We will add script to these files soon. Modify the script reference section of the page to include the following script files:
<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>Let’s start implementing the SignalR part inside a custom service. To keep the controller free from doing anything other than providing data to the view and handling view events, we are creating a custom service to handle the hub communication logic. The service is responsible for:
- Creating objects needed for communication
- Configuring client functions to proxy and to respond when a market is opened, closed, or reset, or when a stock value is updated
- Starting a hub connection
- Getting the current values of stocks and the current market statuses of the stocks once the connection is started
- Opening, closing or resetting markets on demand
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; } ]);We need a controller to start the work. The controller will have the following components:
- An array to store the current stock values and a Boolean value to store the current market state
- Setters to assign values to the fields
- A function to modify the value of an entry in the stocks array
- Functions to handle open, close and reset operations when the corresponding button is clicked
- Set callbacks to the service and ask the service to kick off the communication
Following is the code in the controller:
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(); }
The layout of the HTML page will remain unchanged. But we need to change the way data is rendered on the screen. The stock ticker sample uses a poor man’s template technique to render content in the table and in the scrolling list. Since we are using AngularJS, let’s replace it with expressions. Following is the mark-up on the page in Angular’s style:
<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>
Open this page on a browser and the original stock ticker page on another browser window and play with the buttons. You will see that both screens have the same data at any given point in time. The only difference would be the state of the buttons, their color and a scrolling list. We will fix them in the next post.
Happy coding!
Opinions expressed by DZone contributors are their own.
Comments