Part 2: A Sample Todo List App With Padrino and AngularJS – Setting Up Padrino
Note: This tutorial assumes you are familiar with managing your Ruby environment to setup gems and Gemfiles. It assumes you have deployed a couple of Rails applications (perhaps even by reading the Rails tutorial before and are now exploring other frameworks.
Part 1 of this tutorial covers setting up the backend API with Padrino.
Note: The source code for this repository is at this link: padrino_superhero_todo
In part 2 of this tutorial, we’re going to focus on getting the front end portion working with AngularJS.
Step 1 – Setup the view to create / update / delete the Todos
We’re going to setup a Padrino controller so the user can navigate to a view where we’ll load AngularJS resources to call the API and perform CRUD (create, read, update, delete) actions.
In app/controllers, let’s add a controllers.rb file:
PadrinoSuperheroTodo::App.controllers :superheroes do
get :index do
@superheroes = Superhero.all
render 'superheroes/index'
end
get :api_index do
render 'superheroes_api/index'
end
end
Then you can navigate to a route http://localhost:3000/superheroes/api_index which is where we’ll build the interface to operate on the API.
Step 2 – Setup the AngularJS controller
We’ll be building our functionality using a controller, module, and factory.
Modules – “An angular module is a container for different parts of your application – controllers, services, filters, directives, etc.” (source: Angular Documentation)
Factories – “A factory is a function which returns any object.”
sources: StackOverflow and this StackOverflow thread
In app/assets/javascripts, we’ll create a file called superhero_api.js.
This is the entire code. Let’s walk through it.
'use strict';
(function(angular) {
function ApiAction($resource) {
return $resource(
'/api/:id',
{id: '@id'},
{
get: {
method: 'GET',
isArray: true,
},
update: {
url: '/api/update',
method: 'PUT',
isArray: true,
},
create: {
url: '/api/create',
method: 'POST',
isArray: false,
},
delete: {
url: '/api/delete',
method: 'DELETE',
isArray: false,
},
},
);
}
function superheroCtr($scope, ApiAction) {
var heroes = ApiAction.query();
$scope.superheroes = [];
heroes.$promise.then(
function(data) {
angular.forEach(data, function(item) {
$scope.superheroes.push(item);
});
},
function(data) {
//if error then...
},
);
$scope.appendSuperheroFields = function() {
var i = $scope.superheroes.length + 1;
$scope.superheroes.push({id: i, age: '', superhero_name: ''});
};
$scope.updateSuperhero = function(id, name, age) {
var single_hero = {id: id, superhero_name: name, age: age};
ApiAction.update(single_hero, {});
};
$scope.createSuperhero = function(name, age) {
heroes = ApiAction.create({superhero_name: name, age: age}, function(
data,
) {});
};
$scope.deleteSuperhero = function(id) {
heroes = ApiAction.delete({id: id}, {});
};
}
var superheroApp = angular.module('superheroApp', ['ngResource']);
superheroApp.controller('superheroCtr', [
'$scope',
'ApiAction',
superheroCtr,
]);
superheroApp.factory('ApiAction', ['$resource', ApiAction]);
})(angular);
Step 3 – Setup the AngularJS API resources
In the following snippet, we setup our container module to house the controller and factory. We include ‘ngResource’ to give us access to the $resource object to interact with our backend Padrino API.
var superheroApp = angular.module('superheroApp', ['ngResource']);
superheroApp.controller('superheroCtr', ['$scope', 'ApiAction', superheroCtr]);
superheroApp.factory('ApiAction', ['$resource', ApiAction]);
The following code snippet sets up the behavior that Angular needs to interact with the Padrino API to do CRUD operations.
function ApiAction($resource) {
return $resource(
'/api/:id',
{id: '@id'},
{
get: {
method: 'GET',
isArray: true,
},
update: {
url: '/api/update',
method: 'PUT',
isArray: true,
},
create: {
url: '/api/create',
method: 'POST',
isArray: false,
},
delete: {
url: '/api/delete',
method: 'DELETE',
isArray: false,
},
},
);
}
Step 4 – Setup the AngularJS controller actions
In the following snippet, we have appendSuperheroFields, updateSuperhero, createSuperhero, and deleteSuperhero.
For appendSuperheroFields, when the user clicks the “Add Superhero” button on the html view page, a blank row is added to a table so the user can fill out new information (name and age) for a superhero. We’ll need to add some special code to the HTML view page to make this happen, which will be described later in this tutorial.
For updateSuperhero, this calls an update action on the API and passes an edited superhero’s name and age data.
createSuperhero calls a create action on the API to add a new superhero and their data to the database.
deleteSuperhero calls a delete action on the API to delete a superhero from the database.
The $scope resource enables AngularJS to keep track of what to display on the view so it can automatically update itself without you having to write too much extra Javascript code and associated HTML markup. For example once $scope.appendSuperheroFields is called, a new HTML row will be added (without you having to do anything) and you’ll see it appear in your browser.
If you were doing this with something like jQuery, you’d have to append some HTML markup using jQuery code and it would take a lot more work to do this.
$scope.appendSuperheroFields = function() {
var i = $scope.superheroes.length + 1;
$scope.superheroes.push({id: i, age: '', superhero_name: ''});
$scope.updateSuperhero = function(id, name, age) {
var single_hero = {id: id, superhero_name: name, age: age};
ApiAction.update(single_hero, {});
};
$scope.createSuperhero = function(name, age) {
heroes = ApiAction.create({superhero_name: name, age: age}, function(
data,
) {});
};
$scope.deleteSuperhero = function(id) {
heroes = ApiAction.delete({id: id}, {});
};
};
Step 5 – Mix AngularJS into the view
In app/views/superhero_api/index.html.haml, we create the following markup.
The important things to note are the ng-click directives. Note they call the functions described above in our Angular controller (createSuperhero, updateSuperhero, etc.) when that form element is clicked.
The ng-repeat directive also automatically creates more table rows for us so we don’t have to include any extra markup or use some kind of loop construct in JavaScript to append extra HTML.
%div#section{"ng-app"=>"superheroApp", "ng-controller"=>"superheroCtr"}
%form{"ng-submit"=>"superheroSubmit($resource)"}
%table
%tbody
%tr
%th SH Name
%th SH Age
%th
%th
%th
%tr{"ng-repeat" => "superhero in superheroes track by $index"}
%td
%input{"type"=>"text", "value" => "{{superhero.superhero_name}}", "ng-model" => "superhero.superhero_name" }
%td
%input{"type"=>"text", "value" => "{{superhero.age}}", "ng-model" => "superhero.age" }
%td
%input{"type"=>"submit", "value"=>"Create New Hero", "ng-click" => "createSuperhero(superhero.superhero_name, superhero.age)"}
%td
%input{"type"=>"submit", "value"=>"Save Changes", "ng-click" => "updateSuperhero($index+1, superhero.superhero_name, superhero.age)"}
%td
%input{"type"=>"submit", "value"=>"Delete Superhero", "ng-click" => "deleteSuperhero($index+1)"}
%tr
%td{:colspan=>3}
%input{"type"=>"button", "value"=>"Add Superhero", "ng-click" => "appendSuperheroFields()"}
Note: The source code for this repository is at this link: padrino_superhero_todo
That’s pretty much all you have to do. In a future post, if I feel like enough people are interested, I might blog about testing in AngularJS or setting up a more restful API with the $resource object. The above is pretty basic and is just intended as a demonstration to show you how you can make an AngularJS front end work with a Ruby backend API.