-
Versions Presented: Angular: 1.5.0, Rails: 4.2.5.1, Ruby: 2.3.0, Node: 5.4.1
The purpose of this project is two-fold:
-
Present a series of demonstrations that beginners can use to learn the basics of Angular 1.X
-
Propose a theory on the optimum way to combine traditional Ruby on Rails applications with one or more complex Angular applications.
If you are new to Angular and would like to see examples of some of its core features, and a few popular third party tools, written in adherence to most of the stylistic standards recommended by the community then I hope this project can help you out!
Furthermore, if you are thinking of starting a Rails project that employs Angular and are wondering how best to structure your application I hope this project can be of use to you. Ideally I would like it to serve as a discussion on the best way to combine traditional rails applications with Angular. The idea is to allow flat pages to be served up by Rails alongside any number of complex Angular applications. Also, if you want to add Angular to an existing Rails application I believe this project could serve as a guide on how best to do that since each page served up by the Rails application is its own little Angular application.
Please feel free to report any issues or ways that the ideas presented could be improved. If you have any interesting Angular directives, filters, or other code you’d like to add then please feel free to submit a pull request as well. All comments and additions are welcome!
These installation instructions are aimed at OSX users, but they should work as a satisfactory guide for anyone familiar enough with their Rails setup. They also presume a general familiarity with Rails 4 and testing methodologies.
If you need to get started with Rails on OSX here is a guide: Rails Setup.
NPM is also used to setup some packages for the test suite so ensure you have it and Node installed and running: Node JS/NPM Setup.
You will also need to ensure that you have the correct version of ruby installed in whatever manager you use (RVM, rbenv, etc..) or many of the commands below will fail. The version you need as of the writing of this document is 2.3.0.
To install simply download the repository into a folder on your machine, then from console type:
make install
That will install all the gems and packages needed for the application. Then type the following:
rake db:migrate
That will migrate your local sqlite database and populate it with the tables necessary to run.
Finally test that things are setup by running a rails server:
rails server
and visit your local copy of the application by opening a browser and visiting localhost:3000.
In order to run the integration tests you will need to install protractor globally:
npm install -g protractor
It may also be required to update your Java Development Kit JDK7: Oracle Link
If you have any problems getting the app installed or the test suite running please do not hesitate to write up a github issue or send me an email at [email protected]. I would very much like to know if you have any problems getting this up and running and how I can make it easier for people, and if I can help you do so I am glad to spend the time.
Once you’ve followed all the steps above you should be able to run the test suite by opening two terminal tabs. If you intend on running the integration tests, in the first tab you need to ensure selenium webdriver is running by typing
make selenium
In the next tab you can run any of the following commands to run different parts of the test suite
make test # run the entire test suite make test.unit # run just the mocha angular unit tests make test.integration # run just the protractor integration tests make test.spec # run just the rspec rails unit tests
If your tests fail, or the process crashes for whatever reason, it’s possible you will have an errant rails server running in the background, or some users with the name ‘Test’ on your Forms page. If this happens simply run the following command to clean things up:
make test.clean
If you see a No such process
error, don’t worry. That just means you didn’t have a rails server running in the background and all is good.
So now that you have things (hopefully) up and running let me get into the theories presented on how best to structure Angular with Ruby on Rails. Before getting to the overall structure and architecture of the application I would like to point out some of the things I’ve done that I feel are interesting and unique.
One of the things I wanted to accomplish with this project was to ensure that all the Angular code was written in javascript with no ruby decoration to ensure that it could be easily reused with little to no modifications. This is why none of the Angular related files have a .js.erb
extension. Of course problems arise there if you want to use any of the assets in the asset pipeline within your Angular code, especially when those assets have been compiled with a unique random fingerprint. To get around this I created a script called asset_paths.js.erb
that is included within application.js
whose purpose is to create a javascript object called asset_paths
that stores the results of calling the rails method asset_path
on every asset in the specified folders (currently fonts
, images
, and templates
). The code for this is quite compact so here it is in its entirety:
var asset_paths = { }; <% asset_folders = %w(fonts images templates) asset_folders.each do |asset_folder| Dir.glob("app/assets/#{asset_folder}/**/*").select { |f| File.file?(f) }.each do |asset| relative_asset = asset.gsub("app/assets/#{asset_folder}/", '').gsub('.erb', '') %> asset_paths['<%= relative_asset %>'] = '<%= asset_path(relative_asset) %>'; <% end end %>
So, if you want to use one of the assets from the pipeline within your javascript you can simply reference the relative asset path as a key within the asset_paths
object. For example here is a chunk of code from animate.controller.js
file accessing the templates assets for the “Slide Animation” demo:
vm.people = [ { name: 'Derek Sweet', url: asset_paths['pages/animate/derek-sweet.html'] }, ... ];
Another interesting achievement of this project is to (within reason) give you access to your Rails models from javascript. This is achieved by defining an internal rails API in RESTful fashion and using the Angular $resource
factory to create corresponding javascript objects for your models. This is exactly how $resource
was intended to be used so this is by no means innovative, but I hope that anyone struggling to connect the two may see a pattern here they can employ.
Within the angular/models
folder there are a series of files that are all included within application.js
by default. As an example please take a look at the User.model.js
file. Within you can see a hash defining a number of settings that directly relate to the parameters of the $resource
factory. Here is the contents of that file:
var User = { url: '/api/v1/users/:id', paramDefaults: { id:'@id' }, actions: {}, options: {} };
These are useless on their own, of course, so now take a look at the data-store.service.js
file that defines a service that creates objects for each model. Here is the relevent part of that file:
var dataStore = { User: $resource(User.url, User.paramDefaults, User.actions, User.options), ... };
Any Angular applications that you want to be able to access your Rails models need to simply include the data-store
service. For example if you want to load a javascript version of a record stored in the users
table simply type:
my_user = dataStore.User.get( { id: 'Joe' } );
This will create a javascript object that holds all the data for the user with the first name ‘Joe’. Note: The users are identified by their first names in this application, not IDs. See the Rails API implementation for the corresponding endpoint.
If you want to change a property and save the user, simply type:
my_user.email = '[email protected]'; my_user.$save();
To delete a user, simply type:
my_user.$remove();
To see the API side of things that actually persists the data changes check out app/controllers/api/v1/users_controller.rb
. It is a pretty standard RESTful API controller except in the show
and update
actions it identifies records by first name, not ID.
Also, feel free to write any javascript class methods within the hash that you feel relates to the model you are defining them on. For example:
var User = { url: '/api/v1/users/:id', paramDefaults: { id:'@id' }, actions: {}, options: {}, myClassMethod: function(param1, param2) { // Place javascript code here }
You can then access your class method by simply typing dataStore.User.myClassMethod('param1','param2');
. It is important to realize, however, that this is just an organizational pattern and not really a true class method. It is just a nice place to put all your class related methods and you can even pass in instances of the class as arguments if you like.
The Rails side of things in this application has been kept as thin as possible. It is pretty much a blank rails project with a very simple API added, a User model, and a controller and some views for serving up the various pages. Most of those pages are individual Angular applications that we will get into shortly, and one of those pages is a standard Rails page of references.
If you take a look at pages_controller
you’ll see that each page couldn’t be more simple on the rails side. All the work is done by the views in this application. Each of the pages serving up the Angular applications (directives
, filters
, forms
, routes
, animate
, references
) have a nearly identical structure. Let’s analyze one of them, the directives
page, and I will point out differences in the other pages after that.
Take a look at the app/views/pages/directives.html.erb
page. You’ll notice that the very first thing it does is include a bunch of angular javascript files. Each of the Angular applications load only the objects it needs to work, so any controllers, decorators, directives, filters, models, or services. I am not a fan of just loading up all the angular resources available in application.js
, I much prefer specifying exactly what is needed for each Angular application.
If you take a look at the app/assets/javascripts/angular/
folder you’ll notice it has a structure a little similar to a rails app
folder. Within the controllers/pages/
folder you’ll notice there is a .controller.js
file for each page in pages_controller
that we want to be an Angular application. Let’s take a look at directives.controller.js
You’ll notice that the very first thing each controller file does is specify a module name in a variable called moduleName
. This is necessary so that every other component that comes after it knows the name of the module being created and what it should attach itself to. Take a look at any of the controllers, decorators, directives, filters, models, or services and they will all reference moduleName
at some point. It is the glue that brings everything together no matter what you choose to include on a given page. Many of the controller files also specify a controller name which is mostly for convenience.
If you start having very complex modules with config
and run
commands then you may want to seperate them out into a modules
folder. Just be sure to define the moduleName
variable there and always include them first in your views. This is why the controllers are always the very first file included in each view because they set up the module and define the moduleName
variable that each of the other files depend on existing.
After the Rails view directives.html.erb
loads the directives.controller.js
file, it then loads the sweet-show-hide
, sweet-current-time
, sweet-make-blue
, and sweet-list
directive files because all of those directives are used on this page. Finally it includes the exception-handler
decorator as every page does to ensure that any Angular errors are displayed annoyingly in an alert box instead of just being printed to the console.
If you’re familiar with how Angular applications work the rest of the directives.html.erb
view should make complete sense. You’ll see that each of the directives loaded at the top of the page are eventually used, as well as some nice demonstrations of a few directives that come pre-built with Angular.
Any pages that need to read or write data to the database through the Rails API, like the “Forms” page, must include the data-store
service.
Finally, any templates that need to be used by your custom directives, like the showHide.html
template employed by the sweet-show-hide
custom directive, should be placed within app/assets/templates/angular/directives
. Any templates that need to be loaded by routers, modals, or animations should be placed within app/assets/templates/{controller_name}/{action_name}
Thanks for reading! I really hope this project is of some help to people. Once again if you have any tips or modifications you’d like to suggest please feel free to report an issue, fire me an email at [email protected], or submit a pull request. All comments and additions are welcome!
“Beginning AngularJS” by Andrew Grant - Amazon Link
“Pro AngularJS” by Adam Freeman - Amazon Link
“Angular Style Guide” by John Papa - Github Link
“dirPagination” by Michael Bromley - Part of the angularUtils package - Github Link
“Countries” by Hexorx - Used to obtain countries data - Github Link
“Adventures in Angular” - DevChat Link