Stock Market App

Discover the Power of Directives: Bringing an Ionic App to Life — Part 2

·

This week Hunter Leaman stopped by to write a tutorial on how he used directives to create a stock market application with price tickers that automatically update. Hunter has done a great job with this app and it is even listed in Ionic’s App Showcase. If you’re interested in learning even more about how this application was built, make sure to check out Hunter’s course at the end of this post (along with a generous discount link).

Let’s be honest… sequels are almost never as good as originals; this article, however, is one of those very rare exceptions where the second is, in fact, much better than the first. I’m initiating Part 2 with the exact same first three words as its younger sibling (Part 1) because we’re focusing on directives here too.

In this article, I’m going to explain how directives are bringing my Ionic app to life. Imagine the directive family from Part 1 as the middle school versions of their now collegiate-level all-star older siblings. This updated and optimized implementation will demonstrate how directives can be used to display and update dynamic, time-sensitive data with performance in mind.

So without further ado, I’d like to share two gifs showing the full implementation from stockMarketApp (on the left) and the result of this article’s discussion (on the right):

stockMarketApp
play.ionic.io

The left gif shows several data points, and even the stock’s ng-repeat instance, responding to any updated data as it becomes available; the right gif highlights the most important directive of the former—the auto-updating price change percentage directive.

Let’s jump into the code. You can view the source here: play.ionic.io, but you will need (I’m pretty sure) to use Chrome if you want the app to run because it requires a CORS plugin that allows the $http requests. I’m using Ionic’s Playground to host this discussion’s code because my goal is to highlight the Ionic team’s wonderful creations through application rather than description. Although I’m not going to detail the ins and outs of their playground, I believe you’ll get a good taste of its potential through this demo.

<ion-list>
          
  <ion-item class="stock" auto-update-data="{{::stock.ticker}}" ng-repeat="stock in stocks track by stock.ticker">
              
    <div class="row row-center">
      <div class="col col-center col-33 stock-ticker">{{::stock.ticker}}</div>
      <div class="col col-center col-50 col-offset-25 price-change-percent"></div>
      <i class="ion-arrow-up-a"></i>
    </div>
              
  </ion-item>
            
</ion-list>

In the HTML above, you can see I’ve set up a rather simple ion-list containing an ion-item, which, by way of an ng-repeat directive, builds a list of stocks comprising their tickers and day’s performance á la price change percentage. Below, you can see the MainController’s initialization code, which loads the stocks and their data and formats the list with the results. Not really a point of focus here (as I’ve chosen this style because it accommodates the full implementation rather than this simplified version).

function orderStocks(stocksWithDataArray) {
  var orderedStocksWithDataArray = $filter('orderBy')(stocksWithDataArray, 'priceChangePercent', true);
  $scope.stocks = orderedStocksWithDataArray;
}

function init() {

  var stocks = [
    {ticker: '^GSPC'},
    {ticker: '^DJI'},
    {ticker: 'NFLX'},
    {ticker: 'TSLA'},
    {ticker: 'AAPL'},
    {ticker: 'GPRO'},
    {ticker: 'PYPL'},
    {ticker: 'BTCUSD=X'}
  ],

  stocksWithData = [];

  stocks.forEach(function(stock) {

    stockDataService.getPriceData(stock.ticker).then(function(data) {
      var stockObject = { ticker: stock.ticker, priceChangePercent: data.chg_percent };
      stocksWithData.push(stockObject);
      orderStocks(stocksWithData);
    });

  });
}

init();

The real magic happens in the autoUpdateData directive below. You’ll notice in the HTML above, the auto-update-data attribute (line 3) passes the respective instance’s stock ticker as an attribute (lines 15 and 20), which is used in line 7 (thus also in line 139 in the full source) to enable its requesting data from the API. Each interval, the requested data then redefines each instance’s scope’s stock’s priceChangePercent property (line 21), in addition, adding an arrow icon indicating the direction of change (lines 25-52).

.directive('autoUpdateData', function($timeout, $interval, $filter, stockDataService) {
  return {
    restrict: 'A',
    controller: function() {

      this.checkForNewData = function(ticker) {
        return stockDataService.getPriceData(ticker).then(function(data) {
          return data;
        });
      };

    },
    link: function(scope, element, attrs, ctrl) {

      var ticker = attrs.autoUpdateData,
        priceChangeArrowElement = angular.element(element[0].children[0].lastElementChild);
      priceChangeArrowElement = angular.element(priceChangeArrowElement);

      var checkForNewDataInterval = $interval(function() {
        ctrl.checkForNewData(ticker).then(function(data) {
          scope.stock.priceChangePercent = data.chg_percent;
        });
      }, 3 * 1000); 

      scope.$watch('stock.priceChangePercent', function(newData, oldData) {

        if( newData && newData != oldData ) {

          if( newData > oldData  && newData >= 0 ) {
            priceChangeArrowElement.addClass('up');
            $timeout(function() {
              priceChangeArrowElement.removeClass('up down');
            }, 2001);
          } else if( newData < oldData && newData >= 0 ) {
              priceChangeArrowElement.addClass('down');
              $timeout(function() {
                priceChangeArrowElement.removeClass('down up');
              }, 2001);
          } else if( newData < oldData && newData <= 0 ) {
              priceChangeArrowElement.addClass('up');
              $timeout(function() {
                priceChangeArrowElement.removeClass('up down');
              }, 2001);
          } else if( newData > oldData && newData <= 0 ) {
              priceChangeArrowElement.addClass('down');
              $timeout(function() {
                priceChangeArrowElement.removeClass('down up');
              }, 2001);
          }

        }
      });
    }
  };
})

But who cares right? How does it get to the view? The priceChangePercent directive takes care of that… very simply (below). By way of a simple watcher, the view will update anytime a stock’s priceChangePercent value changes. The beauty, in my opinion, lies in the directive’s ability to add substantial value without cluttering the HTML: like a ninja, the directive blends in with the template seamlessly, as it’s referenced as a class, and makes itself known only when it effects a change.

.directive('priceChangePercent', function() {
  return {
    restrict: 'C',
    require: '^autoUpdateData',
    template: '<span ng-if="stock.priceChangePercent > 0">+</span>{{stock.priceChangePercent | number: 3}}%',
    link: function(scope, element, attrs, ctrl) {

      scope.$watch('stock.priceChangePercent', function(newData, oldData) {
        if( newData ) {
          scope.stock.priceChangePercent = newData;
          // next two lines not optimized
          if( newData >= 0 ) angular.element(element).css('background-color', '#1ac45f');
          if( newData < 0 ) angular.element(element).css('background-color', '#d50000');
        }
      });

    }
  };
})

Most of the grunt work is possible ultimately because of Angular’s genius; however, the front-end doesn’t do the heavy lifting—data retrieval, promise resolving, etc. — the Ionic framework is the guiding influence enabling this potentially unblossomed view to spring to life in the form of an educating app experience. If you want to check out the full blown app featuring the “graduate-level” versions of these directives and controllers, you can download stockMarketApp on the App Store.

If you want to learn how to build stockMarketApp, which is featured on Ionic’s showcase of the most beautiful apps built with their framework, you can enroll in Rapid Prototyping with Ionic: Build a Data-Driven Mobile App. As a thank you for making it to the end of this article, I’d like to offer you a 50% off coupon to the course, so you can start learning Ionic now.

Thanks for reading!

What to watch next...