Rails, AngularJS (1.5.8), and Webpack — Part 4

Okay. We left off last time actually seeing Angular working with webpack. Things were looking good – in a very basic “Hello World” kind of way. Today, we’re going to create a controller and a component to make an http request to hit up the backend for some data. First, let’s create some data.

Seeding the Database

Heading back to Rails land briefly to create some users and meals. In app/db/seeds.db let’s make a couple of arrays and then use a looping method to create our Users and Meals at the same time.

# app/db/seeds.rb

users = ["Luke", "Han", "Leia", "Obi", "Chewbacca", "C3P0"]
# Our users array from a galaxy from far far away

meals = ["Tattooine Tacos", "Chewie's Chowder", "Princess Pizza", "Darth's Death Stack", "Brain du Jar Jar", "R2's Nuts and Bolts"]
# Our meals array with some interesting looking delicacies

i = 0

6.times do
  UserMeal.create(user_id: User.create(name: users[i]).id, meal_id: 
  Meal.create(title: meals[i]).id)
  i += 1
end

Here we are using our two arrays and a loop to create not only a new User and Meal each go round, but the association between each one too. Now we can run ‘rails db:seed’ in our terminal and our database should be seeded. If we go into our rails console (‘rails c’ in the terminal), and type ‘User.first.meals’, we should see something like this:

=> #<ActiveRecord::Associations::CollectionProxy [#]>

Cool! Our seeding experiment worked and our associations seem to be solid. Let’s take it one step further and get the actual name of our first user’s meal – ‘User.first.meals.first.title’

=> “Tattooine Tacos”

Sweet! We are finished with the backend for now. Let’s get this front end party going!

Creating a Controller

First we’ll create our controller and use Angular’s built in $http service to get data from the backend. We have to use ngInject to inject the $http service into the controller. Then we’ll use $onInit to set up the call to the api. In app/javascript/packs, we’ll create a new file called ‘home.controller.js’. Then we’ll add our controller and export it, so it can be imported wherever we want it.

// app/javascript/packs/home.controller.js

class HomeController {
  constructor($http) {
    'ngInject';
    this.$http = $http;
  }

  $onInit = () => {
    this.$http({
      method: 'GET',
      url: '/users.json'
    })
    .then(res => {
      console.log(res.data)
      this.users = res.data
    })
    .catch(err => {
      console.log(err)
    })
  }
}

export default HomeController;

$onInit is a lifecycle hook that initializes on start up. So our $http call will happen when the HomeController is called upon. The $http call takes one argument, a configuration object consisting of a ‘GET’ method and a url, to make the HTTP request and returns a promise. For a successful promise, we use ‘then’. For an unsuccessful promise, we use ‘catch’. I believe the old standard was ‘success’ and ‘error’ to handle promises.

Now we need to output be able to output our data into a template. Stand alone templates are not used with modules. Instead, we’ll use a simple component.

Creating a Component

Let’s create a new file called ‘home.component.js’ in the same folder as our other Angular files. Then we have to import the HomeController and create the component. Components are very simple, as we’ll see. They basically point to a variable, which we’ll then export. Our component will take three properties: controller, controllerAs, and template. Components could take more properties, but we won’t need anything more for our needs. Our users will be coming to us in an array, so we’ll have to loop over this array to get each user. Angular comes with a very handy ng-repeat, which can be inserted right into the html.

// app/javascript/home.component.js

import HomeController from './home.controller';

const HomeComponent = {
  controller: HomeController,
  controllerAs: '$ctrl',
  template: `<div ng-repeat="user in $ctrl.users">
              <h2>{{user}}</h2>
            </div>`
}

export default HomeComponent;

This should give us what we need. Now we have to import our component to our main app module and we should see something in our browser.

Importing Component into Main App Module

Let’s head over to our meals module and see what we can do. In app/javascript/packs/application.js, we’ll import the home component under our angular and uirouter imports. Then we’ll add our component to the meals module so we can use it in the router.

//app/javascript/application.js

import angular from 'angular';
import uirouter from 'angular-ui-router';
import HomeComponent from './home.component';  // New


const meals = angular.module('meals', [uirouter])
  .component('homeComponent', HomeComponent) // New
  .config(($stateProvider) => {
    $stateProvider
      .state('home', {
        url: '/',
        component: 'homeComponent' // New

      })
  });

export default meals;

I added a ‘New’ comment next to the lines that were added. Notice that we still had to add the component to the module. Then we used the stringified name and added it to our ‘home’ route as a component. Let’s run the servers and see if we have anything.

Remember, we need to run two servers – one for the front end and one for the back. In one tab in your terminal run ‘./bin/webpack-dev-server’ and in another tab, run ‘rails s’. Refresh the browser, click home and….

{"id":1,"name":"Luke","created_at":"2017-10-11T22:21:37.642Z","updated_at":"2017-10-11T22:21:37.642Z","url":"http://localhost:3000/users/1.json"}

{"id":2,"name":"Han","created_at":"2017-10-11T22:21:37.679Z","updated_at":"2017-10-11T22:21:37.679Z","url":"http://localhost:3000/users/2.json"}

{"id":3,"name":"Leia","created_at":"2017-10-11T22:21:37.685Z","updated_at":"2017-10-11T22:21:37.685Z","url":"http://localhost:3000/users/3.json"}

{"id":4,"name":"Obi","created_at":"2017-10-11T22:21:37.692Z","updated_at":"2017-10-11T22:21:37.692Z","url":"http://localhost:3000/users/4.json"}

{"id":5,"name":"Chewbacca","created_at":"2017-10-11T22:21:37.698Z","updated_at":"2017-10-11T22:21:37.698Z","url":"http://localhost:3000/users/5.json"}

{"id":6,"name":"C3P0","created_at":"2017-10-11T22:21:37.705Z","updated_at":"2017-10-11T22:21:37.705Z","url":"http://localhost:3000/users/6.json"}

Nice! We got some JSON! We’re on the right track. Now all we have to do is get the name out of each object. To do that, we’ll have to modify the template in our component ever so slightly.

// app/javascript/home.component.js

import HomeController from './home.controller';

const HomeComponent = {
  controller: HomeController,
  controllerAs: '$ctrl',
  template: `<div ng-repeat="user in $ctrl.users">
              <h2>{{user.name}}</h2>
            </div>`
}

export default HomeComponent;

If you can’t see what was added, no worries. In the

tag we were getting the whole user, which is why we got all that extra JSON. The only thing that was added was a name attribute to the user – {{user.name}}. These things update live, so if we look back to the browser, we should see our Star Wars heroes.

An Aside

This actually took me quite a while to get running. I’m learning as I go and had some trouble understanding how to use $http in the controller. Specifically, how to inject it. Everywhere I looked, people were saying the same thing: outside of the HomeController class, inject the $http service as such – HomeController.$inject = [“$http”];

This did not work. I finally came across a someone’s code with ‘ngInject’ loaded in the constructor. I tried that, and everything worked!

We’ve gone through a lot today and I’m going to leave off here. Next time we’ll separate the $http call into a service, as it should be and see our Chewbacca’s favorite meal. Until then…Cheers:)

Advertisements

Rails, AngularJS (1.5.8), and Webpack — Part 3

Alrighty then. Where were we? I believe we left off in a good place. We installed the webpacker gem, and seemed to have communication between our newly created JS file and our app. Now for the hard part. I’m still learning this stuff myself, so bear with me. We’re going to attempt to use ES6 modules instead of the old way of declaring AngularJS components and such. This style guide is a good starting point to get to know what your components will look like and how they’ll interact with each other.

Setting up the Main Module

The first thing we’ll want to do is setup up our main Angular module in app/javascript/packs/application.js. First we have to import angular into the file so our module knows what it’s attaching itself to. We are also going to export the module, which will allow our webpack to compile it when we run app in the terminal.

// app/javascript/packs/application.js

import angular from 'angular';

const meals = angular.module('meals', [])

export default meals;

Now we have to declare our app in our main html file: app/views/layouts/application.html.erb. So in the body tag, add ng-app=”meals”.

<!DOCTYPE html>
<html>
  <head>
    <title>Meals</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all' %>
    <%= javascript_include_tag 'application' %>
    <%= javascript_pack_tag 'application' %>
  </head>

  <body ng-app="meals">
    <%= yield %>
  </body>
</html>

Now our app knows that it’s an Angular app and it’s looking for a module called ‘meals’. Let’s add a route with a component so we can actually see something working on the Angular side of the world.

Routing With angular-ui-router

If you’ve built anything with Angular in the past, you’ve most definitely used angular-ui-router. We’re going to install it with yarn. If you don’t have yarn installed, check out the installation instructions here. To install, or ‘add’ as it’s called in yarnish, head over to your terminal and type: ‘yarn add angular-ui-router’. If you check your dependencies in your package.json file, you should see ‘angular-ui-router’ listed.

So let’s see if we can get a route working. In our angular_home.html file, let’s add a ui-view directive.

<-- app/views/application/angular_home.html -->

<ui-view></ui-view>

This will give us an entry point into AngularLand and let us declare routes with $stateProvider in a config function. Back in our application.js file, let’s add a config function and a route. First we’ll have to import the ui-router so we can use all of its goodness. Check it…

// app/javascript/packs/application.js

import angular from 'angular';
import uirouter from 'angular-ui-router';

const meals = angular.module('meals', [uirouter])
  .config(($stateProvider) => {
    $stateProvider
      .state('home', {
        url: '/',
        template: `<h1>Hola Mundo!</h1>`
      })
  })

export default meals;

Ok so here we’re importing the angular-ui-router and chaining a config function onto our module. Angular-ui-router gives us access to $stateProvider, $urlRouterProvider, $state, and $stateParams. In the config function above, we’re using the Arrow function syntax now available in ES6, and setting the state. With a url and a simple template, we should be getting somewhere. Let’s quickly make a link back in angular_home.html so we can see our route is working.

<-- app/views/application/angular_home.html -->

<a ui-sref="home">Home</a>
<ui-view></ui-view>

Run the Webpack and Rails Server Simultaneously?

Now to run webpack, we can’t just run the rails server. We actually have to run the rails and the webpack servers. Back in the terminal, create a new tab in the same directory, then run ‘./bin/webpack-dev-server’. This will compile your included javascript files through webpack. If you’re like me, then you probably got a lot of red errors followed by this line: webpack: Failed to compile.

So something is not letting us load our module. If we scroll up in the terminal, we’ll see that most of the errors say that angular doesn’t exist. But I thought we installed Angular already!! Well, I’m pretty sure that when we installed it with the webpacker command, it installed Angular 4. We need version 1.5.8, which we can easily install add with yarn. Exit out of the webpack server in your terminal. Then type ‘yarn add angular@1.5.8’.

Check out your package.json file and you should see angular with the correct version listed under the dependencies. Now if we try to run the server again – ‘./bin/webpack-dev-server’ – everything should compile (there might be a few warnings, but let’s not worry about those). In your other tab run the rails server command and in your browser go to ‘localhost:3000’. You should see a link that says ‘Home’. Click it, and you should be routed to the home route that we set up earlier. If you followed along, you should see ‘Hola Mundo!’ on the webpage!

To Sum it Up

We are in the Angular business now! In this episode, we’ve connected the front end to our meals app with webpack, added angular-ui-router and AngularJS, created our main module with a route and template, learned how to run the webpack server, and saw our template working in the browser. Next time, we’ll get some functionality going with some http requests to the backend and do something a little more exciting than Hello World Hola Mundo. As always, thanks for reading. I hope this helps someone out there! Until next time…CHEERS:)

I’ve Got 99 Problems – but Deploying My Rails/Angular App to Heroku is No Longer One of Them:) PART 2

So where was I? Oh yeah, I left off at changing my problem js file extensions to ‘.es6’ so there would be no problem with new JS6 syntax. This definitely helped, but in it’s place sprung another problem. Only the images that were not hooked up to the database on my site were showing . So I used my noggin and realized that I never migrated my database to Heroku! DOH!

When I tried to run ‘heroku run rake db:migrate’, the migration kept aborting with this message:

PG::UndefinedTable: ERROR:  relation "users" does not exist

I definitely had a table called “users” so that didn’t make much sense. I did a little digging and found out that postgres (the database Heroku uses) does not like tables relating to other tables that have not been created yet. The table being created was referencing a table that was created on a later migration. I know it’s not a good idea to mess with the names of migration files, but I did anyway. Rails timestamps migration files, so I just had to change my “users” file timestamp to a date before the file in question.

So I tried the migrate command again and got the same error. Strange. My migration files had quite a few additions which later became subtractions. I decided that I only really needed three migrations for three tables. Since I had a good seed file which could recreate everything I needed, I decided to recreate only the migrations I needed. This would allow me to reset the database and start from scratch. I deleted the schema and development.sqlite3 files so there would be no references to the old migrations. Now I could recreate my database and schema file.

$ rails db:drop db:create db:migrate db:seed
// This will delete the entire database, losing all data
// recreate a new database and runs the migrations
// db:seed will seed the database from seeds.rb

I had a brand spanking new schema and database, ran my local server and everything was running smoothly. So I ran the heroku migration command again and got THE SAME ERROR!! WTF?!? After googling for a bit, it hit me. I didn’t push the new changes to heroku! Often times it is the simple tasks that one might forget to do that causes errors. Note to self: if you are banging your head against the wall, stop for a minute and think of the steps you would normally take. Save yourself some time and energy. The first change to the “users” migration file name probably would have worked if I just slowed down a little.

After trying to migrate to Heroku again, it worked! Now my site is up and running, and I’m feeling good! Hopefully this helps fellow programmers out there that get stuck – and have sore heads! As always, thanks for reading!

AngularJS: Using $localStorage to Keep Users Logged In

If you are familiar with authentication, then you are should be familiar with sessions and web tokens. While the latter is the standard these days, sessions still have their place, especially when building small apps. The app I’m currently working on is using a Rails API with an Angular front-end. Since it was my first time attempting to use authentication with Angular, I used the tried and true Devise gem for Rails and Angular-Devise for Angular. Devise was fairly simple to set up and I had it working pretty quickly. Their documentation is pretty good as far as documentation goes!

I was saving my user info and their signedIn status in the $rootScope of my app which would make it accessible to all of my views and controllers. This worked well until a page was refreshed and my $rootScope was demolished along with all my users signedIn status. This is a good thing to know about Angular! The rootScope starts fresh on every page refresh. After reading up and doing some soul searching, I found a solution.

ngStorage to the Rescue

The ngStorage module allows you to store information that you want to have available during a session. After bowering or npm-ing, all you have to do is add ngStorage to your apps dependencies and inject $localStorage or $sessionStorage into any controller you need it in. Pretty simple. Now to use it, you have to pass $localStorage by reference to a hook under $scope or $rootScope, like so: $rootScope.$storage = $localStorage. Add this line to your controller and you have access to all the goodness that you would like to store there.

The basic usage would be to pass a boolean to your currentUserSignedIn variable on the $storage when they are logged in. Using Devises’ Auth.login function, you can then add the users status in the callback function.

Screen Shot 2017-03-06 at 10.01.13 AM

 

Ignore the toaster.pop (although check it out, it’s cool!) stuff going on and pay attention to the $rootScope.$storage line. Here we set currentUserSignedIn to true and have that information available in our views. Since it’s set on the $rootScope, we should have access to it throughout our app. Now on page reloads, we still have access to our users’ signed in status. Cooool!

I have a navbar throughout my views which uses its own controller. Since I used $rootScope.$storage I should have access to the users’ status. There was one occasion, however, where I would lose access to the $storage variable on a page reload. I wasn’t quite sure what was happening, but when I added $scope.$storage = $localStorage to my navbar controller, everything worked fine again. Don’t forget to inject $localStorage into the navbar controller. Now in the navbar html template, if we wanted to have a link to show “Log In” if the user is not logged in we could use a ng-hide directive as such:

screen-shot-2017-03-05-at-6-57-27-pm

 

If our user is logged in, we can simply change the hide to a show for a Log Out link:

screen-shot-2017-03-05-at-6-58-30-pm

When our user signs out we can reset our $localStorage or set the currentUserSignedIn boolean to false. We do this just like we did in the sign in function:

Screen Shot 2017-03-06 at 10.15.36 AM

Here we set currentUserSignedIn to false and our users session is complete. I’m definitely going to check out JWT Tokens next, but baby steps are good to take with these things. Thanks for reading!

 

 

AngularJS & Rails: No Helpers:(

Angular is a front-end framework and is used for speed and asynchronous page loading (no page reloading on each request). Which means much of the helpers we rely upon in Rails are deemed useless. This can be hard to take for someone new to a front end framework like myself. But, alas, life must go on! The biggest obstacle for me so far is not being able to use the Flash Messages helper provided by Rails. What to do?

There are a few options out there on github that you can use. After trying a few different repos on Github which gave me a plethora of errors, I came across Toastr. It appealed to me right away because of it’s simplicity. I ran into one small bug (which was on my side), so I thought I might share.

The Set Up

screen-shot-2017-01-19-at-5-02-44-pm
First, to install Toastr, you can use Bower or npm. Next, copy the css and js files from the repo into your Asset Pipeline  – app/assets/stylesheets and app/assets/javascripts respectively. This will auto-rails-magically make the files available to use (as long as you //= require your trees *= in your application.js/css files). Now we can set up a factory in Angular.

The Chocolate Cheese Toaster Factory

screen-shot-2017-01-19-at-7-18-05-pm

Hey you…can you pass the butter, please?

Here we have set up a factory (we can just as easily use a service), which returns an object. Note that we also have access to other methods such as info and warning, but I only needed success and error for my project. When we call our toaster from a controller, we can give any text we’d like the user to see as an argument. I needed an error message telling a user that they couldn’t upvote a post more than once.


This Isn’t Going to Hurt, I Swear

We can then inject our toaster (factory) into any controller we’d like, then we have access to our success and error calls.

 

screen-shot-2017-01-19-at-8-20-51-pm
We’re injecting our toaster factory into our Main Controller. Now we can use it in any function we see fit. As I mentioned earlier, I needed to show the user an error if they tried to vote more than once on a post. By using a basic conditional statement with a some method on the array I could check for previous voters. I created a private function called voteChecker that checks the an upvoted posts user_id against the current_user $scope.user.id (I had to use inject the controller with Auth from the angular-devise gem for this).

screen-shot-2017-01-19-at-8-30-10-pm

By using the voteChecker function as the callback for the some method, we can check if any of the previous upvotes were from our current_user (I know, I know). However, the some method will return true if our array is empty, so we have to check that too. If the some method returns true && our upvoted_posts array’s length is not zero, then we have ourselves a two-timer and get to yell at them:) If, however, the challenge is passed, then we get to use our posts service (which I’m not going to delve into) to upvote our post. Very nice!

The Bug

I almost forgot! After doing everything above, I was getting a strange error:

TypeError: Cannot read property ‘extend’ of undefined

Crap! Well after a little Stack Overflowing it turned out that I was requiring jQuery after Angular in my application.js file. Once I put it before angular, everything was all good. NOTE – the same goes for if you’re including links or script tags referencing the angular and jQuery files from your application.html file. Well, all this toaster talk has made me quite hungry, so I think I’ll end here. If you ever need a flash message in your life and you’re using a front end framework, check out toastr!

 

Off the Rails

Learning RoR has been quite challenging to say the least. Add in the personal grief that I’ve been going through for the past few months and I can say it has been one of the hardest things I’ve ever had to do. I was keeping a good pace through June and was nearing the final assessment for the Rails section of the course when I had to do some self-assessing. I realized that even though I was getting through the material, not much of it was sticking. I thought it would be a good idea to take a bit of time off (which the good people at Flatiron allowed me to do) and review. So I took about 6 weeks off and did just that. I came back feeling strong and ready to code.

I thought I would stay with the theme that I started with using Sinatra, which was a Produce App. It basically keeps track of produce that you have in your fridge and lets you know how many days left until spoilage (because finding rotten fruit in the back of the fridge is the worst!). Pretty simple right? Well I ran into some trouble because I didn’t fully understand all of the instructions. Maybe I should rephrase that. I understood the instructions, but jumped in the deep end without making sure there was water in the pool. Although in the end, I learned a ton and feel much more confident in my abilities and understanding of programming and debugging.

So back to the part about jumping in the deep end. Well, it turns out that a little more thinking is involved in an app creation before any actual coding is done. Of course you can always add another column to a table in your database. Or in my case a whole new table itself. It worked this time, but I know in the future I will have to do a little more thinking before I start coding. The initial design of my app was very basic:

  • users table with columns:
    • name
    • email
    • password
  • produce table with columns:
    • name
    • type (fruit or veggie)
    • shelf life (integer)
    • description/benefits
  • users_produce joins table with columns:
    • user_id (foreign key)
    • produce_id (foreign key)
    • created_at (date)
    • eaten? – a boolean value, set to false as default and turned to true if it was taken out of the fridge.

The joins table would have a method to calculate how many days left until expiration. This is accomplished by calculating the difference between the created_at value of the user_produce and Time.now and subtracting that value from the shelf_life value from the produce. Looks something like this:

screen-shot-2016-10-12-at-10-32-03-am

I added some nice bootstrap styling, a Devise authentication system, and had a working app pretty quickly. Then I went back and re-read the instructions. One of the trickiest parts of Rails is nested attributes and my current configuration left me no way to implement this feature. Back to the drawing board. I needed the ability when creating a new ‘something’ in one table, it would also cause the nested attributes to create a new ‘other something’ in another table. I know that is super technical and will go over most heads. I do apologize. So I thought about it. And thought some more. I needed another table that could associate with my produce table. I would have to expand my app.

I decided on a Juices table with just a name column and a JuiceProduce joins table with produce_id, juice_id, and quantity columns. This would enable the user to create a new produce item by way of nested attributes from the creation of a new juice. Cool. Implementation was quite difficult and I would be lying if I said it didn’t take me a few days. It seems like it’s always these sticking points where high frustration ends in the most learning. Lesson to self: don’t get so frustrated…there is an answer and it will make you learn! You just have to keep moving forward and ‘get your hands dirty’. That’s what I like to call prying. Sometimes I’ll put a binding pry after each line of code in my controller action and in the instance method in my model. Here I can see exactly what is going on after each line of code. I likey. It feels like I’m getting my hands dirty:)

I made it a lot harder on myself by having dropdowns for produce (which were already in the database) to choose from in the New Juice form, while also having input fields for new produce (which was not in the database). But like I said earlier, it’s these sticking points that really challenge you and end up speeding up the learning process by leaps and bounds. I’m proud of myself for sticking with it and have a new found confidence that I’m actually going to be a web developer…and a good one too! Check it out in action!