GOOD NEWS

GOOD NEWS
Ask For The Truth...

Sunday, September 2, 2012

Sample Application with Angular.js

Sample Application with Angular.js:
After I blogged a three-part Backbone.js tutorial (part 1, part 2, part 3), a number of people asked me to try Angular.js. So I decided to take it for a test drive. I thought it would be interesting to rebuild with Angular.js the Wine Cellar application I had built with Backbone.



If you are new to my Wine Cellar application, it is a simple CRUD app that allows you to manage (create, update, delete) wines in a Wine Cellar. The data is stored in a MySQL database that the client application accesses through a simple RESTful API. This seemed to be a good fit since CRUD applications are often positioned as the sweet spot for Angular.js.
You can run the application here. The UI is intentionally plain to keep the focus on the key learning points. For obvious reasons, this online version is “read-only”. You can download the fully enabled version here.

Application Walkthrough

The best way to get started with Angular.js is to go through the excellent tutorial that is part of the documentation, so I’ll only provide a high level overview of my code here.
Like the Backbone.js implementation, the Angular.js version is a deep-linkable single page application. index.html is defined as follows:
<!DOCTYPE HTML>
<html xmlns:ng="http://angularjs.org">
<head>
<title>Angular Cellar</title>
<link rel="stylesheet" href="css/styles.css" />
</head>

<body ng:controller="RouteCtrl">

<div id="header">
    Angular Cellar
    <button ng:click="addWine()">New Wine</button>
</div>

<div id="sidebar">
    <ng:include src="'tpl/wine-list.html'"></ng:include>
</div>

<div id="content">
    <ng:view></ng:view>
</div>

<script src="lib/angular-0.9.19.min.js" ng:autobind></script>
<script src="js/services.js"></script>
<script src="js/controllers.js"></script>

</body>

</html>
The application is driven by a set of Controllers that I defined in controllers.js as follows:
function RouteCtrl($route) {

    var self = this;

    $route.when('/wines', {template:'tpl/welcome.html'});

    $route.when('/wines/:wineId', {template:'tpl/wine-details.html', controller:WineDetailCtrl});

    $route.otherwise({redirectTo:'/wines'});

    $route.onChange(function () {
        self.params = $route.current.params;
    });

    $route.parent(this);

    this.addWine = function () {
        window.location = "#/wines/add";
    };

}

function WineListCtrl(Wine) {

    this.wines = Wine.query();

}

function WineDetailCtrl(Wine) {

    this.wine = Wine.get({wineId:this.params.wineId});

    this.saveWine = function () {
        if (this.wine.id > 0)
            this.wine.$update({wineId:this.wine.id});
        else
            this.wine.$save();
        window.location = "#/wines";
    }

    this.deleteWine = function () {
        this.wine.$delete({wineId:this.wine.id}, function() {
            alert('Wine ' + wine.name + ' deleted')
            window.location = "#/wines";
        });
    }

}
RouteCtrl defines the routes of the application. Each route is defined by a template that is rendered in <ng:view> inside index.html. There can only be one <ng:view> in a document (more on that later). For example, here is the wine-list.html template:
<ul ng:controller="WineListCtrl">
    <li ng:repeat="wine in wines">
        <a href='#/wines/{{ wine.id }}'>{{ wine.name }}</a>
    </li>
</ul>
The WineListCtrl and WineDetailCtrl controllers provide access to the data using a service defined in services.js as follows:
angular.service('Wine', function ($resource) {
    return $resource('api/wines/:wineId', {}, {
        update: {method:'PUT'}
    });
});
Services provide a great way to abstract your data access logic, and to easily change it without impacting the rest of the application. For example, you could easily change the Wine service to use a Mock service instead of a RESTful service.

Impressions

I was able to build this application in a very limited amount of time with no prior knowledge of the framework. The data-binding implementation is nice, and, in general, the amount of boilerplate code you have to write is very limited. Frameworks are often a matter of style and personal preferences. Angular.js takes a more declarative approach than other frameworks. Based on this initial experience, I would also describe it as more prescriptive: I didn’t have to spend a lot of time wondering what was the best way to do things as Angular.js tends to have clear utilization patterns. I haven’t spend enough time with the framework to determine if that comes at the cost of less control, and I’d love to hear what other people are thinking.
The only problem I ran into while building the application is the “one route / one view” coupling I alluded to above. As suggested in this thread, you can use <ng:include> to bind partials in the page. The same thread also indicates that multiple <ng:views> per route definition will be supported in the future. I was able to handle the simple UI requirements of this app using one <ng:view> and one <ng:include>. For more complex applications, I’d love the routing infrastructure to allow me to easily and arbitrarily add, remove, change, or animate different elements of the DOM when the route changes in order to support deeper UI transformations. I’m sure there are ways to do this. If Angular.js folks are reading this post, I’d love to hear what they think and what’s coming.

Download

The application is available on GitHub here.
You will need the RESTful services to run this application. A PHP version (using the Slim framework) is available as part of the download. If you want to test the application with a Java back-end, you can download the Backbone version here, and reuse its JAX-RS back-end.



Retweet this





Share on Facebook




Follow @ccoenraets
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");

1 comment: