FullStack Labs

Please Upgrade Your Browser.

Unfortunately, Internet Explorer is an outdated browser and we do not currently support it. To have the best browsing experience, please upgrade to Microsoft Edge, Google Chrome or Safari.
Upgrade
Welcome to FullStack Labs. We use cookies to enable better features on our website. Cookies help us tailor content to your interests and locations and provide many other benefits of the site. For more information, please see our Cookies Policy and Privacy Policy.

Working with Ruby on Rails, Angular 8, Typescript, and Atom

Written by 
Bryan Villafane
,
Mid-Level Software Engineer
Working with Ruby on Rails, Angular 8, Typescript, and Atom
blog post background
Recent Posts
Getting Started with Single Sign-On
Choosing the Right State Management Tool for Your React Apps
Accessibility in Focus: The Intersection of Screen Readers, Keyboards, and QA Testing

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

Table of contents

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:

-- CODE language-bash keep-markup --
rails new hello-angular -T

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

-- CODE language-bash keep-markup --
gem 'webpacker', '~> 4.x'

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

-- CODE language-bash keep-markup --
bundle exec rake webpacker:install
bundle exec rake webpacker:install:angular

Once finished, you can start writing a 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 lets 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:

-- CODE language-bash keep-markup --
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:

-- CODE language-bash keep-markup --
<%= 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.

-- CODE language-bash keep-markup --
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:

-- CODE language-bash keep-markup --
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 file, 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, we use this service to fetch the list of blogs, 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, basically this will let us import html files into TS code.

-- CODE language-javascript keep-markup --
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:

-- CODE language-bash keep-markup --
- .elm
- .coffee
- .html

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

-- CODE language-javascript keep-markup --
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.

-- CODE language-jsx keep-markup --
<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:

-- CODE language-javascript keep-markup --
<hello-angular>Loading...</hello-angular>

<%= javascript_pack_tag 'hello_angular' %>

The <hello-angular></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></router-outlet>, this will load our routing system:

-- CODE language-javascript keep-markup --
@Component({
  selector: 'hello-angular',
  template: '<router-outlet></router-outlet>'
})

Now let’s start our server application by running:

-- CODE language-bash keep-markup --
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:

-- CODE language-bash keep-markup --
./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.

Now we’re going to show you some great Angular configs and tips for RoR applications as well as some Atom plugins to work with TypeScript.

Configs

Open the tsconfig.json file and tweak the configuration; this is necessary to access the latest stuff in the ECMAScript specification. To the array of lib, append es2019:

-- CODE language-bash keep-markup --
"lib": ["es6", "dom", "es2019"],

In the module, change es6 to esnext:

-- CODE language-bash keep-markup --
"module": "esnext",

Tips!

If we’re writing a feature test with Rspec, by default the code will be compiled every time we run it; however, this takes time and the waiting gets annoying. Instead, in the webpacker config file (config/webpacker.yml) we can update it to use the development compilation build of the angular code by doing this:

	
test:
  <<: *default
  compile: false

  # Compile test packs to a separate directory
  public_output_path: packs
	

The downside of this approach is that we need the development build to be updated, but since we’re constantly working on the code, it should happen anyways.

Atom’s Plugins

All Atom’s packages can be installed by going to settings and then searching for them: 

We start with atom-typescript, as it does not ship with any useful configuration/tools; the list of features can be found here.

In the next section, we’re going to configure and use tslint, so go ahead and install these plugins:

All three are required for linting our TS code.

tslint + tslint-angular + codelyzer

This is the best setup/combination I’ve found for linting Angular code written in TS. We should add tslint globally using yarn:

-- CODE language-bash keep-markup --
yarn add tslint -dev

yarn add tslint-angular -dev

yarn add codelyzer -dev

Next, we need to create a new file called tslint.json and add these basic rules:

	
{
  "extends": ["tslint:recommended", "tslint-angular"],
  "rules": {
    "max-line-length": {
      "options": [120]
    },
    "new-parens": true,
    "no-arg": true,
    "no-bitwise": true,
    "no-conditional-assignment": true,
    "no-consecutive-blank-lines": false,
    "no-console": {
      "severity": "warning",
      "options": ["debug", "info", "log", "time", "timeEnd", "trace"]
    }
  },
  "jsRules": {
    "max-line-length": {
      "options": [120]
    }
  }
}
	

If we open an angular file and hit `save,` we’ll probably see some warnings/errors in the console.

This is how the linter looks in action; in this example, it’s telling us that the interface OnDestroy was imported but is not being used.

If we forget to import a module, this is the message we see:

As you can see, this is a necessary tool as it makes sure our final angular code is clean, readable, and good looking.

Bonus

If you want to see these beautiful file icons in Atom, you should install prettier-atom

I hope you enjoyed these tips and tricks. If you have some suggestions, please leave them in the comments.

Conclusion

Using techniques like what is listed above, we have had the opportunity to address our clients’ concerns and they love it! If you are interested in joining our team, please visit our Careers page.

Bryan Villafane
Written by
Bryan Villafane
Bryan Villafane

I knew from the age of 11 that I wanted to become a software developer. I have always enjoyed the freedom of expression that comes with code and how its possibilities widen with each new advent. I'm relaxed and focused, and I thrive when I'm helping others tackle their ambitions with software, which I've done for clients in marketing, project management, data analytics, and more. I'm partial to Ruby on Rails, but also can appreciate JavaScript with TypeScrpt. I also enjoy playing video games and spending time with my pets.

People having a meeting on a glass room.
Join Our Team
We are looking for developers committed to writing the best code and deploying flawless apps in a small team setting.
view careers
Desktop screens shown as slices from a top angle.
Case Studies
It's not only about results, it's also about how we helped our clients get there and achieve their goals.
view case studies
Phone with an app screen on it.
Our Playbook
Our step-by-step process for designing, developing, and maintaining exceptional custom software solutions.
VIEW OUR playbook
FullStack Labs Icon

Let's Talk!

We’d love to learn more about your project.
Engagements start at $75,000.