Creating an FX watchlist widget

Full stack part 2: HTML5, CSS, AngularJS — directive widget

Bill Gooch
HTML5 RIA full stack

--

Intro

Leading on from part 1, we have a forex price feed and we now need a better representation of our data on the client.

Part 2:

We will be using angular JS to create a FX watch list widget which will display the symbol price data. In the angularJS world creating a widget / component means that we will be implimenting an angular directive.

We have the following requirements…

  1. Widget to be loosely coupled from the hosting application

2. Widget to be encapsulated

3. API available to notify hosting application that user has selected a symbol

UI/UX

The widget will display the following 4 columns.

Symbol | Bid | Ask | Time

As a requirement the widget needs to be loosely coupled from the hosting application and encapsulated. Therefore, css library’s such as bootstrap will not be used, specificity will be used to ensure the widget styles are not overridden.

Creating a 4 column grid in css

Prior to creating the grid, a container needs to be set to house the grid.

#gchFxMarketWatch {
background: #000000;
width: 500px;
padding: 5px;
border-radius:5px;
color: #ffffff;
}

I can now setup the grid with column width 25% and float left.

--------------------------------------
GRID SYSTEM
--------------------------------------
[class*='col-'] {
float: left;
}
.col-1-4 {
width: 25%;
padding: 2px;

background: ... some lovely gradients ....
}.col-1-4Header {
width: 25%;
padding: 2px;
background-color: #1C1E22;
font-weight: bold;
}

Food for thought

Ideally we would like the host application to have ability to change the look and feel of the widget ( white label), but as an eternal pragmatist lets leave that for another post.

Client implementation

As angular is now known as a MVWhatever framework i like to keep it simple and talk about the client in terms of MVC.

The service layer will be made up of the socket service as an angular factory

Service

The socket service is set up by injecting the socketFactory, creating an instance and defining our socket events for the angular life cycle to manage.

    // Service
//---------------------------------
function fxPriceService(socketFactory){ var socket = socketFactory();
socket.forward(['fxPriceUpdate','disconnected']);
return socket;
}

The socket factory is an API that is part of the angular-socket-io package.

Model

The node fx price service module is responsible for the creation of the model, an array of symbol objects and in this context our client just needs to handle the model. This is achieved via a socket event handler in the controller of our directive.

$scope.$on('socket:fxPriceUpdate', function(event, data) {                    $scope.rates = data.payload;
});

The model is contained in the data.payload property returned in the socket:fxPriceUpdate event handler.

If you remember we set this value when emitting a fxpriceUpdate event from the fx-price-service node module.

fx-price-srv.js

socket.emit('fxPriceUpdate', {
payload: fxPriceData
});

Controller

The directive represents our widget / component architecture inside of angular, as part of that architecture we have a controller for our DOM template.

A controller is to be used to manage events and changes of the model from the view and bind the model to the view.

In our case we simply need to handle the socket events and manage the behaviour of a user selecting a currency pair.

function Controller ($scope, SYMBOL_SELECTED_EVT){ //EVENT HANDLERS
//——————————————————————————
$scope.symbolSelected = function(currency){ $scope.$emit(SYMBOL_SELECTED_EVT,currency); } //Socket HANDLERS
//——————————————————————————
$scope.$on(‘socket:fxPriceUpdate’, function(event, data) {
$scope.rates = data.payload;
});
$scope.$on(‘socket:disconnected’, function(event, data) { console.log(“The SOCKET has been disconnected”);
});
}

API

As our widget will be loosely coupled from the host application the best way to communicate is through events. In our use case the widget will be used by a host application hence we will want to emit our event.

This will cause the event to propagate up the scope chain, where the host application will be able to handle it.

$scope.$emit(SYMBOL_SELECTED_EVT,currency); 

View

The view of our widget / component is again part of the directive architecture and in this case is represented by the template.

The template is using the ng-repeat directive to display the symbols and handle the user click event using ng-click.

<div ng-repeat="rate in rates" 
ng-click="symbolSelected(rate)">
...... display symbol .....
</div>

Best practise

When dynamically altering css its always preferable to place such logic into the view using the ng-class directive as we want to avoid having any DOM manipulation in the controller. In our case we need to display to the user if the bid or the ask price has changed and to display the appropriate style

ng-class=”{’glyphicon-arrow-down fx-priceDown’ : rate.offerBullBear == ’bear’, ’glyphicon-arrow-up fx-priceUp’ : rate.offerBullBear == ’bull’}”

Putting it all together the Directive

As previously mentioned the directive provides the architecture to define your widget / component.

function fxMarketWatchDirective (fxPriceService){ function Controller ($scope, SYMBOL_SELECTED_EVT){ //EVENT HANDLERS
//——————————————————————————
$scope.symbolSelected = function(currency){
$scope.$emit(SYMBOL_SELECTED_EVT,currency);
}
//Socket HANDLERS
//——————————————————————————
$scope.$on(‘socket:fxPriceUpdate’, function(event, data) {
$scope.rates = data.payload;
});
$scope.$on(‘socket:disconnected’, function(event, data) {
console.log(“The SOCKET has been disconnected”);
});
}

// DIRECTIVE CONFIGURATION
return {
restrict:’E’,
replace:’true’,
//ISOLATE SCOPE
scope: { },
template: wacthlistTemplate() ,
controller : Controller
}
}

Most of the code above has already been discussed although we have the additional properties.

// DIRECTIVE CONFIGURATION
return {
restrict:’E’,
replace:’true’,
//ISOLATE SCOPE
scope: { },
template: wacthlistTemplate() ,
controller : Controller
}

DOM rendering

The restrict:’E’ and replace :’true’ relate to how the directive is to be rendered on the DOM.

In the case of restrict:’E’ the directive needs to be declared as an element as when angular enters the compile phase this is what it will be looking for when matching the directive to the DOM.

This is my preferred choice for a widget or component and bodes well to HTML5 semantics.

The html tag will look like the following

<fxmarket-Watchlist>

</fxmarket-Watchlist>

As a side note restrict:’E’ causes some issues with older browser especially <IE8 as a consequence restrict:’A’ is perceived a more flexible choice.

“Different horses for different courses”

Encapsulation and scope

In order to ensure encapsulation and that we do not pollute the surrounding scope we will use an isolate scope for our directive. The isolate scope is represented by the property.

//ISOLATE SCOPE
scope: { },

Template view

 template: watchlistTemplate() 

The watchlistTemplate() is simply a helper method that returns the HTML of our view

Conclusion

Through the use of an isolate scope, css specificity we have encapsulated our component / widget logic and through the use of emitting our events we have loosely coupled our component from the hosting application.

The code for the fx-watchlist is stored on github here……..

Keep up to date on this series by following me on twitter @billgch

Previous article : Full stack part 1

Next article : Grunt bower and npm

--

--

Bill Gooch
HTML5 RIA full stack

Full Stack Architect Node, AngularJS, Javascript, HTML5, CSS