====== An AngularJS Tutorial ====== [[https://angularjs.org/|{{:angularjs:angularjs-logo48.png?nolink&36|A}}]] lot of [[https://angularjs.org/|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 [[wp>Single_page_application|SPA]]s using an [[wp>https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller|MVC]]-ish architecture. Thus it makes more sense to me to learn the framework the way I learn other MVC-ish frameworks: - Routing - Controllers - Views - Models - Additional groovy bits So, this is my lame-ass attempt to do that. ((This might turn out to be a [[http://jan.varwig.org/archive/how-to-do-nested-views-in-angularjs-hint-dont|bad way of approaching AngularJS]]; we'll see.)) 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 [[https://www.google.com/#q=angularjs+hockey+stick|hockey stick]]: fundamental concepts are easy, advanced concepts not as much. This tutorial only pretends to cover the short part of the stick. ===== Startup ===== {{ :angularjs:power-button300.jpg?nolink |}} 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: 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 [[https://angularjs.org/|download]], [[http://bower.io/search/?q=angular|Bower]], or [[https://developers.google.com/speed/libraries/devguide#angularjs|CDN]] and include it in your HTML page: At the time of this writing, I recommend [[https://code.angularjs.org/|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: An AngularJS app

An AngularJS app

var demoApp = angular.module('RoutingDemoApp', []); ===== Routing ===== {{ :angularjs:traffic-sign300.jpg?nolink |}} ==== 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: 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 [[https://code.angularjs.org/|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'': Routing Demo (AngularJS)

Routing Demo (AngularJS)


==== 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: 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'':

Hello, there!

This is my home page. Fascinating, isn't it?

=== Multiple routes === Additional routes can be specified by chaining: 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 me

Enough about me. Let's talk about you. What do you think of me?

Contact

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 ''#''. In other words, if you published the example above at http://yoursite.com, * the home page would be reached at http://yoursite.com/#/ or http://yoursite.com/index.html#/, * the "about" page would be reached at http://yoursite.com/#/about or http://yoursite.com/index.html#/about, * a request to plain http://yoursite.com or http://yoursite.com/index.html would initiate AngularJS redirects to http://yoursite.com/#/ and http://yoursite.com/index.html#/ (respectively). So, to make our so-far useless navigation useful, we need to update the links as follows: Routing Demo (AngularJS)

Routing Demo (AngularJS)


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 ===== {{ :angularjs:control-panel300.jpg?nolink |}} The typical URL request handling process in AngularJS unfolds as follows: - The ''$routeProvider'' determines which controller and template will be used to fulfill the request. - 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. - 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. 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:

Hello, {{name}}!!

This is my home page. Fascinating, isn't it?

==== 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'':

Hello, {{name}}!!

This is my home page. Fascinating, isn't it?

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 ''[[https://docs.angularjs.org/api/ng/directive/ngRepeat|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

A list of fruits

Another useful directive is ''[[https://docs.angularjs.org/api/ng/directive/ngIf|data-ng-if]]'': ...

A list of sweet fruits

Got apple?

...
Other ones worth noting include ''[[https://docs.angularjs.org/api/ng/directive/ngSwitch|data-ng-switch]]'', ''[[https://docs.angularjs.org/api/ng/directive/ngHide|data-ng-hide]]'', and ''[[https://docs.angularjs.org/api/ng/directive/ngShow|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-="..."%%'') 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 ''[[https://docs.angularjs.org/api/ng/filter/orderBy|orderBy]]'' filter:
  • {{fruit.name}}
Here, we add an ''[[https://docs.angularjs.org/api/ng/filter/uppercase|uppercase]]'' filter to the ''fruit.name'' interpolation to make it yell (because you should be eating more fruit):
  • {{fruit.name | uppercase}}
In addition to ''[[https://docs.angularjs.org/api/ng/filter/orderBy|orderBy]]'' and ''[[https://docs.angularjs.org/api/ng/filter/uppercase|uppercase]]'', there are filters for formatting ''[[https://docs.angularjs.org/api/ng/filter/currency|currency]]'', formatting ''[[https://docs.angularjs.org/api/ng/filter/date|date]]''s, converting JavaScript objects into ''[[https://docs.angularjs.org/api/ng/filter/json|JSON]]'', making content ''[[https://docs.angularjs.org/api/ng/filter/limitTo|limitTo]]'' a particular size, converting strings to ''[[https://docs.angularjs.org/api/ng/filter/lowercase|lowercase]]'', formatting a ''[[https://docs.angularjs.org/api/ng/filter/number|number]]'', and a pretty powerful filter named ''[[https://docs.angularjs.org/api/ng/filter/filter|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:

A sorted list of sweet fruits

  • {{fruit.name}}
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 ===== {{ :angularjs:gps_satnav_on300.png?nolink |}} ==== 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/'' 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 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:

{{fruit.name}}

I don't know that much about the {{fruit.name}}. But these people probably do.

==== 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.name}} ({{food.type}})

I don't know that much about the {{food.type}} called {{food.name}}. But these people probably do.

===== Models ===== [[http://youtu.be/YtQq0T3ExLs|{{ :angularjs:zoolander300.jpg?nolink |}}]] 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 [[wp>https://en.wikipedia.org/wiki/Create,_read,_update_and_delete|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 ===== {{ :angularjs:icehockey.png?nolink |}} 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 [[https://www.google.com/#q=angularjs|Google]] (or [[https://duckduckgo.com/?q=angularjs|Duck]]) on and go for it. ===== Resources ===== The official [[https://docs.angularjs.org/api|AngularJS API documentation]] can be a bit confounding, but it's indispensable. I found [[https://www.youtube.com/channel/UCtbxTmNfHcXLV5nfpnQxFkw|Dan Wahlin]]'s //AngularJS in 60-ish minutes// and [[https://www.youtube.com/user/howardjeremyp|Jeremy Howard]]'s //AngularJS end-to-end web app tutorial// video series helpful introductions. Apparently, [[https://egghead.io/|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 //[[http://shop.oreilly.com/product/0636920028055.do|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 [[http://mithatkonar.com|Mithat Konar]]. All rights reserved.