Connecting Rails to Angular - Making a Full Stack Web Application
I recently finished my final project for Learn Verified! It was an exciting moment, a culmination of everything I’ve learned in these past four months. My favorite moment came when I realized how to connect my rails database to the front end, which was built with Angular. Below you’ll find the steps with lots of code snippets. Peruse the full code here.
This article is quite technical and assumes you have working knowledge of rails and angular.
Connecting the Back End and Front End
A struggle I first had in building the front end of apps with Angular was that I couldn’t use rails helpers in my views. The front end and back end aren’t automagically connected anymore! To get the information from the database onto the front end, I had to serialize it and use $http.get
to grab and render JSON data with Angular.
The most interconnected part of this application is a feature which grabs a user’s data from Spotify and then renders a chart of the artist’s earnings on the front end (check out the whole project here). Here’s a breakdown of the process, in eleven easy steps:
Rails Back End
Here’s the steps of the rails part of the app.
First:
A user authenticates the Spotify API after clicking ‘Log Into Spotify’, which refers them to /users/auth/spotify
Second:
Oauth handles the Authorization Code Flow and returns a secure key to the backend, also logging a user into Devise or creating their account with their Spotify email.
# app/controllers/users
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def spotify
@user = User.from_omniauth(request.env["omniauth.auth"]
...
end
end
# in models/user.rb
class User < ActiveRecord::Base
...
def self.from_omniauth(auth) # grab data for user from Omniauth.
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.name = auth.info.name
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.access_token = auth.credentials.token
user.refresh_token = auth.credentials.refresh_token
end
end
Third:
The RSpotify gem (which is amazing) grabs the user’s top twenty artists, which I sliced to be only the top five artists.
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
...
spotify_user = RSpotify::User.new(request.env['omniauth.auth'])
top = spotify_user.top_artists.slice(0,5)
Artist.parse_from_user(@user, top)
Genre.scrape_genres(top)
end
end
Fourth:
The models extract the artist and genre data, saving it to the database and associating it with the user.
# Similar logic is used for the genres in the genre model
class Artist < ActiveRecord::Base
def self.parse_from_user(user, array)
array.each do |artist|
newbie = Artist.find_or_create_by(name: artist.name)
newbie.name = artist.name
newbie.image = artist.images[0]["url"]
newbie.popularity = artist.popularity
newbie.link = artist.external_urls["spotify"]
newbie.uri = artist.uri
newbie.followers = artist.followers["total"]
user.artists << newbie unless user.artists.include?(newbie)
newbie.save
user.save
end
end
end
Fifth:
Active Model Serializer and controllers render the JSON for each RESTful URL.
# in app/serializers/user_serialzer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :artists
end
Which renders JSON at /user/:id
(don’t forget to set the show method in the users controller and routes!)
{
"id": 1,
"name": "lukeymoo",
"artists": [
{
"id": 1,
"name": "Teebs",
"image": "https://i.scdn.co/image/787867c011ffcb68f684378c5bdbc7004e71cb55",
"streams": null,
"link": "https://open.spotify.com/artist/2L2unNFaPbDxjg3NqzpqhJ",
"followers": null,
"uri": "spotify:artist:2L2unNFaPbDxjg3NqzpqhJ"
}, ...
Sixth:
After all this data is processed, the user is redirected to the #/user/:id
page, where we pick up the front end. This required an override of Devise’s after_sign_in_path_for
method.
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
id = resource.id.to_s
'/#/chart/' + id || root_path
end
end
The Angular Front End
Seventh:
The ui-router for a user has a dynamic URL for each user, which is #/user/:id
.
angular // dependencies are ui.router and chart.js, among others
.module('app', [
'ui.router',
'chart.js',
])
...
.state('userChart', {
url: '/chart/:id',
templateUrl: 'userChart.html',
controller: 'UserChartController as user',
})
Eighth:
The UserChartController grabs the user ID from $stateParams, so it can call the right user’s data. This is almost identical to params[:id]
in Rails.
function UserChartController($scope, $stateParams, BackEndService, LastfmService) {
var userId = $stateParams.id
BackEndService /
.getUserArtists(userId)
.then(function(response) {
... // what to do with this data
})
}
Ninth:
The BackEndService calls to the /user/:id
and grabs the JSON data for that user, which is all their top artists. Here is the point of connection! This returns the JSON data mentioned above in step five.
function BackEndService($http) {
...
this.getUserArtists = function(id) {
return $http.get('http://localhost:3000/users/' + id)
}
}
And, finally, tenth!
The data that get returned are put into an array of names. For each artist, I call the Last.fm API to grab the number of total listens.
This is passed to a Chart.js chart, which renders the data on the front page! Which is as easy as:
function UserChartController($scope, $stateParams, BackEndService, LastfmService) {
...
$scope.labels = names; // names is an array of artist names pulled from the user data
$scope.data = [listens]; //listens is an array of the number of listens.
Because Angular-Chart-JS is so rad, creating that chart is done with calling a canvas object in the view.
See the full code of the UserCharts controller here. Or read about this entire final project here.