Work
with us

Tell us about your idea
and we will find a way
to make it happen.

Get estimate

Join our awesome team

Check offers

This tutorial is a second part of article by Mateusz Sagan. It is not a straight follow-up, more like an independent AngularJS project based on his post. This is a mobile application for currency conversion. I integrated European Central Bank API with Rails application and AngularJS front-end. This tutorial will show you the basics of AngularJS, so you will get the idea on how a Rails application can interact with AngularJS front-end. As a Ruby on Rails developer, I will explain many AngularJS features by comparing them to their Rails counterparts.

Configuration

Before switching to AngularJS, you have to prepare the application background. Please remember that my project is divided into two layers: Rails layer, and front-end/javascript layer. Still, everything is combined in one application. It is a very good solution for small projects. For bigger applications, I recommend creating Rails REST API, and communicating with AngularJS front-end application.

Two gems are required for the development: bower-rails and angular-rails-templates. I also use norigem for XML to Hash conversion. I added it for simplicity, as European Central Bank API does not return JSON data. It is not required for every AngularJS integration project.

gem 'nori'
gem 'bower-rails'
gem 'angular-rails-templates'

Next step is to prepare assets for the front-end part. If you had never used bower, its function is similar to bundler, i.e. managing and installing packages. I have chosen very basic plugins: angular-route for routing mechanism inside AngularJS part, mobile-angular-ui – UI framework similar to JQuery Mobile, and a bootstrap along with bootstrap select dropdown, which is not included in the official package. Use a ‘rake bower:install’ command in console for installation.

asset 'angular'
asset 'angular-route'
asset 'bootstrap-sass-official'
asset 'mobile-angular-ui'
asset 'angular-bootstrap-select'

Now add assets paths to application.rb file. Every external requirement should be added to application.js file.

config.assets.paths << Rails.root.join("vendor","assets","bower_components")
config.assets.paths << Rails.root.join("vendor","assets","bower_components","bootstrap-sass-official","assets","fonts")
//= require jquery
//= require jquery_ujs
//= require angular
//= require angular/angular
//= require angular-route/angular-route
//= require angular-rails-templates
//= require angular/application
//= require mobile-angular-ui
//= require angular/application
//= require_tree ./angular/controllers
//= require_tree ./angular/services
//= require_tree ../templates

Also notice that Turbolinks have been removed.

Background – Rails

The main objective of my application is to download currency rates from ECB API, allowing to convert selected currencies. The Background application is small, and I will not focus on its contents much. In general, I need only one model, and a few controllers. Currency Model has one method that is used for connection with the API. It looks a bit complicated, but it’s only a XML data parser which updates rate field in the Currency objects.

def self.parse_currencies_from_xml
  doc = Nokogiri::XML(open("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"))
  parser = Nori.new
  hash = parser.parse(doc.to_s)
  # sorry for that 'Cube' repetition, but this is how the ECB xml is build
  hash["gesmes:Envelope"]["Cube"]["Cube"]["Cube"].each do |currency|
    c = Currency.find_by(name: currency["@currency"])
    c.rate = currency["@rate"].to_f
    c.save!
  end
end

Currency controller communicates with AnguarJS app with JSON data. It contains only the most important methods: to list all currencies, and to update its rates with static method presented in previous codeblock.

def list
  @currency = Currency.order(:id).to_a
  @currency_list = @currency.each do |c|
    {
    id:       c.id,
    name:     c.name,
    country:  c.country,
    rate:     c.rate
    }
  end
  render json: { data: @currency_list.to_json }
end

def refresh
  if Currency.parse_currencies_from_xml
    render json: { data: 'refresh' }
  else
    render json: { data: 'error' }
  end
end

I made HomeController with index action routed as a root path. The View is empty, but controller loads new layout. It is not necessary, but creating new layout for Angular application gives you the possibility to add Rails views in the future. E.g., you can build a Rails admin panel for this application.

class HomeController < ApplicationController
  layout "angular"
  def index ; end
end

Front-end – mobile UI

AngularJS application consists of the main application module, a few controllers, services, and templates. Everything is kept under app/assets/javascripts/ directory, and the asset pipeline will put them all together. Set up the layout with the angular directives. HomeController loads angular layout to application. It is a little bit complicated, because the mobile framework is used to build the front-end part. I will highlight only the most important parts that are used in all AngularJS applications. Layout uses many directives:

ng-scope – angular directive for extending scope. ng-app – root element of the application. ng-controller – defines which angular controller is used in particular block of code. ng-include – adds an external template view, in our case it is a partial view with sidebar menu. ng-view – a place where particular template is loaded, similar to yield tag in rails layout.

!!!
%html.overthrow-enabled
  %head
    %meta{:charset => "utf-8"}/
    %meta{:content => "IE=Edge,chrome=1", "http-equiv" => "X-UA-Compatible"}/
    %meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}/
    %title Currency calculator
    = stylesheet_link_tag    "application", :media => "all"
    = javascript_include_tag "application"
    = csrf_meta_tags
  %body.ng-scope.has-navbar-top.has-sidebar-left{ "ng-app" => "currency-calculator", "ng-controller" => "AppController", "ui-prevent-touchmove-defaults" => ""}
    .sidebar.sidebar-left{"ng-include" => "'sidebar_menu.html'", "ui-track-as-search-param" => "true"}
    .app{"ng-swipe-left" => "Ui.turnOff('uiSidebarLeft')", "ng-swipe-right" => "Ui.turnOn('uiSidebarLeft')"}
      .navbar.navbar-app.navbar-absolute-top
        .navbar-brand.navbar-brand-center{"yield-to" => "title"}
          Currency Converter
        .btn-group.pull-left
          .btn.sidebar-toggle{"ui-toggle" => "uiSidebarLeft"} menu
    .app-body
        .app-content
          .scrollable.scrollable-content.overthrow{"ng-view" => ""}

Remember to import every stylesheet provided in these packages.

@import 'bootstrap-sass-official/assets/stylesheets/bootstrap-sprockets';
@import 'bootstrap-sass-official/assets/stylesheets/bootstrap';
@import 'mobile-angular-ui/dist/css/mobile-angular-ui-hover.min.css';
@import 'mobile-angular-ui/dist/css/mobile-angular-ui-base.min.css';
@import 'mobile-angular-ui/dist/css/mobile-angular-ui-desktop.min.css';

The application has one module that contains global configuration file, similar to application.rb. It contains information about every asset added to the application, and routes with corresponding templates and services. RouteProvider loads particular template for a given URL. Here you can also define which Controller will be used for this template.

pp = angular.module 'currency-calculator',
  [
    'ngRoute',
    'templates',
    'mobile-angular-ui',
    'GlobalService'
  ]

  app.config ['$routeProvider', ($routeProvider) ->
    $routeProvider
    .when('/', { templateUrl: 'main.html', reloadOnSearch: false})
    .when('/about', { templateUrl: 'about.html', reloadOnSearch: false})
    .when('/list', { templateUrl: 'list.html', controller: 'ListController', reloadOnSearch: false })
    .when('/calculator', { templateUrl: 'calculator.html', controller: 'CalculatorController', reloadOnSearch: false })
  ]

I wanted to add some simple global methods to the application, so I decided to create GlobalService. It works the same way Rails services do. It is a class with methods that can be invoked in every place in the application. I defined the method ‘go()’ that works like ‘link_to’, as it is used to switch between templates. Method ‘back()’ is used for falling back to a previously-used template. Both methods are built upon the $locationProvider.

angular.module('GlobalService', []).service 'GlobalService',
["$rootScope", "$location", ($rootScope, $location) ->

  history = []

  $rootScope.$on '$routeChangeSuccess', ->
    history.push $location.$$path
    return

  $rootScope.go = (path) -> $location.path path

  $rootScope.back = ->
    prevUrl = if history.length > 1 then history.splice(-2)[0] else '/'
    $location.path prevUrl
    return
]

Sidebar menu is defined in a HAML template file. It contains a list with buttons for each action. Each button has a directive ng-click. After a button is clicked, it invokes a method; in this particular case it is the method ‘go()’ defined in GlobalService – it allows template switching.

.scrollable
  %h1.scrollable-header
  .scrollable-content
    .list-group{"ui-turn-off" => "uiSidebarLeft"}
      %a.list-group-item{ng: {click: "go('/')"}}
        .glyphicon.glyphicon-menu-hamburger
        Main
      %a.list-group-item{ng: {click: "go('/list')"}}
        .glyphicon.glyphicon-list
        Currency List
      %a.list-group-item{ng: {click: "go('/calculator')"}}
        .glyphicon.glyphicon-euro
        Converter
      %a.list-group-item{ng: {click: "go('/about')"}}
        .glyphicon.glyphicon-info-sign
        About

List Controller is responsible for displaying currencies, and allows users to refresh the values on the list. It uses HTTP requests to communicate with Rails part of the application. List of currencies is saved to a scope variable ($scope.currency). A Refresh method makes a Get request to run Refresh method defined in Currency Controller inside rails part. In the next step, updated values are assigned to currency variable.

app = angular.module('currency-calculator')
ListController = ($scope, $http, GlobalService) ->

  $http.get('/currency_list.json').success (result) ->
    $scope.currency = JSON.parse(result.data)

  $scope.refresh = ->
    $http.get('/refresh.json').success (result) ->
      console.log(result.data)
    $http.get('/currency_list.json').success (result) ->
      $scope.currency = JSON.parse(result.data)

app.controller 'ListController', ['$scope', '$http', 'GlobalService', ListController]

Calculator controller contains many variables and methods used to present currencies in dropdown select boxes. It automatically updates values after a number is provided. Methods are not complicated, it is a simple math. Method ‘calculate()’ adds a provided digit to string with value and automatically recalculates result value. Method ‘zero()’ sets both strings to zero, just the way you do using real calculator. What is important, I decided that the first currency visible in the dropdown will be one with ID = 31 – it is a number of all Currency objects.

pp = angular.module('currency-calculator')
CalculatorController = ($scope, $http, GlobalService) ->

  $scope.value1 = 1
  $scope.value2 = 1
  $scope.rate1 = 1
  $scope.rate2 = 1
  $scope.newNumber = true

  $http.get('/currency_list.json').success (result) ->
    $scope.currency1 = JSON.parse(result.data)
    $scope.SelectedOption1 = $scope.currency1[31]

  $http.get('/currency_list.json').success (result) ->
    $scope.currency2 = JSON.parse(result.data)
    $scope.SelectedOption2 = $scope.currency2[31]

  $scope.changedValue1= (currency) ->
    $scope.rate1 = currency.rate
    $scope.value2 = ( $scope.value1 * $scope.rate2 ) / $scope.rate1

  $scope.changedValue2= (currency) ->
    $scope.rate2 = currency.rate
    $scope.value2 = ( $scope.value1 * $scope.rate2 ) / $scope.rate1

  $scope.calculate= (val) ->
    if ($scope.value1 == "0" || $scope.newNumber)
      $scope.value1 = val;
      $scope.newNumber = false;
    else
      $scope.value1 += String(val)
    $scope.value2 = ( $scope.value1 * $scope.rate2 ) / $scope.rate1

  $scope.zero= ->
    $scope.value1 = "0"
    $scope.value2 = "0"

app.controller 'CalculatorController', ['$scope', '$http', 'GlobalService', CalculatorController]

Here is presented a menu of application:

This is the application menu:

After choosing the ‘Currency List’, the template defined below will be displayed. AngularJS makes it very easy nowadays. Button with directive ng-click is used to launch the refresh() method defined previously in ListController. The $scope.currency method contains an array with currency objects. A directive ng-repeat is used to iterate on each of them and display information (name, rate, country). Directives use whole range of nice filters. Currencies will be ordered by name, and rates will be rounded up to 4 decimal places.

.page
  %h3 Currency List
  .list-group-item
    .btn.btn-default.width-max{ng:{click: "refresh()"}}
      .glyphicon.glyphicon-refresh
    refresh
  .list-group-item.box-list{ng: {repeat: "c in currency | orderBy:'name'"}}
    .box  -  -
  .list-group-item.bottom-box

I created a very simple calculator view. The template contains two select dropdowns with currency objects, and buttons with numbers. Clicking them changes values next to dropdowns: the first one is provided by users; the second one is calculated after the conversion. The ‘x’ button clears the value.

.page
  .list-group-item
    .value
    .select
      %select.selectpicker.form-control{ng: {model: "SelectedOption1", options: "c.name for c in currency1 | orderBy:'name' ", change: 'changedValue1(SelectedOption1)'} }
  .list-group-item
    .value
    .select
      %select.selectpicker.form-control{ng: {model: "SelectedOption2", options: "c.name for c in currency2 | orderBy:'name' ", change: 'changedValue2(SelectedOption2)'} }
  .calc-wrap
    .btn.calc-button{ng: {click: "calculate(1)"}}
    1
    .btn.calc-button{ng: {click: "calculate(2)"}}
    2
    .btn.calc-button{ng: {click: "calculate(3)"}}
    3
    .btn.calc-button{ng: {click: "calculate(4)"}}
    4
    .btn.calc-button{ng: {click: "calculate(5)"}}
    5
    .btn.calc-button{ng: {click: "calculate(6)"}}
    6
    .btn.calc-button{ng: {click: "calculate(7)"}}
    7
    .btn.calc-button{ng: {click: "calculate(8)"}}
    8
    .btn.calc-button{ng: {click: "calculate(9)"}}
    9
    .btn.calc-button{ng: {click: "zero()"}}
    x
    .btn.calc-button{ng: {click: "calculate(0)"}}
    0
    .btn.calc-button
  .list-group-item.bottom-box

Conclusion

I think it would be easier to understand the code if you could run the application. You can try it by yourself. It is available online. I recommend using a mobile view in your PC browser, or displaying this page on a mobile device.

Post tags: