Angular - Setup, Styling, and GET All


In this video we are going to get started with our Angular 1.5 application. By the end of this video we will have:

  • 'Installed' Angular 1.5
  • Got a local server up and running
  • Replicated the design from our Twig CRUD setup
  • Connected our Angular app to our Symfony 3 API backend

That's quite a lot of 'stuff' happening in one video.

Before proceeding any further I want to stress that I am not a JavaScript specialist, and certainly not an Angular specialist. I'm largely not even a fan of Angular, finding it overly complicated in terminology, and with a horribly steep learning curve. I much prefer React, which is coming up later in this series.

That said, it's in use all over the corporate world, so it would be remiss of me not to cover it. However, I just want to give a fair warning that these videos are about communication between Angular and your Symfony back end, rather than being a "beginners guide to Angular", or even a "best practice guide to Angular".

This tutorial expects that you have a basic knowledge of Angular - nothing crazy - but at least having heard words like scope, template, and controller are going to be helpful in getting the most out of this, and the next few videos.

Getting Going

To save time and potential conflicts with setup of the Angular side of things, I am going to create this project from the Angular Seed App.

The Angular Seed docs are decent enough to get you going, but here is what I did:

git clone https://github.com/angular/angular-seed.git angular-symfony-3.dev
cd angular-symfony-3.dev
sudo npm install
bower install
sudo npm start

This will get you up and running, but this will also take over the terminal whilst the server is running. Not a problem usually, if using a modern terminal client simply open another tab. I'm not sure how that might go on Windows though...

The Angular Seed will create a components directory for you also. I immediately removed this as we won't be using this at all. If you choose to remove this, also remove the myApp.version line in your app/app.js file.

By using this Angular Seed we will get a simple skeleton application (which we will largely replace) that gives us a working setup to start from. This includes an easy to use local development server, which will start on http://localhost:8000/, and it's super important to add this value to your Symfony 3 API parameters.yml file:

# /app/config/parameters.yml

    # nelmio cors
    cors_allow_origin: 'http://localhost:8000'

CORS can be quite painful to set up properly. However, CORS is what allows Cross Origin Resource Sharing, which sounds like technobabble, but is effectively the settings that allows code not on your API domain to access your API. Another way of putting this would be to say:

API - https://api.yourdomain.com

Front End - https://www.someothersite.com

If you went with this setup, by default, you would not be allowed to connect from your front end to your back end, because you have not explicitly told your API to expect connections from www.someothersite.com. It's one of those things that's fairly easy to get working development - often due to lack of security, or the ability to remove pesky security from the equation - but may bite you when you move into production.

That's as deep as I want to go into CORS for the purposes of this video, but as it can be a source of problems / frustration I strongly advise you to read the docs.

Making Things Look Nice

We have all the Bootstrap-y layout and CSS from our Twig project. There's no point re-inventing the wheel here, so it's largely a case of ctrl+c / ctrl+v, unless you are deeply keen on practicing your typing skills.

Here's the contents of app/index.html:

<!DOCTYPE html>
<!--[if lt IE 7]>      <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html lang="en" ng-app="myApp" class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en" ng-app="myApp" class="no-js"> <!--<![endif]-->
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Angular CRUD</title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="bower_components/html5-boilerplate/dist/css/normalize.css">
  <link rel="stylesheet" href="bower_components/html5-boilerplate/dist/css/main.css">
  <link rel="stylesheet" href="app.css">
  <script src="bower_components/html5-boilerplate/dist/js/vendor/modernizr-2.8.3.min.js"></script>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
</head>
<body>
  <!--[if lt IE 7]>
      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
  <![endif]-->

  <nav class="navbar navbar-default navbar-fixed-top">
    <div class="container">
      <div class="navbar-header">
        <a class="navbar-brand" href="/">Angular CRUD</a>
      </div>
    </div>
  </nav>

  <div class="container">

    <div class="row">
      <div class="col-sm-12">
        <div ng-view></div>
      </div>
    </div>

  </div><!-- /.container -->

  <script src="bower_components/angular/angular.js"></script>
  <script src="bower_components/angular-route/angular-route.js"></script>
  <script src="app.js"></script>
  <script src="view1/view1.js"></script>
</body>
</html>

Aside from the various lines at the top of this file which bootstrap your Angular app (ng-app="myApp"), the crucial line here is the <div ng-view></div>, which is where the various views (rendered templates) will be created as we go through this course.

This works in conjunction with ngRoute, which is the inbuilt router for Angular. You may have heard of, or have experience with Angular UI Router. For our purposes, ngRoute is more than capable, but in larger projects you tend to see UI Router used instead.

If you'd like to know more about the differences between ngRoute and Angular's UI Router, then I'd recommend giving this StackOverflow post a read. Don't just blindly go with UI Router as it's more powerful (it is), because you may not need all the extra complexity it entails.

And also I copied over the custom CSS we created in the Twig section of this course:

/* /app/app.css */

body {
  padding-top: 90px;
}
.starter-template {
  padding: 40px 15px;
  text-align: center;
}

.social-metric-count {
  font-size: 26px;
  text-align: center;
}

.list-group-item {
  min-height: 60px;
}

That should be enough to make our Angular CRUD app look just like the Twig CRUD Bootstrap layout from earlier in this series. Next, we need to add in our list of Blog Posts.

Making Things Work

At this stage we are going to do the very bare minimum to make our app functional. This means the code will be funky, but we will prove that our app will work before we start worrying about directory structure, file names, and so on.

For the moment we will re-use what is already in our app seed.

We'll start off by creating the table 'outline', which is just plain old HTML, nothing dynamic at all:

<!-- /app/view1/view1.html -->

<table class="table table-hover table-responsive">
    <thead>
    <tr>
        <th>id</th>
        <th>Title</th>
        <th>Options</th>
    </tr>
    </thead>
    <tbody>
    </tbody>
</table>

This should be enough to render some content out - a simple Bootstrap topnav element and the table row headers. The table will be empty, of course, as we haven't actually told the table to show any rows, static or dynamic.

We will come back and actually add in the repeated table row HTML elements very shortly. For the moment, we kinda need some data.

GET'ting Data

I've made a wild assumption up until now that you have completed the API portion of this course. If you haven't then this next step is going to be, ahem, challenging.

Our Symfony 3 API allows us to GET either an individual or a collection of BlogPost entities.

For this step, we need to GET a collection, as we want to display all the blog post entries in our table, just like we did in the very first CRUD video.

However, in our Twig videos we hit the database directly via Doctrine queries. Then, we passed the result of this query to Twig which helped us render some HTML which we could then return to our end user.

We then implemented our API GET methods to enable the decoupling of front end and back end. We removed the HTML element, instead only returning JSON.

And here we are now.

In my case, sending in a GET to http://api.symfony-3.dev/app_dev.php/posts is all I need to do to retrieve a JSON array of 50 blog post representations. I can do this using Postman client, or I can do this using Angular's $http.

Let's do that.

This is the contents of view1.js before we start changing stuff up:

// /app/view1/view1.js

'use strict';

angular.module('myApp.view1', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/view1', {
    templateUrl: 'view1/view1.html',
    controller: 'View1Ctrl'
  });
}])

.controller('View1Ctrl', [function() {

}]);

You can see that when a user hits the http://localhost:8000/view1 url, they will get that view1/view1.html template rendered, and that template will be connected up the the View1Ctrl controller, also defined in this same file.

It's nice and easy to see everything that's happening here - given that it's in one file - but generally this isn't how Angular projects will look. Actually, I've never found any two Angular projects that have had the exact same file structure / layout before... but did I mention I'm not a huge fan of Angular :)

Anyway, this is why making those template changes we did a little bit earlier 'just worked'.

We need to use the $scope as the glue between our data model and the view / template. If you haven't already done so, be sure to give the Angular documentation on scope a once over.

We will initialise ourselves an empty array of blogPosts.

// /app/view1/view1.js

.controller('View1Ctrl', ['$scope', function($scope) {

  $scope.blogPosts = [];

}]);

Now that in itself is not that useful. But it does expose blogPosts on to our template.

We already know a blog post JSON representation will have some specific fields - id, title, and body. We don't need to display the body contents in our list view. But we can make use of the id and title:

<!-- /app/view1/view1.html -->

<table class="table table-hover table-responsive">
    <thead>
    <tr>
        <th>id</th>
        <th>Title</th>
        <th>Options</th>
    </tr>
    </thead>
    <tbody>

    <tr ng-repeat="post in blogPosts">
        <td>{{ post.id }}</td>
        <td>{{ post.title }}</td>
        <td>
            <a href="#" class="btn btn-default btn-sm">Edit</a>
            <a href="#" class="btn btn-danger btn-sm">Delete</a>
        </td>
    </tr>

    </tbody>
</table>

We are going to use an ng-repeat directive to loop over all the contents in our $scope.blogPosts array (just called blogPosts in the view, no need to reference the $scope) and for each, display the id, title, and a couple of useless (but nice looking) buttons.

This should render, but we still haven't actually gotten any data to display. Let's fix that also:

// /app/view1/view1.js

.controller('View1Ctrl', ['$scope', '$http', function($scope, $http) {

  $scope.blogPosts = [];

}]);

Incidentally, most larger Angular projects tend to use something like ng-annotate to remove the need to pass in the string name of the variable and have the array as the second parameter in the controller function.

Given that we have the $http service we can now start making requests to places such as... hey, our API! Why, it's almost as if this was all planned.

// /app/view1/view1.js

.controller('View1Ctrl', ['$scope', '$http', function($scope, $http) {

  $scope.blogPosts = [];

  $http.get('http://api.symfony-3.dev/app_dev.php/posts')
       .then(function(result) {
         console.log('things went well!', result);

         $scope.blogPosts = result.data;

       }, function (err) {
         console.error('things did not go so well', err);
       });
}]);

And that should be enough to get all of our 50 blog post entries rendered out and looking good.

I don't much like how you pass in two functions into the then method. I think it looks weird and confusing.

Anyway, that's basically it for our proof of concept. We've got connectivity, we've got our blog posts displaying, and it wasn't that painful to get to this point.

The good news is, everything we do from here on our is very similar to this. It would be nicer to start structuring our project in a little more realistic fashion, so that's what we will do in the very next video.

Code For This Video

Get the code for this video.

Episodes