User Tools

Site Tools


angularjs:angularjs_tutorial

An AngularJS Tutorial

A lot of AngularJS books and tutorials start with the framework's data binding features, then move on to controllers and models, and then finally to routes. This sequence doesn't fit the way my brain wants to grok things. AngularJS is a framework for writing SPAs using an MVC-ish architecture. Thus it makes more sense to me to learn the framework the way I learn other MVC-ish frameworks:

  1. Routing
  2. Controllers
  3. Views
  4. Models
  5. Additional groovy bits

So, this is my lame-ass attempt to do that. 1) It assumes you know at least a little bit how MVC works and what a SPA is. I will focus exclusively on what you (i.e., I) will probably want to do with the framework rather than the million and a half things you can do with it and the gazillion ways of doing those things.

Some people describe the AngularJS learning curve as being shaped like a hockey stick: fundamental concepts are easy, advanced concepts not as much. This tutorial only pretends to cover the short part of the stick.

Startup

To use AngularJS, you'll want to declare that your page is an AngularJS app and specify the name of the app module it will use. You do that using the data-ng-app directive, which the framework will detect and process:

<html data-ng-app="RoutingDemoApp">

Throughout this tutorial, I use the the valid but less common data-ng* forms of AngularJS directives rather than the invalid but more common ng* forms. I like valid code.

Next, you'll need to get AngularJS via download, Bower, or CDN and include it in your HTML page:

<script src="lib/angularjs/angular.min.js"></script>

At the time of this writing, I recommend downloading the latest stable directly since additional modules that are critical to writing SPAs with AngularJS are not available through Bower and are not publicized by Google CDN.

You can also link just the core to the CDN and use local downloads for everything else.

Finally, you'll need some JavaScript that creates the “RoutingDemoApp” module instance for AngualrJS to use:

var app = angular.module('RoutingDemoApp', []);

The second parameter (in this example an empty array) is not optional: it's where additional modules (i.e., dependencies) that your app module will use are specified. You'll almost certainly want to put this JS in a separate file, say, app.js.

The resulting not-doing-anything-interesting-yet files might look like:

index.html
<!DOCTYPE html>
<html data-ng-app="RoutingDemoApp">
 
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>An AngularJS app</title>
    <script src="lib/angularjs/angular.min.js"></script>
    <script src="js/app.js"></script>
</head>
 
<body>
    <h1>An AngularJS app</h1>
</body>
 
</html>
app.js
var demoApp = angular.module('RoutingDemoApp', []);

Routing

Preparing index.html

To use AngularJS's routing, you'll need to use the angular-route.min.js dependency. Include an additional JS file, angular-route.min.js, after the angular-min.js include:

<script src="lib/angularjs/angular.min.js"></script>
<script src="lib/angularjs/angular-route.min.js"></script>

As of this writing, Bower doesn't have a package for this file and Google's CDN doesn't publicize it, so you'd do best to download it directly from the latest stable release.

Next, you'll want to create an element into which AngularJS will render its payload. This is done with the data-ng-view directive. This results in the following index.html:

index.html
<!DOCTYPE html>
<html data-ng-app="RoutingDemoApp">
 
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Routing Demo (AngularJS)</title>
    <script src="lib/angularjs/angular.min.js"></script>
    <script src="lib/angularjs/angular-route.min.js"></script>
    <script src="js/app.js"></script>
</head>
 
<body>
    <h1>Routing Demo (AngularJS)</h1>
    <nav>
        <ul>
            <li><a href="#">Home</a></li>
            <li><a href="#">About</a></li>
            <li><a href="#">Contact</a></li>
        </ul>
    </nav>
    <hr>
 
    <div data-ng-view="">
        <!-- AngularJS content will go here. -->
    </div>
</body>
 
</html>

Specifying routes

You next need to tell the your app instance that uses the ngRoute routing module by specifying it in the second parameter of angular.module. You also need to define some routes. This yields the following app.js, wherein I have specified exactly one route:

app.js
var demoApp = angular.module('RoutingDemoApp', ['ngRoute']);
 
demoApp.config(function ($routeProvider) {
    $routeProvider.when('/', {
        //controller: 'homeController',
        templateUrl: 'views/home.html'
    });
});

$routeProvider is one of AngularJS's magical objects that (surprise!) is responsible for handling routing. In this case it's being injected into the config method's argument, and in that function the actual routes are specified with $routeProvider's when method. The first parameter specifies the route (in this case the app's root route), and the second is a configuration object.

controller and templateUrl properties

In AngularJS, a controller is a JavaScript function that is responsible for collecting the needed data to fulfill a page view request, and a view is a template file (typically full of HTML and AngularJS directives) used to show that data in the browser. The controller for a route is specified in the config object's controller property and the view in the templateUrl property.

Routes typically specify both a controller and a templateUrl, and there is often a one-to-one correspondence between them (i.e., for each controller there is a unique templateUrl). In the example above, I commented out the controller property because for this simple example all I want to do is render some static HTML, which is located at views/home.html:

home.html
<h2>Hello, there!</h2>
<p>This is my home page. Fascinating, isn't it?</p>

Multiple routes

Additional routes can be specified by chaining:

app.js
var demoApp = angular.module('RoutingDemoApp', ['ngRoute']);
 
demoApp.config(function ($routeProvider) {
    $routeProvider.
        when('/', {
            templateUrl: 'views/home.html'
        }).
        when('/about', {
            templateUrl: 'views/about.html'
        }).
        when('/contact', {
            templateUrl: 'views/contact.html'
        }).
        otherwise({
            redirectTo: '/'
        });
});
about.html
<h2>About me</h2>
<p>Enough about me. Let's talk about you. What do you think of me?</p>
contact.html
<h2>Contact</h2>
<ul>
    <li>tel: +1.212.555.5555</li>
    <li>email: wh@tev.er</li>
</ul>

Note the catch-all otherwise method and the redirectTo property.

Route URLs

As is de rigueur for SPA frameworks, AngularJS uses anchor tag syntax for its internal URLs to keep requests from being sent to the sever. Therefore, the routes you register with the $routeProvider will be relative to <filename>#. In other words, if you published the example above at http://yoursite.com,

So, to make our so-far useless navigation useful, we need to update the links as follows:

index.html
<!DOCTYPE html>
<html data-ng-app="RoutingDemoApp">
 
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Routing Demo (AngularJS)</title>
    <script src="lib/angularjs/angular.min.js"></script>
    <script src="lib/angularjs/angular-route.min.js"></script>
    <script src="js/app.js"></script>
</head>
 
<body>
    <h1>Routing Demo (AngularJS)</h1>
    <nav>
        <ul>
            <li><a href="#/">Home</a></li>
            <li><a href="#/about">About</a></li>
            <li><a href="#/contact">Contact</a></li>
        </ul>
    </nav>
    <hr>
 
    <div data-ng-view="">
        <!-- AngularJS content will go here. -->
    </div>
</body>
 
</html>

We now have a complete single-page static-content app. It's a whole new 1998.

There are additional routing concepts we'll need to explore—such as adding parameters to routes—but first we'll have to get our heads around controllers and templates.

Controllers and Templates

The typical URL request handling process in AngularJS unfolds as follows:

  1. The $routeProvider determines which controller and template will be used to fulfill the request.
  2. The controller is invoked, and it goes about generating the data needed by the request.
    • Typically, the controller will call on models (discussed later) to generate the data.
  3. The generated data is fused into the template to produce the HTML that is ultimately rendered.

Controllers

An AngularJS controller is just a JavaScript function, and it can be defined anywhere. There are a number of “best” practices for where and how to define controllers. An easy but problematic way is to define a function in app.js or some other file:

function uselessController() {
    console.log('yawn.');
}

This works but messes with the global namespace and supposedly has other issues (e.g., with minification FIXME ref?). It also offends my sense of modularity.

Another way is to define it using the app's controller method:

demoApp.controller('uselessController', function () {
    console.log('yawn.');
});

I can tolerate that.

You tell the $routeProvider about a controller by setting the controller property for a route to a string that is the name of the controller:

...
when('/', {
    controller: 'uselessController',
    templateUrl: 'views/bored.html'
})
...

$scope

Controllers are almost always passed an AngularJS magical object called $scope. $scope is the glue between controllers and templates: whatever is contained within the $scope object after the controller completes execution will be available to the template.

Here is a new app.js where the root route is associated with a controller called homeController. In the controller, we set a property on $scope called name. That chunk of data will be available for use in the template.

app.js
var demoApp = angular.module('RoutingDemoApp', ['ngRoute']);
 
demoApp.config(function ($routeProvider) {
    $routeProvider.
        when('/', {
            controller: 'homeController',
            templateUrl: 'views/home.html'
        }).
        when('/about', {
            templateUrl: 'views/about.html'
        }).
        when('/contact', {
            templateUrl: 'views/contact.html'
        }).
        otherwise({
            redirectTo: '/'
        });
});
 
demoApp.controller('homeController', function ($scope) {
    $scope.name = "Olivia";
});

A suitable template (discussed next) for the above might me:

home.html
<h2>Hello, {{name}}!!</h2>
<p>This is my home page. Fascinating, isn't it?</p>

Templates

Templates are files containing HTML markup and AngularJS directives used to render output. We have seen simple template files above that serve up static content. In the example below, we have a new home.html that uses data made available to it via $scope:

home.html
<h2>Hello, {{name}}!!</h2>
<p>This is my home page. Fascinating, isn't it?</p>

The double curly-brackets notation is AngularJS-speak for, “Replace this double-curly stuff with the property from the $scope object that has the given name.” The process is called interpolation.

Pretty simple.

But wait. There's more.

Project structure

But first, a review of the project structure so far:

.
├── index.html
├── js
│   └── app.js
├── lib
│   └── angularjs
│       ├── angular.min.js
│       └── angular-route.min.js
└── views
    ├── about.html
    ├── contact.html
    └── home.html

'Nuff said.

Template directives

AngularJS provides some directives that allow you to implement control-flow-like structures in your templates. A common one is data-ng-repeat, which works as an iterator:

// file app.js
...
        when('/fruit', {
            controller: 'fruitController',
            templateUrl: 'views/fruit.html'
        }).
...
 
// Create a collection of fruits on $scope.
demoApp.controller('fruitController', function ($scope) {
    $scope.fruits = [
        {name: "apple",      isSweet: true},
        {name: "tomato",     isSweet: false},
        {name: "strawberry", isSweet: true},
        {name: "avocado",    isSweet: false}
    ];
});
fruit.html
<h2>Fruit</h2>
<h3>A list of fruits</h3>
<ul>
    <li data-ng-repeat="fruit in fruits">{{fruit.name}}</li>
</ul>

Another useful directive is data-ng-if:

...
<h3>A list of sweet fruits</h3>
<ul>
    <li data-ng-repeat="fruit in fruits" data-ng-if="fruit.isSweet">{{fruit.name}}</li>
</ul>
 
<h3>Got apple?</h3>
<ul>
    <li data-ng-repeat="fruit in fruits" data-ng-if="fruit.name === 'apple'">{{fruit.name}}</li>
</ul>
...

Other ones worth noting include data-ng-switch, data-ng-hide, and data-ng-show.

Template filters

Another way you can do some lightweight processing and formatting of data in the template is by using filters. They can be applied to both AngularJS data attributes (i.e., data-ng-<whatever>="...") and to template interpolations (i.e., {{whatever}} expressions). The syntax is:

the-thing-you-want-filter | filter-name : filter-parameters

A common application is sorting, which can be achieved with the orderBy filter:

<ul>
    <li data-ng-repeat="fruit in fruits | orderBy:'name'">{{fruit.name}}</li>
</ul>

Here, we add an uppercase filter to the fruit.name interpolation to make it yell (because you should be eating more fruit):

<ul>
    <li data-ng-repeat="fruit in fruits | orderBy:'name'">{{fruit.name | uppercase}}</li>
</ul>

In addition to orderBy and uppercase, there are filters for formatting currency, formatting dates, converting JavaScript objects into JSON, making content limitTo a particular size, converting strings to lowercase, formatting a number, and a pretty powerful filter named filter that lets you select a subset of items from array against some rather flexible matching criteria. And (I am lead to believe) you can create your own filters.

Filters can be chained if you want to apply more than one filter at a time:

<h3>A sorted list of sweet fruits</h3>
<ul>
    <li data-ng-repeat="fruit in fruits | filter:{isSweet:'true'} | orderBy:'name'">{{fruit.name}}</li>
</ul>

If you find yourself going crazy with control-flow-like directives and/or filters in templates, you might want to ask yourself whether the templates have exceeded their responsibility and whether some of the logic in them ought to be moved to the controllers or models.

Advanced routing

Parameterized routes

One of the things any app worth being an app will have to do is handle parameterized requests. For example, maybe you want yoursite.com/#/fruit to give a list of fruit and yoursite.com/#/fruit/<fruitname> to provide more information on a particular fruit. (Why did I pick fruit for these examples? I should have picked something more interesting like whales or guitar picks.)

In this example, the <fruitname> should be treated as a parameter.

The first step in doing this is to specify a parameterized route:

...
        when('/fruit/:fruitname', {
            controller: 'fruitDetailController',
            templateUrl: 'views/fruitDetail.html'
        }).
...

A colon in front of a token in the path indicates that it is a parameter.

Next, the controller. You can access the parameters passed in the route by using another of AngularJS's magical objects: $routeParams. It, like $scope, needs to be passed to the controller if you want to use it:

demoApp.controller('fruitDetailController', function ($scope, $routeParams) {
    var fruitname = $routeParams.fruitname;
 
    // In a real app, I would do something really smart to generate
    // the data you want about fruitname and then put that data into
    // $scope. But for now, I'm going to be very lazy:
    $scope.fruit = {
            name: fruitname,
            link: "https://lmddgtfy.net/?q=" + fruitname
        };
});

And finally, the template—where there's nothing really new:

fruitDetail.html
<h2>{{fruit.name}}</h2>
<p>
    I don't know that much about the {{fruit.name}}. 
    But <a href="{{fruit.link}}">these people</a> probably do.
</p>

Multiple parameters

A request can hold multiple parameters, each of which is available in $routeParams:

...
        when('/:foodtype/:foodname', {
            controller: 'foodDetailController',
            templateUrl: 'views/food-detail.html'
        }).
...
 
demoApp.controller('foodDetailController', function ($scope, $routeParams) {
    var type = $routeParams.foodtype,
        name = $routeParams.foodname;
 
    // Still being lazy:
    $scope.food = {
            type: type,
            name: name,
            link: "https://lmddgtfy.net/?q=" + type + ' ' + name
        };
});
food-detail.html
<h2>{{food.name}} ({{food.type}})</h2>
<p>
    I don't know that much about the {{food.type}} called {{food.name}}.
    But <a href="{{food.link}}">these people</a> probably do.
</p>

Models

In “traditional” server-side MVC frameworks, the controller typically snarfs data from/to data models—which are usually high-level wrappers around database tables that the controller interacts with in a nice, OOP-y way.

In AngularJS, models (or the effective analog) come in the form of Factories, Services, Providers, and Values.

Factory

An AngularJS factory is one way to encapsulate data models. It is defined on the app's module as an object that has methods for doing things of interest:

myApp.factory('sillyFactory', function () {
    var factory = {}; // TODO: Is the name 'factory' magical here?
 
    factory.getCarter = function () {
        return 'Carter';
    };
 
    factory.getShorty = function () {
        return 'Shorty';
    };
 
    factory.getAll = function () {
        return ['Carter', 'Shorty', 'Smart'];
    };
 
    return factory;
});

To use a factory in a controller, you need to inject it—here as the second parameter in the controller definition:

myApp.controller('someController', function ($scope, sillyFactory) {
    $scope.leFoo = sillyFactory.getCarter();
});

Note that factories know nothing of $scope, controllers. or templates. Nice and clean.

In the following, we have rewritten the fruitController to use a fruitFactory. The only upside to this complication for now is that the fruits in the factory can be shared with other controllers. Ultimately the associated data doesn't have to be hard-coded; it might come from an API call or database or elsewhere.

// file app.js //
...
demoApp.controller('fruitController', function ($scope, fruitFactory) {
    // Get some fruit from the factory:
    $scope.fruits = fruitFactory.gimmieFruit();
});
...
demoApp.factory('fruitFactory', function () {
    var factory = {},
        fruitList;    // the data of interest.
 
    // Some dummy data:
    fruitList = [
        {name: "apple",      isSweet: true},
        {name: "tomato",     isSweet: false},
        {name: "strawberry", isSweet: true},
        {name: "avocado",    isSweet: false}
    ];
 
    // Public methods
    factory.gimmieFruit = function () {
        return fruitList;
    };
 
    return factory;
});

Service

A service is similar to a factory except there is no intermediate object:

// file app.js //
...
demoApp.controller('fruitController', function ($scope, fruitService) {
    // Get some fruit from the service:
    $scope.fruits = fruitService.gimmieFruit();
});
...
demoApp.service('fruitService', function () {
    var fruitList;    // the data of interest.
 
    // Some dummy data:
    fruitList = [
        {name: "apple",      isSweet: true},
        {name: "tomato",     isSweet: false},
        {name: "strawberry", isSweet: true},
        {name: "avocado",    isSweet: false}
    ];
 
    // Public methods
    this.gimmieFruit = function () {
        return fruitList;
    };
});

I'm not going to cover and values and providers (for now?).

The factory and service we created above are very simplistic. In a more typical app, you would put methods for doing CRUD operations into your factories and services. All we've done so far is some primitive “R”. To do “C”, “U”, and “D”, it will help a thing or two about data binding.

Data binding

TODO

AJAX and APIs

TODO

Additional modularization

TODO

Conclusion

That's about it for this short tour of the short end of AngularJS's hockey stick. There's tons more to explore. Get your Google (or Duck) on and go for it.

Resources

The official AngularJS API documentation can be a bit confounding, but it's indispensable.

I found Dan Wahlin's AngularJS in 60-ish minutes and Jeremy Howard's AngularJS end-to-end web app tutorial video series helpful introductions.

Apparently, egghead.io is really good for expanding your knowledge of the framework with short, highly focused videos.

I've got a copy of Green and Seshadri's AngularJS, but apart from looking at the table of contents, I haven't read it.

All images used here save that of Derek Zoolander and the AngularJS logo are in the public domain.


Copyright © 2014 Mithat Konar. All rights reserved.

1)
This might turn out to be a bad way of approaching AngularJS; we'll see.
angularjs/angularjs_tutorial.txt · Last modified: 2015/06/25 12:30 by mithat