Ruby on Rails with Angular 8

Written by Bryan Villafane, Software Engineer

In this blog post we are going to learn the quickest way to integrate Angular 8 into a Ruby on Rails project.


We are going to use webpacker to manage app-like JavaScript modules in our Rails application. Webpacker is the fastest and easiest way to integrate any JavaScript framework that exists out there, that’s why that’s our go to solution.


In order to complete this tutorial, we should have already installed node, yarn, ruby and ruby on rails.


I am using RoR 5.2.4.1 and Ruby 2.6.2 for this tutorial + Angular 8.


We start by creating a new rails project:


    
rails new hello-angular -T
    
  

We are going to use Angular for this project, so let’s open our Gemfile file and add Webpacker:


    
gem 'webpacker', '~> 4.x'
    
  

Now let’s run bundle install. Once finished, we have to run:


    
bundle exec rake webpacker:install
bundle exec rake webpacker:install:angular
    
  

Once finished, you can start writing modern ES6-flavored Angular app right away.


Sqlite3 has a bug that can stop the server from running on a fresh Rails app. Thankfully, the fix is straightforward.


In the Gemfile we need to replace gem 'sqlite3' with gem 'sqlite3', '~> 1.3.0'. Now let’s run bundle install once again.


Now let's access our new project directory and let’s open our code in our favorite code editor. The first thing we noticed is the new javascript folder inside /app, that was added by webpacker and that’s the suggested place to start writing TypeScript code for our Angular app.


Let’s open our application.js file inside /packs and add these lines per recommendation from Webpacker:


    
import "core-js/stable";
import "regenerator-runtime/runtime";
    
  

Now let’s open our application layout file app/views/layouts/application.html.erb; in order to add our JavaScript pack(s) in our Rails views we are going to use the helper javascript_pack_tag, add the following line in that file, like this:


    
<%= javascript_pack_tag 'application' %>
    
  

Basically, webpacker is going to compile every main directory inside the app/javascript folder into the packs folder (excluding the packs folder) resulting in a .js file, as we can see there is also another file inside packs, hello_angular.js; this file is the Javascript’s compiled version of the TypeScript code that is present inside app/javascript/hello_angular. So if we want to use the code that got compiled into the file hello_angular.js we should use javascript_pack_tag again to require it in the specific view we want. We are going to add that later, for now let's create a quick scaffold:


  
    
rails g scaffold Blog title:string content:text && rake db:migrate
    
  

Now let’s open the rails console using rails c and create an example blog with the following line:


    
Blog.create(title: 'My first blog post', content: 'Lorem ipsum')
    
  

Create a new folder called blog inside app/javascript/hello_angular/app. Inside this new folder let’s create another folder called index. As you just notice we are going to fetch and show our list blogs.


Inside our new index folder let’s create three more files, these are gonna be called index.component.ts, index.component.html and html.d.ts, in our index.component.ts let’s add the basic structure for a TS component:


  
import { Component, OnInit } from '@angular/core';

import templateString from './index.component.html';
import { BlogService }  from '../blog.service';
import { Blog }  from '../blog.class';

@Component({
  selector: 'blogs',
  template: templateString,
})

export class BlogIndexComponent implements OnInit {
  blogs: Blog[];

  constructor(
    private blogService: BlogService,
  ) {
    this.blogService.getBlogs().subscribe(blogs => {
      this.blogs = blogs;
    });
  }

  ngOnInit() { }
}
  
  

Here we are injecting the future blog service that we are going to create later, then use this service to fetch the list of blogs, and then we are going to use the blogs variable to display blogs in our html file later.
In order to use HTML templates with Typescript and Angular we need to do the following things. Let’s add html-loader using yarn yarn add html-loader.


Let’s add this to html.d.ts, to let us import html files into TS code:


    
declare module "*.html" {
  const content: string
  export default content
}
    
  

In the file config/webpack/environment.js you need to add this environment loader:


    
environment.loaders.append('html', {
  test: /\.html$/,
  use: [{
    loader: 'html-loader',
    options: {
      minimize: true,
      removeAttributeQuotes: false,
      caseSensitive: true,
      customAttrSurround: [ [/#/, /(?:)/], [/\*/, /(?:)/], [/\[?\(?/, /(?:)/] ],
      customAttrAssign: [ /\)?\]?=/ ]
    }
  }]
})
    
  

Finally in the file config/webpacker.yml add this for the extensions list:


    
- .elm
- .coffee
- .html
    
  

Inside the blog folder let’s create two files, blog.class.ts and blog.service.ts


    
export class Blog {
  constructor(public id: number, public title: string, public content: string) {}
}
    
  

We are here defining the structure of our blog class.


Now let’s define our function that will fetch and return a list of blogs, so we are going to communicate with our server. Add this to the blog service:


    
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { BlogIndexComponent  } from 'rxjs';

import { Blog } from './blog.class';

@Injectable({
  providedIn: 'root'
})

export class BlogService {
  constructor(private http: HttpClient) { }

  getBlogs(): Observable {
    return this.http
      .get('/blogs.json')
      .pipe(
        map((blogs: Blog[]) => blogs.map(blog => {
        return new Blog(
          blog.id,
          blog.title,
          blog.content,
        )
    })));
  }
}
    
  

Now let’s focus on the blogs html file (index.component.html).


    
<section>
  <table>
    <tr>
      <th>Id</th>
      <th>Title</th>
      <th>Content</th>
    </tr>

    <tbody>
      <tr *ngFor="let blog of blogs">
        <td>{{blog.id}}</td>
        <td>{{blog.title}}</td>
        <td>{{blog.content}}</td>
      </tr>
    </tbody>
  </table>
</section>
    
  

Just a basic iteration over the blogs variables to show every single blog we have created.


Open the file app/views/blogs/index.html.erb and replace all the content there with these two new lines:


    
<hello-angular>Loading...</hello-angular>
<%= javascript_pack_tag 'hello_angular' %>
    
  

The <hello-angular> tag, is the selector specified on line number 4 on the file app/javascript/hello_angular/app/app.component.ts. This tag will pick up the code in our new component.


Finally, let’s create a simple routing system for our Angular app. We will need to add @angular/router using yarn yarn add @angular/router


Let’s create a new file called app-routing.module.ts inside app/javascript/hello_angular/app and add this code:


    
import { RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core';

import { BlogIndexComponent } from './blog/index/index.component';

const appRoutes: Routes = [
  { path: 'blogs', component: BlogIndexComponent },
  { path: '', redirectTo: '/blogs', pathMatch: 'full' },
];

@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes, { scrollPositionRestoration: 'enabled' }),
  ],
  exports: [RouterModule]
})

export class AppRoutingModule { }
    
  

In our app.module.ts we will need to import our new AppRoutingModule class:


    
import { AppRoutingModule } from './app-routing.module';

...

Imports: [
    AppRoutingModule,
    ....
],
...
    
  

And finally in our app.component.ts we will need to add <router-outlet>, this will load our routing system:


    
@Component({
  selector: 'hello-angular',
  template: '<router-outlet></router-outlet>'
})
    
  

Now let’s start our server application by running:


    
rails s    
    
  

If we visit the url http://localhost:3000/blogs we can now see the list of blogs.


Finally, we have two options to compile our TypeScript code, on demand or upfront. By default our code is getting compiled on demand, that means we have to reload the page every time we make a change in our code. If we want to change this behavior we will need to open a new console tab and run:


    
./bin/webpack-dev-server   
    
  

Now our TS code will be compiled upfront! By doing this we also have live reloading now, so no need to refresh the web page.


---
At FullStack Labs, we pride ourselves on our ability to push the capabilities of cutting-edge frameworks like Angular. Interested in learning more about speeding up development time on your next project? Contact us.

Let’s Talk!

We’d love to learn more about your project. Contact us below for a free consultation with our CEO.
Projects start at $50,000.

FullStack Labs
This field is required
This field is required
Type of project
Reason for contact:
How did you hear about us? This field is required