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:)