Doing a Rails and Angular minimum viable product

12 Mar 2014

My first draft of this post was titled something like "here's what I learned in writing my first Angular app". But I realized that it was basic stuff that wouldn't help much if someone hadn't written already something in Angular and would be old news to anyone who had. And the section on "here's the stuff I put off until later" kept getting longer and longer. So now that's the primary focus of this post. First, here's some background info.

I run a site that tracks a bunch of military reading lists. It shows what's on the lists now, what used to be on the lists, and lets people track their progress in reading the books. It's been a great source of reading material over the past 5-6 years.

Every few weeks I'd come across a new revision of one of the lists and I'd cringe, because I'd know that adding this revision was going to be painful. The process of removing old books and section, adding new ones, and importing new books into the system involved a lot of page reloads that for some of the longer lists could take 5-6 seconds. And the Javascript code around that process had gone through many iterations and had grown crufty. So I decided to rewrite that part of the app in Angular.

My goal was get the rewrite done before I added any more revisions to any lists. This provided a sense of urgency because I'm always running across new lists and I wanted to add them. But instead I stuck them in the README and carried on with the port. This also gave me a distinct target. I knew I was done with version one when I could add a revision to a list. So I made a variety of compromises and shortcuts but that got me to the finish line.

Workflow-wise I made a list of features and started banging them out. These were all pretty simple, like "view all the sections", "add a new section", etc. Some of them I expanded as I started working on them. For example, "add a new section" grew into "display a text field and a 'add' button" followed by "post new section data to the server" followed by "clear the 'new section' text field after adding a new section". Once I had each small step working I'd squash it into my previous commit. As I learned more about the framework I stopped breaking up the features into such small chunks, but at the beginning it was nice to get a continual series of small successes.

So, here are the things I didn't do as part of version one of the app:

  • I didn't use Angular routes other than just a single default route. Since the Angular section of the app is all contained on one page it didn't seem necessary.
  • I didn't use Coffeescript because I don't know it and it doesn't seem like it would make a big difference in getting the rewrite done.
  • I didn't use the angularjs gem because I wanted to see exactly how the Javascript dependencies were being pulled in.
  • I didn't attempt to reuse my existing Rails controllers. Instead I made an api namespace and added new controllers. This led me to refactor some code out of my existing controllers, and it freed me from having to ensure that I didn't break the current stuff. And now I get the fun of deleting all the unused code!
  • I didn't break the Javascript parts of app into lots of files. Keeping it in one big (well, 250 lines) file made it easier to keep track of things and let me refactor more easily.
  • I didn't write any Angular tests. Oh the shame!
  • I avoided asset pipeline issues by using the Google CDN versions of Angular and, as mentioned above, by jamming all my JS into one file. This means more HTTP requests initially, but the browser will cache those files. And for a small admin tool this really doesn't matter. Update: Now that everything works I went back to fix this up. I added a second manifest, app/assets/javascripts/angular_application.js, that contains only the require directives that the Angular app needed. Then I removed my layout file's Google CDN javascript_include_tag calls and added a single javascript_include_tag for angular_application.js. Then I added that manifest to the precompile list in config/environments/production.rb, i.e., config.assets.precompile += ["angular_application.js"]. I'm still not quite sure how to handle the source map files. I thought about just deleting the directives, but I wanted to make them work somehow. So I edited the sourceMappingURL to be fetched from the document root, e.g., /angular-animate.min.js.map. Then I moved the source map files into public/ and added ProxyPass ! directives for them in the virtual host. So now the web server is serving those. Problem is that it'll be easy for them to get out of sync.

Despite all those deferred item, the app works great. I added the US European Command reading list - 103 books, including War and Peace! - and it was a lot easier and faster than before. Technically, I feel more confident about my ability to make changes to the code and I'm actually looking forward to it because I'll be learning more about a framework, not plowing through my own poorly structured Javascript.

If I had to pick one deferred item from the above list that I want to clean up it'd be the "put everything in one file" thing. I've got a custom directive that I want to get out of the main file. That'll also lead into the asset pipeline cleanup. I want to see the asset pipeline concatenating all the things without having name mangling issues.

Here are some notes from the original version of this post. active_model_serializers is great stuff and this excellent post from MichaƂ Kwiatkowski is well worth a read. I'm using ng-resource to manage resources and it's worked out fine. I got the occasional badcfg error but by working in small steps I could usually spot where I'd made a typo. And at one point I used ng-switch because I didn't know about ng-if.

Next step is to read more about Angular so's to use it more effectively. I'd welcome any book suggestions!