Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

AngularJS - Get a First Impression

DZone's Guide to

AngularJS - Get a First Impression

· Web Dev Zone
Free Resource

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

AngularJS is an amazing JavaScript framework designed to create single-page applications (SPA) using MVC architecture. In this article I would like to demonstrate the strength of the framework and show how to use angular basic features in action.

As a demo application we’ll create currency converter called “Fancy Currency”. The use case of the app is simple: user enters the value with source currency and gets the value converted into other popular world currencies. Besides the table with converted values the app will have a chart giving visual representation of results. Also we’ll see how to implement table sorting, form validation and other common app features.

If you are interested in what we’ll get in the end, follow the link to look at the result.

The source code is available here.

Do you want to know how to create this app with AngularJS? Please, go on reading.

Application Structure

Before we start with Fancy Currency, lets look at the structure of our app.

It’s pretty simple:

We have directory for styles (css) and for javascript code (js) that contains controllers (js/controllers), models (js/app/models), vendor scripts (js/vendor), and general app scripts (app.js and db.js). Index.html is a main page of an SPA.

List of Currencies

Lets start with creating a list of currencies. Angular uses the declarative approach for creating views. We put the following markup into the file index.html:

<!DOCTYPE html>
<html ng-app>
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <title>Fancy Currency</title>
        <meta name="viewport" content="width=device-width" />
        <link rel="stylesheet" href="css/bootstrap.css" />
        <link rel="stylesheet" href="css/app.css" />
    </head>
    <body>
        <header>
            <div class="container">
                <h2>Fancy Currency</h2>
            </div>
        </header>

        <div class="container" ng-controller="app.ctrls.CurrencyConvertCtrl">
            <div class="row">
                <div class="span6">
                    <table class="table table-hover table-striped">
                        <tr>
                            <th>Currency</th>
                            <th>Ticker</th>
                            <th>Rate</th>
                            <th>Converted</th>
                        </tr>
                        <tr ng-repeat="currency in currencies">
                            <td>{{currency.name}}</td>
                            <td>{{currency.ticker}}</td>
                            <td>{{currency.rate}}</td>
                            <td>{{currency.value}}</td>
                        </tr>
                    </table>
                </div>
            </div>
        </div>

        <hr>

        <script src="js/vendor/jquery-1.9.1.js"></script>
        <script src="js/vendor/bootstrap.js"></script>
        <script src="js/vendor/angular.js"></script>
        <script src="js/vendor/highcharts.js"></script>

        <script src="js/app.js"></script>
        <script src="js/models/Currency.js"></script>
        <script src="js/contollers/CurrencyConvertCtrl.js"></script>
        <script src="js/db.js"></script>
    </body>
</html>

We use Twitter Bootstrap to make the sample looking pretty. That’s why we see here some bootstrap-related css classes like contaner, row, span6 and table-striped.

The most interesting in the markup is angular-specific attributes:

  • ng-app bootstraps the application. The page should have only one ng-app directive. It’s usually placed at the root of the page.

  • ng-controller specifies the controller corresponding to the view. In our case the it is app.ctrls.CurrencyConvertCtrl.

  • ng-repeat defines a for-each loop over a collection of items. In our sample there is a collection of currencies. Table row will be rendered for each currency.

The markup of the currency list is ready. Now we have to define the model (Currency) and the controller (CurrencyConvertCtrl). One of the best angular features is using plain Javascript objects to describe models and controllers. There is no need to inherit from specific framework defined base model or so. This brings us to the dead simple model description:

    app.models.Currency = function(attrs) {
        angular.extend(this, {
            name: "",
            ticker: "",
            rate: 0,
            value: 0,
        }, attrs);
    };

We define the model fields with default values and extend it with values passed into the constructor.

The controller is a plain object too.

    app.ctrls.CurrencyConvertCtrl = function($scope) {
        $scope.currencies = [];

        angular.forEach(app.db.Currencies, function(currency) {
            $scope.currencies.push(new app.models.Currency(currency));
        });
    };

The controller constructor has an input parameter named $scope. It contains the view model data. $scope is like a link between a controller and a view. Usually a controller constructor extends $scope with fields and methods for a view.

The app is the root namespace of the application. It’s defined in app.js:

    window.app = {
        models: {},
        ctrls: {}
    };

In our demo we define static list of currencies in db.js. Of course, we could load the data from the server, but it is not the matter. 

    app.db = {
        Currencies: [
            { name: "Australian Dollar", ticker: "AUD" },
            ...
            { name: "Swiss Franc", ticker: "CHF" },
            { name: "Turkish Lira", ticker: "TRY" },
            { name: "U.S. Dollar", ticker: "USD" },
        ]
    };

Now we can start the app and see the list of currencies:

You can find all source code here.

Currency Conversion

It’s clear with rendering a simple list. Now we’ll see how to make an interactive view. Lets code currency conversion functionality.

Firstly, we add a form to enter a converting value and a source currency.

<div class="container" ng-controller="app.ctrls.CurrencyConvertCtrl">
    <div class="row">
        <div class="span12">
            <form class="well form-inline">
                <input type="number" class="input-small" ng-model="value">
                <select ng-model="currency">
                    <option ng-repeat="currency in currencies" value="{{currency.ticker}}">
                        {{currency.name}} ({{currency.ticker}})
                    </option>
                </select>
                <button class="btn-primary btn" ng-click="convert()">Convert</button>
            </form>
        </div>
    </div>

  ...

</div>

We see two angular attributes new for us:

  • ng-model sets two-way binding between view model and HTML control. The directive is typically used for an input, select or textarea.

  • ng-click is a handler expression of the click event firing on the element. In our case after click on the convert button the method $scope.convert will be executed. The context of expression execution is a view model.

Now we need to implement the method convert in the controller.

    var CONVERT_SERVICE_URL = "https://rate-exchange.appspot.com/currency";

    app.ctrls.CurrencyConvertCtrl = function($scope) {
        var currencies = [];

        angular.forEach(app.db.Currencies, function(currency) {
            currencies.push(new app.models.Currency(currency));
        });

        angular.extend($scope, {

            currencies: currencies,

            convert: function() {
                var self = this,
                    valueToConvert = self.value,
                    currencyTicker = self.currency;

                angular.forEach(self.currencies, function(currency) {
                    if(currency.ticker === currencyTicker) {
                        currency.rate = 1;
                        currency.value = parseFloat(valueToConvert);
                        return;
                    }

                    $.ajax({
                        type: "GET",
                        url: CONVERT_SERVICE_URL,
                        dataType: "jsonp",
                        data: {
                            from: currencyTicker,
                            to: currency.ticker,
                            q: valueToConvert
                        }
                    }).done(function(data) {
                        currency.rate = data.rate;
                        currency.value = data.v;
                        self.$apply();
                    });
                });
            }
        });
    };

We loop over the list of currencies and make ajax requests to the currency converting service. On the done callback of each request the result of conversion and exchange rate are set into the model. A request is made by means of jQuery and executed from outside of angular scope, that’s why we need to notify angular about changes in the model manually by calling special method $apply. Usually when you use angular utilities and mechanisms there is no need to call $apply, framework does it itself.

Lets add some number formatting logic to our Currency model.

    Currency.prototype = {

        formattedRate: function() {
            return this._formatNumber(this.rate);
        },

        formattedValue: function() {
            return this._formatNumber(this.value);
        },

        _formatNumber: function(number) {
            return number ? number.toFixed(2) : "-";
        }
    };

Conversion functionality is ready. Look at the result:

The diff for the step can be found here.

Conversion chart

Lets add a bar chart visualizing the conversion results. To do it we are going to use the HighchartsJS - very powerful and easy to use free javascript chart library.

We add a new div for the chart.

<div class="row">
    <div class="span5">
        <table class="table table-hover table-striped">
            <tr ng-repeat="currency in currencies">
                ...
            </tr>
        </table>
    </div>
    <div class="span7">
        <div id="chart" class="convert-chart"></div>
    </div>
</div>

The controller constructor function creates the chart:

var chart = new Highcharts.Chart({
    chart: {
        renderTo: "chart",
        type: "bar"
    },
    series: [{
        name: "Value",
        data: []
    }],
    title: {
        text: "Value in world currencies"
    },
    xAxis: {
        title: {
            text: null
        }
    },
    yAxis: {
        min: 0,
        title: {
            text: null
        },
        labels: {
            overflow: "justify"
        }
    },
    legend: {
        enabled: false
    },
    plotOptions: {
        bar: {
            dataLabels: {
                enabled: true,
                formatter: function() {
                    return this.y.toFixed(2);
                }
            }
        }
    },
    credits: {
        enabled: false
    }
});

We won’t talk about chart options too much. You can find detailed info on the HighchartsJS docs page. Two the most important options are:

  • renderTo is an id of an HTML element where to render the chart

  • type is a type of the chart (we use a bar chart)

The chart is ready. All that we need is to set data series. There is one problem though: the data conversion process is asynchronous, and we can set series only after all request are done. As known $.ajax returns promise resolved when request is succeeded. We combine promises for all requests with $.when and set chart data on done callback of combined promise. So lets change convert function as follows:

convert: function() {
    var self = this,
        valueToConvert = self.value,
        currencyTicker = self.currency,
        promises = [];

    angular.forEach(self.currencies, function(currency) {
        if(currency.ticker === currencyTicker) {
            currency.rate = 1;
            currency.value = parseFloat(valueToConvert);
            return;
        }

        var promise = $.ajax({
            type: "GET",
            url: CONVERT_SERVICE_URL,
            dataType: "jsonp",
            data: {
                from: currencyTicker,
                to: currency.ticker,
                q: valueToConvert
            }
        }).done(function(data) {
            currency.rate = data.rate;
            currency.value = data.v;
        });

        promises.push(promise);
    });

    $.when.apply(null, promises).done(function() {
        self.setChartData();
        self.$apply();
    });
}

Now there is one promise, so we can call $apply function only once. The function setting data for the chart is pretty simple:

setChartData: function() {
    var currencyTickers = [],
        values = [];

    angular.forEach(this.currencies, function(currency) {
        currencyTickers.push(currency.ticker);
        values.push(currency.value);
    });

    chart.xAxis[0].setCategories(currencyTickers);
    chart.series[0].setData(values);
}

After the changes are done we have a chart with conversion results.

Follow the link to find the diff of the step.

Currency Table Sorting

The table sorting is often necessary feature. Lets implement sorting ability for our table of currencies. The sorting is performed by clicking on a table column header. To add some logic to the table header, we change the columns rendering:

<table class="currency-table table table-hover table-striped">
    <tr>
        <th ng-repeat="column in columns" ng-click="setSorting(column.field)"
            ng-class="columnClass(column.field)">
            {{ columnSortingPrefix(column.field) }} {{column.title}}
        </th>
    </tr>
...

We see one new angular attribute here:

  • ng-class sets the CSS class to the HTML element. In our case th CSS class depends on current sorting field.

The controller constructor has next changes:

angular.extend($scope, {
    ...

    columns: [
        { field: "name", title: "Currency" },
        { field: "ticker", title: "Ticker" },
        { field: "rate", title: "Rate" },
        { field: "value", title: "Converted" }
    ],

    sorting: {
        field: "name",
        asc: true
    },

    columnClass: function(field) {
        return this.sorting.field === field ? "sorted" : "";
    },

    columnSortingPrefix: function(field) {
        if(this.sorting.field === field) {
             return this.sorting.asc ? "↑" : "↓";
        }
    },

    setSorting: function(field) {
        var sorting = this.sorting;
        if(sorting.field === field) {
            sorting.asc = !sorting.asc;
        } else {
            sorting.field = field;
            sorting.asc = true;
        }
    }
});

The $scope has some new ingredients:

  • columns is the array with table columns.

  • sorting is plain object with info about current sorting (field name and sorting direction).

  • columnClass returns a CSS class for the th HTML element.

  • columnSortingPrefix returns up/down arrow sign for the field to indicate sorting direction.

  • setSorting sets current sorting field and direction

“Where is actual sorting?”, you ask. Good question. The sorting is really simple with angular:

<tr ng-repeat="currency in currencies | orderBy : sorting.field : !sorting.asc">
    <td>{{currency.name}}</td>
    <td>{{currency.ticker}}</td>
    <td>{{currency.formattedRate()}}</td>
    <td>{{currency.formattedValue()}}</td>
</tr>

We do sorting with special angular filter orderBy : expression : reverse.

It has two parameters:

  • expression is used to sort array. In our scenario it’s a sorting field name.

  • reverse says whether to reverse the items in the array. The value of parameter depends on sorting direction.

That’s all with it. The diff is available here.

5. Loading and Validation

Now when all general functionality is ready, it’s time to do some polishing. Firstly, it would be better to give to the user some feedback during conversion process, for instance to show loading indicator.

To notify the user during conversion process we add a flag field called loading to the $scope. In the beginning of the convert function the flag is set to true. The conversion promise done callback sets the flag to false. In markup we add a loading indicating gif.

<img src="img/ajax-loader.gif" ng-show="loading">

There is a new angular attribute:

  • ng-show defines whether to show HTML element depending on expression. In our case the expression is value of the flag-field loading.

It’s also a good practice to validate the user input before conversion. Angular allows to do validation with ease. We may use HTML5 attributes like "required" and specific input types like "number". To check whether all entered values are valid we may use converting.$invalid, where "converting" is a form name. The field $invalid also available for each form field. To inform the user that he has to enter correct values we add alert div with message.

<form name="converting" class="well form-inline">
    <input type="number" class="input-small" ng-model="value" placeholder="Enter Value" 
        required ng-class="{ error: converting.value.$invalid }">
    <select ng-model="currency" required ng-class="{ error: converting.currency.$invalid }">
        <option ng-repeat="currency in currencies" value="{{currency.ticker}}">
            {{currency.name}} ({{currency.ticker}})
        </option>
    </select>
    <button class="btn-primary btn" ng-click="convert()" 
        ng-disabled="converting.$invalid || loading">Convert</button>
    <img src="img/ajax-loader.gif" ng-show="loading">
    <div class="alert alert-info" ng-show="converting.$invalid">
        Enter numeric value and select source currency to convert.</div>
</form>

The convert button is disabled while form values are invalid. We achieve this by means of ng-disabled attribute. To prevent simultaneous conversions convert button is also disabled while loading.

The directive ng-class is smart and allows to use not only string value but also plain object where each field is a name of CSS class and a field value is a boolean defines whether to attach the class. In our case the class "error" is added for each field with invalid value.

Now if enter wrong value we see the following:

The diff of the step can be found here.

Conclusions

The AngularJS is very powerful and flexible javascript framework for creating SPA. In this post we learned how to use its basic features: create views and controllers, define two-ways bindings, attach click handlers, make forms validation and table sorting. We saw how easy it can be used with other javascript libraries like jQuery and HighchartsJS. One of the best advantages of the angular is that it allows to use plain javascript objects declaring models and controllers, while other frameworks often require to extend specific predefined types. Angular has a lot of other important  features like defining modules, routing, custom directives (attributes) and custom services dependency injection. Unfortunately, they are beyond the topic of our short article. Hope you’ve got the first impression of AngularJS, and you are interested in going on with learning and trying it out.

Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}