A Simple App with the AngularJS Framework (Part 1)

In my last post I introduced concepts for MVC and MVVM and said I would be delving into the code for a three page application – a home page, a page that loads from a remote REST interface and an authenticated page. These three pages provide enough of a look into each framework to allow me to understand the framework and decide on which one I want to use long term. Today is all about AngularJS.

Bootstrapping AngularJS

Before we can do anything with AngularJS, we need to get it into our application. We did the same thing with Aurelia when we covered that and I fully expect to do the same thing with the other frameworks. Somehow, your page needs to know about the framework. Firstly, let’s install AngularJS:

jspm install angular

Now let’s adjust the public/index.html file to load it:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Angular Test Site</title>
	</head>
	<body ng-app>
		<h1>Hello {{"World" + "!"}}</h1>
		<script src="jspm_packages/github/angular/bower-angular@1.4.2/angular.min.js"></script>
	</body>
</html>

The interesting code is highlighted. Firstly, let’s discuss the ng-app Angular Directive. You will sometimes see this referred to as the ngApp directive. If the directive is being used declaratively (i.e. in HTML), then it is represented as all lower case with a dash. If it’s being used in code then it’s represented as camel-case with no dash. They mean the same thing. Directives are indications that a specific behavior needs to be attached to an element. In this case, the ng-app directive tells Angular that it needs to process the body element (i.e. the whole application). If you want Angular to process only a little bit of the page, you can do that too.

Note the double curly-brackets in the h1 element. That’s an Angular Expression and allows you to embed basically any JavaScript expression inside them. More importantly, you can inject code and variables that are “in-scope” within an Angular expression. This is more useful when I talk about views – for now, just know that when you see the double curly brackets, you are looking at an Expression that will get evaluated before display.

We’ve done a whole lot of mark-up before bringing in the JavaScript. The normal thing to do these days is to load scripts right at the end of the page. When Angular loads, it looks for the directives and gets to work. You could just as easily placed the script tag in the head of the document. This is just “more normal”.

The Welcome Page

When we introduced the first page in the Aurelia tutorial, there was already a bunch of stuff around routing, controllers and views. You don’t actually need that in an Angular app – you can just start with a single page. Let’s augment our public/index.html page with the additional code to produce the Welcome page:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Angular Test Site</title>
		<link rel="stylesheet" href="jspm_packages/npm/font-awesome@4.3.0/css/font-awesome.min.css">
		<link rel="stylesheet" href="styles/site.css">
	</head>
	<body ng-app>
		<section ng-init="fn='Adrian';ln='Hall'">
			<h2>Enter Your Name</h2>
			<form role="form">
				<div class="form-group">
					<label for="fn">First Name</label>
					<input type="text" ng-model="fn" class="form-control" id="fn" placeholder="first name">
				</div>
				<div class="form-group">
					<label for="ln">Last Name</label>
					<input type="text" ng-model="ln" class="form-control" id="ln" placeholder="last name">
				</div>
				<div class="form-group">
					<label>Full Name</label>
					<p class="help-block">{{fn + " " + ln}}</p>
				</div>
			</form>
		</section>

		<script src="jspm_packages/github/angular/bower-angular@1.4.2/angular.min.js"></script>
	</body>
</html>

Even if you haven’t included a stylesheet, this will demonstrate the point. I have made an ad-hoc Angular Model. It’s initialized with another Angular directive – ng-init provides the initial values for the two fields. I’ve then bound those two fields to text input boxes with the ng-model directive. Finally, I’m using an angular expression to compute the full name. Start changing the text in the text-boxes and you will see the full name change along with it. This is data-binding in action.

Note that the documentation for ng-init tells you that this is a bad way to do things. You should use a controller instead. Good idea – let’s do that!

Adding Controllers

Now that I have a view working (albeit embedded into the main page), I need to develop a controller as well. Angular conventions tell me to wrap all the logic about the controller (and anything else I use) into an Angular Module. Let’s take a look at a new file: public/app.js:

"use strict";

var testApp = angular.module("testApp", []);

testApp.controller("WelcomeController", function () {
	this.fn = "Adrian";
	this.ln = "Hall";

	this.fullName = function () {
		return this.fn + " " + this.ln;
	}
});

This contains the same model parameters as before (fn and ln) and a function to calculate the full name. I want to adjust the HTML view in public/index.html so that it uses the controller instead of the embedded model now:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Angular Test Site</title>
		<link rel="stylesheet" href="jspm_packages/npm/font-awesome@4.3.0/css/font-awesome.min.css">
		<link rel="stylesheet" href="styles/site.css">
	</head>
	<body ng-app="testApp">
		<section ng-controller="WelcomeController as welcome">
			<h2>Enter Your Name</h2>
			<form role="form">
				<div class="form-group">
					<label for="fn">First Name</label>
					<input type="text" ng-model="welcome.fn" class="form-control" id="fn" placeholder="first name">
				</div>
				<div class="form-group">
					<label for="ln">Last Name</label>
					<input type="text" ng-model="welcome.ln" class="form-control" id="ln" placeholder="last name">
				</div>
				<div class="form-group">
					<label>Full Name</label>
					<p class="help-block">{{welcome.fullName()}}</p>
				</div>
			</form>
		</section>

		<script src="jspm_packages/github/angular/bower-angular@1.4.2/angular.min.js"></script>
		<script src="app.js"></script>
	</body>
</html>

The changed lines are highlighted. First off, our ng-app statement says “I’m an angular app and module testApp contains my stuff”. The next line says “this section uses the WelcomeController as it’s scope and I’m going to name it welcome within this section”. The ng-controller directive defines a controller association. The two input boxes have had their ng-model statements changed so that they reference the fn and ln variables within the WelcomeController. Finally, my help-block has changed. Instead of an embedded expression to calculate the full name, I’m using a function.

Running this will do the same thing as before, except now I am using a controller instead of default actions. Hopefully, you will see that writing a controller is easy and writing a view is also easy. You can embed them or separate them from your main file.

Routing

I’ve gotten to the point where I have a view (embedded in the page) and a controller (in a separate file). I want to add routing such that when I can handle multiple pages within the same application and the view and controller are separated.

This is a multi-step process. Step 1 is to add routing into the application config. I do this in my app.js file:

"use strict";

var testApp = angular.module("testApp", [ 'ngRoute' ]);

testApp.config(["$routeProvider", function($routeProvider) {
	$routeProvider
		.when("/welcome", {
			templateUrl: "partials/welcome.html",
			controller: "WelcomeController"
		});
}]);

I am declaring a dependency on ngRoute – the Angular Routing module – when I create the module. I also need to bring in the angular-route module in my main page. More on that later. Note that you don’t have to use the Angular provided router. There are others out there (most notably one called UI-Router) that provide different functionality. The core is the same though, so I’m just going to use the standard one.

Once I’ve declared the dependency on ngRoute, I can create a route-map. Right now, there is only one route – the /welcome URI maps to a template and a controller. Note how dependency-injection happens here. I am injecting the $routeProvider and then using it to generate the route-map.

To install the angular-route module, do this:

jspm install angular-route

Don’t forget to add it to the public/index.html as well – I’ll show that when I show off the new file later on.

Another place where dependency injection happens is in the route-enabled controller:

testApp.controller("WelcomeController", [ '$scope',
	function WelcomeController ($scope) {
		$scope.fn = "Adrian";
		$scope.ln = "Hall";

		$scope.fullName = function () {
			return $scope.fn + " " + $scope.ln;
		}
	}
]);

Instead of using this, I’m using a $scope variable. This allows me to access the internals of the model without using an ngController directive to associate the view to the controller.

So that’s the controller – what about the view? I’ve created a new directory: partials and created a welcome.html file within that directory to contain the view:

<h2>Enter Your Name</h2>
<form role="form">
	<div class="form-group">
		<label for="fn">First Name</label>
		<input type="text" ng-model="fn" class="form-control" id="fn" placeholder="first name">
	</div>
	<div class="form-group">
		<label for="ln">Last Name</label>
		<input type="text" ng-model="ln" class="form-control" id="ln" placeholder="last name">
	</div>
	<div class="form-group">
		<label>Full Name</label>
		<p class="help-block">{{fullName()}}</p>
	</div>
</form>

This is the contents of the section element within the main page. I’ve removed the welcome. tags on all the angular directives and expressions – those are now part of the scope and the scope is the default, so there is no reason to qualify it. My final change is to the public/index.html. I need to remove the view code and replace it with something that specifies the ng-view directive. The ng-view directive tells Angular where to place the compiled view.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Angular Test Site</title>
		<link rel="stylesheet" href="jspm_packages/npm/font-awesome@4.3.0/css/font-awesome.min.css">
		<link rel="stylesheet" href="styles/site.css">
	</head>
	<body ng-app="testApp">
		<section ng-view></section>

		<script src="jspm_packages/github/angular/bower-angular@1.4.2/angular.min.js"></script>
		<script src="jspm_packages/github/angular/bower-angular-route@1.4.2/angular-route.min.js"></script>
		<script src="app.js"></script>
	</body>
</html>

You can see the ng-view directive at line 10. I’ve also brought in the angular-route module at line 13.

If you run this application as before, you will note that nothing is displayed. However, remember when I was doing the Aurelia tutorial that the URLs looked something like http://localhost:3000/#/welcome – put in that and you will get the same activity that we have had all along. This time it’s in a reusable controller and template view.

I can add a “default route” using an otherwise() statement in the route map:

testApp.config(["$routeProvider", function($routeProvider) {
	$routeProvider
		.when("/welcome", {
			templateUrl: "partials/welcome.html",
			controller: "WelcomeController"
		})
		.otherwise({
			redirectTo: "/welcome"
		});
}]);

This basically says “here is my route map, but if you don’t recognize anything, display the welcome page”.

Let’s extend the controller slightly. The Aurelia tutorial had a submit button – the application provided an alert when you clicked on the submit button. Let’s add that functionality:

testApp.controller("WelcomeController", [ '$scope',
	function WelcomeController ($scope) {
		$scope.fn = "Adrian";
		$scope.ln = "Hall";

		$scope.fullName = function () {
			return $scope.fn + " " + $scope.ln;
		}
		
		$scope.submit = function () {
			alert("Hello " + $scope.fn + " " + $scope.ln);
		}
	}
]);

I also need to update the view in partials/welcome.html:

<h2>Enter Your Name</h2>
<form role="form">
	<div class="form-group">
		<label for="fn">First Name</label>
		<input type="text" ng-model="fn" class="form-control" id="fn" placeholder="first name">
	</div>
	<div class="form-group">
		<label for="ln">Last Name</label>
		<input type="text" ng-model="ln" class="form-control" id="ln" placeholder="last name">
	</div>
	<div class="form-group">
		<label>Full Name</label>
		<p class="help-block">{{fullName()}}</p>
	</div>
	<button id="submit" ng-click="submit()">Submit</button>
</form>

I’m using another directive – ng-click – to wire up the event handler so that the user can click on the Submit button.

To finish up this first page, I also want to get bootstrap installed. This was just a case of using jspm to install it and then adding the CSS and JS files to the index.html file. I’ve skipped that step in these instructions, but the code is in the repository.

Adding a Second Page

Now that I have my first page configured, I want to create a second page – the flickr example from the Aurelia app. To do that, I need to add a controller and a template (easy enough given the Aurelia app has pretty much provided those for me) and then I need to wire up a route. Let’s first of all wire up the route:

testApp.config(["$routeProvider", function($routeProvider) {
	$routeProvider
		.when("/welcome", {
			templateUrl: "partials/welcome.html",
			controller: "WelcomeController"
		})
		.when("/flickr", {
			templateUrl: "partials/flickr.html",
			controller: "FlickrController"
		})
		.otherwise({
			redirectTo: "/welcome"
		});
}]);

I need to create the template view in partials/flickr.html:

<h2>{{heading}}</h2>

<div class="row">
	<div class="col-sm-6 col-md-4" ng-repeat="image in images">
		<a class="thumbnail">
			<img style="width: 260px; height: 180px;" src="{{image}}"/>
		</a>
	</div>
</div>

I’m using another Angular directive here – ng-repeat – this duplicates a given block while iterating over an array. I’m using it to generate multiple thumbnails based on the array “images”. This is pretty much the same as the Aurelia version (with slightly different semantics for the repeating block). Now, on to the controller:

testApp.controller("FlickrController", [ "$scope", "$http",
	function FlickrController ($scope, $http) {
		$scope.heading = "Flickr";
		$scope.images = [];
		$scope.tags = "ranier";
		var url = "http://api.flickr.com/services/feeds/photos_public.gne";

		$http.jsonp(url, {
			params: {
				"tags": $scope.tags,
				"tagmode": "any",
				"format": "json",
				"jsoncallback": "JSON_CALLBACK"
			},
			"responseType": "json"
		}).success(function (data, status, headers, config) {
			$scope.images = data.items.map(function (v) {
				return v.media.m;
			});
		});
	}
]);

There is some very interesting stuff going on here. First of all, I am injecting the $http object via dependency injection. This allows me to do HTTP calls. I’m going to use it to call the Flickr API via JSONP. Note the jsoncallback parameter though. That’s very important. The JSONP element normally is returned via a callback which is the object wrapped in a function call. That function call must be “JSON_CALLBACK” so that the JSON object gets decoded properly. This took me quite a while to figure out. The $http methods all produce promises. However, this isn’t the Promise that I’ve come to love – it’s different. That means I call success() and error() instead of then() and catch() like normal.

Once I’ve got the data, I assign it to images (mapping each element along the way) and the data binding takes care of the rest.

Thoughts so far

Obviously, I’m going to compare the Angular version to the Aurelia version. Angular feels more fully featured than Aurelia and the community in Angular is obviously much bigger. One of the things I found particularly intriguing was the decoupling of the data source from the models. Aurelia has yet to tackle the data problem – they are concentrating on controllers and views.

However, for controllers and views (and particularly multiple controllers and views), Angular just seems much more “heavy” than the Aurelia counterpart. I wrote less code in the Aurelia version than the Angular version.

More To Come!

I haven’t finished yet, but I’ve written a lot of code and learned a lot about Angular. The Angular app was so big that I had to split it into two posts. In the next post I’ll take a look at implementing the menu bar and authentication. Stay tuned!