Outline
One of the most handy features of rails is its asset pipeline which runs on
sprockets. This is what allows us
to use the require
syntax at the beginning of our javascript/css files so
that they'll be concatenated in production. It also allows us to easily drop in
gems to compile any assets that need processing (such as less or coffeescript).
You can read more about the asset pipeline at this official Rails guide.
Using Bower to manage dependencies
Bower is a command line utility that lets us manage our
front-end dependencies with a bower.json
file (think Gemfiles for front-end).
It also integrates with the asset pipeline pretty well. Bower requires Node.js
and git, so be sure to have git and npm on your system
and run npm install -g bower
if you haven't already.
bower init
to generate our initial bower.json file. You can
just keep hitting enter and go with the default options.
.bowerrc
file in the root our Rails project. This is a
configuration file for bower where we'll specify where we want our dependencies
installed. We'll save them in vendor/assets/bower_components
by providing the
following configuration:
{
"directory":"vendor/assets/bower_components"
}
bower install angular angular-ui-router bootstrap --save
to install our frontend dependencies. The
--save
flag automatically adds them to bower.json
.
Now that we have our dependencies installed locally, we can use sprockets
directives to require
them in application.js/css. Sprockets will
automatically require the right file based off of the package name, as long as
that package's bower.json
has its main
attribute set properly. Let's update
application.js
and application.css
, and then remove the cdn links in our
layout.
angular
and angular-ui-router
in application.js
//= require angular
//= require angular-ui-router
//= require_tree .
If you already tried *= require bootstrap
in app/assets/stylesheets/application.css
,
you may have noticed that your Rails app throws an exception. This is because
Bootstrap's bower.json
has a .less file in its main
attribute, which our
project doesn't currently support. We can get around this by requiring the path
to the bootstrap.css
file in the bower_components folder instead (you won't
need the bower_components
as part of the path as sprockets should
automatically look in vendor/assets/bower_components
).
bootstrap/dist/css/bootstrap
in application.css
*= require bootstrap/dist/css/bootstrap
*= require_tree .
<head>
section of our layout so it looks
like the rails default:
<head>
<title>FlapperNews</title>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
<%= csrf_meta_tags %>
</head>
Now when we load the page, all of our javascript and css dependencies should be loaded from our vendored components, and they'll be concantenated and minified with the rest of our assets in production.
Note that if you want to use implicit dependency injection in angular, you'll want to install the ngannotate-rails gem so your javascript won't break during minification.
Avoid installing front-end dependencies in multiple places when you're using
bower, as this could lead to the wrong file being required (for example, if
you had the angularjs-rails
in your Gemfile
AND angular
in your
bower.json
).
There's also a bower-rails
gem if you prefer a more Gemfile
-like syntax to your bower.json
and rake
tasks that wrap the bower
commands, however, this will be another dependency
in your Gemfile
, and also requires Bower to be installed via npm.
Moving Angular Templates into the Asset Pipeline and Structuring the javascripts
Folder
Right now all of our Angular templates are inlined in our application's layout.
This is fine for now, but can become hard to organize as we add more views to
our app. Luckily, there is a gem called angular-rails-templates
that takes html templates and compiles them into javascript files that insert
our templates into Angular's $templateCache.
This allows us to move our templates into the app/assets/javascripts
folder
while referencing them in our app using the same syntax. We'll be grouping
our files in the javascripts
folder by feature. Having templates and
javascript in the same folder may seem weird if you're used to Rails' directory
structure, but it will make our project easier to navigate. See #1 in this list of top AngularJS mistakes.
Keep in mind that because this gem is changing HTML files to to javascript files, you want to avoid having two assets of the same basename (for example,
posts.html
andposts.js
) in the same folder as sprockets will end up overwriting one of the files.
Let's install the angular-rails-templates
gem and move the templates for the
posts
and home
state from within the <script>
tags in
application.html.erb
to HTML files in our javascripts folder. We can then
remove the script tags in our layout, leaving us with a pretty clean
application.html.erb
.
gem 'angular-rails-templates'
to your Gemfile
bundle install
angular-rails-templates
in your application.js
after angular
templates
module into our Angular app
angular.module('flapperNews', ['ui.router', 'templates'])
.config([
...
home
and posts
within the javascripts
folder
/home.html
template from application.html.erb
to a new file _home.html
inside the home
folder you just created.
/posts.html
template from application.html.erb
to a new file _posts.html
inside the posts
folder you just created.
Once we move our templates, we'll need to update the templateUrl
s in
app.js
in order to reflect the new paths to our templates. Note that the
leading slash has been removed.
The leading underscore we used in our new HTML file names is a common practice used to show that the file contains a partial and not a full html file (it also helps us avoid asset path collisons mentioned earlier).
You are also able to change the HTML files to have the .html.erb
extension
and use erb tags to include images from the asset pipeline, but be aware that
the ruby environment in the assets folder is separate from the rest of the
application, and you should only be using sprockets helpers such as
image_path
or asset_path
when mixing javascript and erb.
templateUrl
for the home
state in app.js
to home/_home.html
templateUrl: 'home/_home.html',
templateUrl
for the posts
state in app.js
to posts/_posts.html
templateUrl: 'posts/_posts.html',
While we're in app.js
, let's take the time to split out our Angular services
and controllers into separate files as well.
posts
service from app.js
to posts/posts.js
angular.module('flapperNews')
.factory('posts', [function(){
...
MainCtrl
from app.js
to home/mainCtrl.js
angular.module('flapperNews')
.controller('MainCtrl', [
'$scope',
...
PostsCtrl
from app.js
to posts/postsCtrl.js
angular.module('flapperNews')
.controller('PostsCtrl', [
'$scope',
...
The resulting javascripts
folder should look something like this:
javascripts
||- home/
||__||- _home.html
||__||- mainCtrl.js
||- posts/
||__||- _posts.html
||__||- posts.js
||__||- postsCtrl.js
||- app.js
||- application.js
This should leave our app.js
file with just our app declaration and
config function. If we check our app in the browser, our app should still be
fully functionaly with all of our files organized and going through the asset
pipeline. Now that we have our front-end fully integrated with the asset
pipeline, let's start building an API with Rails for our app to interface with
so we can get persistent data.