Skip to content

Instantly share code, notes, and snippets.

@theotherzach
Last active December 29, 2015 05:09
Show Gist options
  • Save theotherzach/7619655 to your computer and use it in GitHub Desktop.
Save theotherzach/7619655 to your computer and use it in GitHub Desktop.

This tutorial is designed to give backend developers a place to put Angular to work for them, today. It starts with a hypothetical site which has table of records rendered server side. The tutorial gently guides the reader through moving the rendering from the server and to the client with Angular and uses the momentum from that to add two new features:

  • Sort table by column
  • Filter table by input field

The Server Side Start

This is the server side template which renders a collection of songs.

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Artist</th>
      <th>Album</th>
      <th>Time</th>
      <th>Download</th>
    </tr>
  </thead>

  <tbody>
    <% songs.each do |song| %>
      <tr>
        <td><%= song.name %></td>
        <td><%= song.artist %></td>
        <td><%= song.album %></td>
        <td><%= song.time %></td>
        <td><%= link_to "Download", song.file %></td>
      </tr>
    <% end %>
  </tbody>
</table>

The Build Process

This tutorial assumes that you have access to frontend tooling for linting, compilation, concatenation, and minification. We use lineman.js at test double for our build process and quite like it.

Render Dummy Data on the Client

Our first step is the Folger's challenge. We're going to move the rendering to the client without changing any functionality. Before writing any JavaScript, you'll need to pull in the angular.js library.

Add the ng-app attribute to a parent element of the table. Scope it as close as possible if you're nervous or just add it to the html tag if you don't want to think about it.

<html ng-app="mixtape">

We're going to change that server side template into a flat HTML file with Angular markup. (A video on the ng.repeat filter)

<table ng-controller="songListCtrl">
  <thead>
    <tr>
      <th>Name</th>
      <th>Artist</th>
      <th>Album</th>
      <th>Time</th>
      <th>Download</th>
    </tr>
  </thead>

  <tbody>
    <tr ng-repeat="song in songs">
      <td>{{ song.name }}</td>
      <td>{{ song.artist }}</td>
      <td>{{ song.album }}</td>
      <td>{{ song.time }}</td>
      <td><a href="{{ song.download }}">Download</a></td>
      </tr>
  </tbody>
</table>

We declare our angular module.

mixtape.js

angular.module("mixtape", []);

Add our controller. (A video on controllers.)

controllers/song_list_ctrl.js

angular.module("mixtape").controller("songListCtrl", function($scope) {
  return $scope.songs = app.songFixture;
});

A temporary fixture file so we won't have to worry about an ajax request quite yet.

fixtures/song_fixture.js

window.app = app || {};

//Temporary fixture
window.app.songFixture = [{
  name: "My Love",
  artist: "The Bird and the Bee",
  album: "Ray Guns Are Not Just The Future",
  time: "3:46",
  download: "https://example.s3.amazonaws.com/path/02_My_Love.m4a",
  url: "http://example.com/songs/1.json",
  path: "/songs/1"
},
{
  name: "Team",
  artist: "Lorde",
  album: "Pure Heroine",
  time: "3:13",
  download: "https://example.amazonaws.com/path/Lorde_-_Team__Clean_.mp3",
  url: "http://example.com/songs/30.json",
  path: "/songs/30"
}]

Our page should look identical other than us only displaying two songs.

Render Real Data on the Client

It's time to pull in real data and render it. First we'll need to expose restful routes with our server side code of choice.

Here are the routes I'm exposing for songs.

GET    /songs                     songs#index
POST   /songs                     songs#create
GET    /songs/new                 songs#new
GET    /songs/:id/edit            songs#edit
GET    /songs/:id                 songs#show
PATCH  /songs/:id                 songs#update
PUT    /songs/:id                 songs#update
DELETE /songs/:id                 songs#destroy

Next we'll need to download and include the separate angular-resource.js to get access to the $resource provider. Once we have that file included in our dependencies, we'll add it to our module declaration.

mixtape.js

angular.module("mixtape", ["ngResource"]);

Next we'll need an object that knows how to interact with those RESTful routes. (A video on services.)

models/song.js

// use "/songs/:id.json" in Rails.
angular.module("mixtape").factory("Song", function($resource) {
  return $resource("/songs/:id");
});

Point our controller at our Song object instead of the fixture.

controllers/song_list_ctrl.js

angular.module("mixtape").controller("songListCtrl", function($scope, Song) {
  return $scope.songs = Song.query();
});

Delete fixtures/song_fixture.js

We're now at feature parity with the server side code.

Feature Request: Sort on Columns

No change to our JavaScript for this feature. (Read about ng.filter:orderBy.)

<table ng-controller="songListCtrl">
  <thead>
    <tr>
      <th><a href="" ng-click="predicate = 'name'; reverse=!reverse">Name</a></th>
      <th><a href="" ng-click="predicate = 'artist'; reverse=!reverse">Artist</a></th>
      <th><a href="" ng-click="predicate = 'album'; reverse=!reverse">Album</a></th>
      <th><a href="" ng-click="predicate = 'time'; reverse=!reverse">Time</a></th>
      <th>Download</th>
    </tr>
  </thead>

  <tbody>
    <tr ng-repeat="song in songs | orderBy:predicate:reverse">
      <td>{{ song.name }}</td>
      <td>{{ song.artist }}</td>
      <td>{{ song.album }}</td>
      <td>{{ song.time }}</td>
      <td><a href="{{ song.download }}">Download</a></td>
      </tr>
  </tbody>
</table>

Seriously, that's it.

Feature Request: Filter on Input

Once again, no change to our JavaScript. (Read about filtering repeaters.) Here are the changes to the HTML in isolation.

Filter: <input ng-model="query">
...
    <tr ng-repeat="song in songs | orderBy:predicate:reverse | filter:query">
...

And the whole file.

Filter: <input ng-model="query">

<table ng-controller="songListCtrl">
  <thead>
    <tr>
      <th><a href="" ng-click="predicate = 'name'; reverse=!reverse">Name</a></th>
      <th><a href="" ng-click="predicate = 'artist'; reverse=!reverse">Artist</a></th>
      <th><a href="" ng-click="predicate = 'album'; reverse=!reverse">Album</a></th>
      <th><a href="" ng-click="predicate = 'time'; reverse=!reverse">Time</a></th>
      <th>Download</th>
    </tr>
  </thead>

  <tbody>
    <tr ng-repeat="song in songs | orderBy:predicate:reverse | filter:query">
      <td>{{ song.name }}</td>
      <td>{{ song.artist }}</td>
      <td>{{ song.album }}</td>
      <td>{{ song.time }}</td>
      <td><a href="{{ song.download }}">Download</a></td>
      </tr>
  </tbody>
</table>

Next

We can build out more lightweight frontend features from here:

  • pagination
  • in-place CRUD
  • async, parellel file uploads

Conclusion

The goal of this tutorial was providing a recipe for making small changes to the functionality of an app with Angular. We wanted the changes to be small to give us a place to use Angular without rewriting the existing codebase which makes it easier to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment