AngularJS is an awesome framework for easily creating rich, client-side applications. Its powerful bindings allow you to do a lot with very little code. Here I show how to integrate Angular with a Rails app.
AngularJS is a framework for creating rich client-side applications. In this episode we’ll demonstrate some of what it can do and how to integrate it into a Rails application. The application we’ll build will be a simple raffling app where we can enter names and draw random winners. We used a similar app for demonstrating Backbone and Meteor in previous episodes so this will provide a good comparison. We’ll start by creating a Rails application with a RaffleController with an index action. We’ll also remove the default static index page.
Next we’ll set this action up as the root action.
We already have some CSS written for this application so we’ll add that to the raffle.css.scss file. We’ll also wrap the call to yield in the layout file in a div so that we can reference it from our stylesheets.
We now have a single-page application, although it doesn’t do anything. Let’s add AngularJS to it. There are several ways that we can do this: one is to download the relevant files from the AngularJS website and add them to our application’s /vendor directory. Alternatively we can use a gem and we’ll add one called angularjs-rails to the assets group of our gemfile. As ever we need to run bundle to install it.
We can now start to use Angular in our index template. At the top of this page we want a form where the user can add a name to go into the raffle so we’ll give it a text field. One of the cool features of Angular that we can use here is its two-way binding. We can bind a form field to some model data by adding an ng-model attribute whose value is a name.
We’ve used newEntry.name here. One way to think of this is that newEntry is the object we’re working with in this form and name is the attribute that we’re managing through this text field. To see this binding in effect we display its value which we do by using double curly brackets. We’ll start up our application and load its home page now to try it out. As we start typing in the text box the value is instantly bound and what we’ve typed is repeated where we display the value.
We’ll write this controller in the raffle CoffeeScript file. This can be a simple function but in order for it be accessible we’ll use the @ symbol so that AngularJS can see it.
We now have an unordered list and we want to display each item in the collection in it. We’ve done this by adding an ng-repeat attribute to its li element and giving it a value of entry in entries. This loops through our entries array and repeats the element for each one and display its name. When we reload the page we’ll see the list of entries that we entered.
Next we’ll make changes so that when a new entry is entered in the text box and the form is submitted it’s added to the list. We’ll add a button to the form and hook into the submit event by adding an ng-submit attribute to the form. This will trigger a function on the scope that we’ll call addEntry.
We’ll write this function in our CoffeeScript file. In it we just need to push the new entry to the array using $scope.newEntry which is the object that the form creates. We then set this to an empty object to clear the text box.
When we reload the page now the button is there and when we type an entry and submit the form it’s added to the list without a page reload.
Next we’ll add a “Draw Winner” button below the list which will select a random entry and mark it as a winner. First we’ll add the button in the view below the list. To listen to its click event we use an ng-click attribute with a function.
We’ll write that function now. In it we pick a random entry and set its winner property to true.
In the view we’ll add some code to show winning users as winners.
Here we use the ng-show attribute which only displays the element it’s attached to if its value returns true. If we reload the page now then click “Draw Winner” a random entrant should be marked as a winner.
We can select multiple winners and we’d like the most recently-selected winner to be displayed in red. To do this we’ll need to keep track of the most-recent winner so we’ll create a lastWinner variable in our scope.
Now we’ll add a class to the span tag for the last winner. We use the ng-class attribute for this and give it an expression with single curly braces so that the highlight class is added if the entry is the last winner.
When we reload the page now and click the “Draw Winner” button a couple of times we’ll see that the most recent winner is shown red just like we want.
The next thing we want to do is change the behaviour of the “Draw Winner” button so that it doesn’t re-select a current winner, which it currently does. We want to choose only from the entrants who have yet to win so we’ll create a pool of the entrants who haven’t yet won and choose a winner from those.
We do this by using Angular’s forEach to loop through the entries and adding the ones what haven’t won to the pool. We then select our winners from this pool.
We’re pretty much done with the client-side behaviour of our application but any entrants we add or winners we pick are reset when we reload the page and aren’t persisted to the database. We’ll modify our app so that it communicates with the Rails back end and persists the changes we make to the database. We’ll generate an Entry resource with name and winner attributes then migrate the database.
We want some initial data to work with so we’ll create some entries in the seeds file. We can then run rake db:seed to add them to the database.
We need to make a JSON API for this data so that we can communicate with AngularJS and we’ll write this in the EntriesController.
To use this in our application we’ll need to restructure the CoffeeScript code a little. We need to define the resource as a dependency and to do this we’ll need to make a module which we do by calling angular.module and giving it a name. We need to define ngResource as a dependency for this module to allow us to take advantage of the new file that we included and we’ll store the result of this in a variable. Now that we have our Angular Resource dependency set up we can pass the resource as an argument to our controller. This is a function that we can call to return an object that allows us to communicate over a REST API. The first argument passed to this function is the URL to the API and we use /entries/:id here which makes :id a parameter. The second argument specifies the default parameters and we’ll set the id to the @id which will be the current object’s id. If we’re passing an Entry object in to here which has an id assigned then this will be used in the URL otherwise it’s skipped and /entries is used. The third argument allows us to specify additional actions that we want to call on the API. The default values handle most of what we need except for the update action so we’ll add this and set its method to PUT.
If you want to learn more about how Angular Resource works take a look at the documentation which describes the arguments that we supplied and which shows the default actions which are get, save, query, remove and delete. We can trigger each of these actions as functions so we’ll replace the static list of entries in our code with a call to Entry.query() which will call our API that will fetch all the entries from the database.
Since we’ve given our application a module name we’ll need to set this as the name in our layout file which we do by setting a value on the ng-app attribute.
When we reload the page now we should see the list of names that we added to the database from our seed data.
When we create an entry we want to save it before we add it to the list. We do this by calling the Entry.save function which will submit a POST request and trigger the create action in our Rails app.
When we mark an entry as a winner we need to update it in the database and we can do this by either calling Entry.update and passing in the entry or alternatively calling entry.$update as our entry is already a resource object that we can trigger actions on.
To see if this works we’ll reload the page again, add a new entrant and mark some entrants as winners. When we reload the page again the changes should persist.
This has worked. The data has persisted as it was stored in the Rails database.
We want to refactor resources out into services. A service is something that we can pass into our controller (scopes and resources are services). We want to pass the Entry resource into our controller instead of creating the resource there. To do this we can call app.factory to generate a resource that we’ll call “Entry”.
# rest of code omitted.
It’s important to understand what’s going on here. Angular does dependency injection which means that it takes a look at the arguments that this function accepts and gives it those services depending on the name of the argument. In this case the Entry argument will be trigger the Entry factory function and use whatever it returns. This means that we could even swap the two arguments and our application would still work as the arguments are supplied based on the name that we pass in.
This tells AngularJS what the dependencies are so that if the names do change it won’t matter. We have to do this for every function that accepts services, for example the one we have for creating resources.
Another solution is to modify our production environment’s configuration file and configure how the compression is done.
Doing this means that the names won’t be changed, although the minification won’t be as effective. Whichever approach you take it’s a good idea to thoroughly test your Angular applications in the production environment to make sure that there are no problems when it goes live.
Our application is now complete and AngularJS made it very easy to make a dynamic client-side app and it all syncs up nicely with our Rails back end. We’ve only scratched the surface of what AngularJS can do in this episode. It has routers, views and much more. The learning section of the size is well worth taking a look at for more details and the Egghead website has some free screencasts covering Angular. There are also several other Ruby gems available to help to integrate Angular into your Rails applications, for example AngularJS Scaffold.