(This is largely pulled from https://docs/angularjs.org/tutorial/)

About

AngularJS is a MVW framework, meaning Model-View-Whatever. They don't takes a stance on whether to use a controller, a service, or whatever.

AngularJS helps create dynamic webpages.

Requirements

You'll need to download angular.js.

Minification

AngularJS relies on matching strings and variable names to html attributes, so minification can totally break it. There are ways around this that I haven't learned yet.
Naming Conventions

kebab-case for custom html attributes
camelCase for the corresponding directives which implement them

x.spec.js contains tests for x.js.

x.component.js contains the component x.
x.component.spec.js contains the tests for component x.

The $ prefix is reserved for angular-provided services.
The $$ prefix is reserved for private-do-not-touch angular properties.

Organization

The test (spec) file resides with the file it tests.

One feature per file, be it a component, a controller, or a template.

All files for a component reside in a folder with the component's name.

Ex:

app/
    app.js
    phone-list/
        phone-list.component.js
        phone-list.component.spec.js
    shared/

The shared folder can be called 'shared', 'common', or 'core'.

Phone Example 1

Templates

ng-app marks the scope of your AngularJS application. Only inner elements will be affected. You can add custom attribute ng-app to any html tag. This is your html template.


<html ng-app>
</html>

Bindings

AngularJS uses double 'staches to code within html tags.


<html ng-app>
    <p>{{ 'Hello '+name }}</p>
</html>

In this example, the variable 'name' is bound to the paragraph tag. When the variable 'name' is changed, the contents of the paragraph will be automatically updated.

AngularJS expressions (the contents of the double 'staches) are javascript-like. They are evaluated in the context of the current model scope.

Example

appName.html:

<html ng-app="appName">
    <head>
        <script src="angular.js" />
        <script src="appName.js" />
    </head>
    <body ng-controller="PhoneListController">
        <ul>
            <li ng-repeat="phone in phones">
                <span>{{ phone.name }}</span>
                <p>{{ phone.spinnet }}</p>
            </li>
        </ul>
    </body>
</html>

appName.js:

var appName = angular.module('appName', []);
appName.controller('PhoneListController', function PhoneListController($scope) {
    $scope.phones = [
        { name:'Nexus', snippet:'fast and furious' },
        { name:'Galaxy', snippet:'to infinity and beyond' },
        { name:'Motorola', snippet:'next next gen' }
    ];
});

Important connections:
    ng-app="appName" => angular.module('appName'...
    ng-controller="PhoneListController" => appName.controller('PhoneListController'...
    ng-repeat="...phones"> => $scope.phones
    
resulting html:

<html>
    <head>
        <script src="angular.js" />
        <script src="appName.js" />
    </head>
    <body>
        <ul>
            <li>
                <span>Nexus</span>
                <p>fast and furious</p>
            </li>
            <li>
                <span>Galaxy</span>
                <p>to infinity and beyond</p>
            </li>
            <li>
                <span>Motorola</span>
                <p>next next gen</p>
            </li>
        </ul>
    </body>
</html>

Scope

In the example above, ng-app creates a global scope, ng-controller creates a nested scope, and ng-repeat creates another nested scope.

Unit Tests

Based on the previous example.

app.spec.js (assuming controller is in global namespace):

describe('PhoneListController', function() {
    it('description of test', function() {
        var scope = {};
        var controller = new PhoneListController(scope);
        expect(scope.phones.length).toBe(3);
    });
});

app.spec.js (when controller is not in global namespace):

describe('PhoneListController', function() {
    beforeEach(module('appName')); //load module before each test
    it('description of test', inject(function($controller) {
        var scope = {};
        var controller = $controller('PhoneListController', { $scope: scope });
        expect(scope.phones.length).toBe(3);
    }));
});
Components

A component is a combination of template and controller. They have a private scope, that isolates them from the rest of a project. They enable code and template reuse.

Each instance on a component will have its own "isolate scope", ie private scope.

Components are a subset of directives.

Component Definition Object

Simple example:

angular.module('appName').component('greetUser', {
    template: 'Hello, {{ $ctrl.user }}',
    controller: function GreetUserController() {
        this.user = 'Steve';
    }
});
//then, in the view
<greet-user></greet-user>

Note that we say 'greetUser' in the javascript, and 'greet-user' in the html. This is the convention AngularJS expects.

$ctrl is the default alias for the component's private scope.

The controller is optional; leave it out if your component does not need behavior.

The template can be an html string, or a filename to an html file.

Phone Example 2

Phone example 1 refactored to use components.

App/index.html

<html ng-app="appName">
    <head>
        <script src='../angular.js' />
        <script src='app.js' />
        <script src='phone-list/phone-list.component.js' />
    </head>
    <body>
        <phone-list></phone-list>
    </body>
</html>

App/app.js

angular.module('appName', []);

App/phone-list/phone-list.component.js

angular.module('appName').component('phoneList', {
    templateUrl: 'phone-list.template.html',
    controller: function PhoneListController() {
        this.phones = [
            { name:'Nexus', snippet:'fast and furious' },
            { name:'Galaxy', snippet:'to infinity and beyond' },
            { name:'Motorola', snippet:'next next gen' }
        ];
    }
});

Loading a template through a file will cause extra round trips to the server. See $templateRequest and $templateCache to prevent that.

App/phone-list/phone-list.template.js

<ul>
    <li ng-repeat="phone in $ctrl.phones">
        <span>{{ phone.name }}</span>
        <p>{{ phone.spinnet }}</p>
    </li>
</ul>

App/phone-list/phone-list.component.spec.js

describe('phoneList', function() {
    beforeEach(module('appName'));
    describe('PhoneListController', function() {
        it('description of test', inject(function($componentController) {
            var ctrl = $componentController('phoneList');
            expect(ctrl.phones.length).toBe(3);
        }));
    });
});

Modules

Each feature should declare its own module, and all features should register to it.

"appName" is the module in all of the examples so far, and each component says "angular.module('appName')...".

You want the entire component to be easy to copy to another project without editing them. So now each part of the phone-list component needs to reference a 'phone-list' module instead of the 'appName' module.

Phone Example 3

App/index.html

<html ng-app="appName">
    <head>
        <script src='../angular.js' />
        <script src='app.module.js' />
        <script src='phone-list/phone-list.module.js' />
        <script src='phone-list/phone-list.component.js' />
    </head>
    <body>
        <phone-list></phone-list>
    </body>
</html>

App/app.module.js

The appName module now registers phoneList as a dependency.


angular.module('appName', ['phoneList']);

App/phone-list/phone-list.module.js

angular.module('phoneList', []);

App/phone-list/phone-list.component.js

angular.module('phoneList').component('phoneList', {
    template: 'phone-list.template.html',
    controller: function PhoneListController() {
        this.phones = [
            { name:'Nexus', snippet:'fast and furious' },
            { name:'Galaxy', snippet:'to infinity and beyond' },
            { name:'Motorola', snippet:'next next gen' }
        ];
    }
});

App/phone-list/phone-list.template.js

<ul>
    <li ng-repeat="phone in $ctrl.phones">
        <span>{{ phone.name }}</span>
        <p>{{ phone.spinnet }}</p>
    </li>
</ul>

App/phone-list/phone-list.component.spec.js

describe('phoneList', function() {
    beforeEach(module('appName'));
    describe('PhoneListController', function() {
        it('description of test', inject(function($componentController) {
            var ctrl = $componentController('phoneList');
            expect(ctrl.phones.length).toBe(3);
        }));
    });
});

Ng-repeat

Data Source

Hard coded array:

<table>
    <tr><th>Row Number</th></tr>
    </tr ng-repeat="i in [0,1,2,3,4]"><td>{{ i }}</td></tr>
</table>

Array set in controller:

//in template
<table>
    <tr><th>Row Number</th></tr>
    </tr ng-repeat="i in myList"><td>{{ i }}</td></tr>
</table>
//in controller
$scope.myList = [0,1,2,3,4];

Array returned from controller function:

//in template
<table>
    <tr><th>Row Number</th></tr>
    </tr ng-repeat="i in GetMyList()"><td>{{ i }}</td></tr>
</table>
//in controller
$scope.GetMyList = function() { return [0,1,2,3,4]; };

In-scope Variables

$index: index of current element, starting at 0
$first: true if is first element
$last: true if is last element
$middle: true if is not first or last element
$even: true when $index is even
$odd: true when $index is odd

Example: comma-separated list

<span ng-repeat="x in list">{{ x }}{{ $last ? '' : ', ' }}</span>

Because ng-repeat has its own scope, it must reference its $parent to update the model.

<div>{{ sortBy }}</div>
<div ng-repeat="item in list">
    <input type="radio" ng-model="$parent.sortBy" name="sortBy" ng-value="item" />
</div>

Sort


Sort: <select ng-model="$ctrl.sort">
        <option value="name">Alphabetical</option>
        <option value="age">Newest</option>
    </select>
<br/>
<ul>
    <li ng-repeat="phone in $ctrl.phones | orderBy: $ctrl.sort">
        <span>{{ phone.name }}</span>
        <p>{{ phone.snippet }} ({{ phone.age }} years old)</p>
    </li>
</ul>

OrderBy returns an array of elements ordered by the 'comparator' function. The 'comparator' functions uses the values returned by the 'expression'. 'Reverse' can be true or false.


{{ x in list | orderBy: expression : reverse : comparator }}

{{ x in list | orderBy: 'property' }} //uses x['property']
{{ x in list | orderBy: property }} //uses x[property] where property is a variable

'expression' can be a list of properties (order by x, then by y, then by z...)

{{ x in list | orderBy: [a, b, c] }}

You can also use a method as the 'expression'. This can be useful if the property name contains spaces.

//in template
{{ x in list | orderBy: MyExpression }}
//in controller
$scope.MyExpression = function(collection) {
    return collection[$scope.sortBy];
};

Custom comparator:

//in template
<p ng-repeat="x in list | orderBy: x : MyComparator"></p>
//in controller
$scope.MyComparer = function(obj1, obj2) {
    return (obj1.value < obj2.value) ? -1 : 1;
};

The custom comparator parameters have three properties.
Obj.type = data type
Obj.index = index in list
Obj.value = value

To pass extra parameters into a custom comparator, return a closure:

//in template
<p ng-repeat="x in list | orderBy: x : MyComparator(y)"></p>
//in controller
$scope.MyComparer = function(y) {
    return function(obj1, obj2) {
        //can use y, obj1, and obj2 here
    };
};

Filters

This filter is applied to the array $ctrl.phones before the repeater iterates over it.

Search: <input ng-model="$ctrl.query" /><br/>
<ul>
    <li ng-repeat="phone in $ctrl.phones | filter: $ctrl.query">
        <span>{{ phone.name }}</span>
        <p>{{ phone.snippet }}</p>
    </li>
</ul>

This custom filter function is defined in code first.

//in javascript
app.filter('reverse', function() {
    return function(list) { return list.slice().reverse(); }
});

//in html
<td ng-repeat="x in list | reverse"></td>

Duplicate Data

ng-repeat defaults to using the current value as the id of the element. If you know your list may have duplicate values, explicitly set the id value.


<div ng-repeat="x in list track by $index"></div>

Repeat Multiple Elements

You can repeat a set of elements, as well as just a single element.
Everything from ng-repeat-start to ng-repeat-end will be repeated. Make sure these are place in sibling elements.


<p ng-repeat-start="x in list">Title: {{ x.title }}</p>
<span>{{ x.description }}</span><br/>
<span ng-repeat-end>{{ x.notes }}</span>
Radio Buttons


<input type="radio" name="color" ng-model="$ctrl.color" value="red" />
<input type="radio" name="color" ng-model="$ctrl.color" value="blue" />


//in controller
$scope.optionA = { id:2, value:"green" };
//in template
<input type="radio" name="color" ng-model="$ctrl.color" ng-value="$ctrl.optionA" />
Flow Control

Ng-if


<td ng-repeat="x in list">
    <div ng-if="x < 2">{{ x }}</div>
</td>
Fetch Data

Use angular service $http to fetch remote data. This example gets a text file, but it could just have easily called a MVC web page.

App/phones/phones.json

[
    { name:'Nexus', snippet:'fast and furious' },
    { name:'Galaxy', snippet:'to infinity and beyond' },
    { name:'Motorola', snippet:'next next gen' }
]

App/phone-list/phone-list.component.js

angular.module('phoneList').component('phoneList', {
    templateUrl: 'phone-list/phone-list.template.html',
    controller: function PhoneListController($http) {
        var self = this;
        $http.get('phones/phones.json').then(function(response) {
            self.phones = response.data;
        });
    }
});

Format Data

As JSON

<span>property value = {{ $crtl.x | json }}</span>
Images

ng-src keeps the browser from trying to interpreting "{{...}}" as a url.


<img ng-src="{{ x.imageUrl }}" alt="{{ x.name }}" />
Styles


//in template
<span ng-style="$ctrl.myStyles"></span>
//in controller
$scope.myStyles = { color:"red", padding:"50px" };
Custom Filters

App/core/core.module.js

angular.module('core', []);

App/core/checkmark/checkmark.filter.js

angular.module('core').filter('checkmark', function() {
    return function(input) {
        //these are the unicode chars for check-mark and x-mark
        return input ? '\u2713' : '\u2718';
    };
});

App/app.module.js

angular.module('appName', ['core']);

Template

Features:<br/>
    Infrared {{ $ctrl.phone.connectivity.infrared | checkmark }}<br/>
    GPS {{ $ctrl.phone.connectivity.gps | checkmark }}
Keyboard Events

Global

<ng-keyup ng-keyup="expression"></ng-keyup>

One Element

<div ng-keyup="expression"></div>

Examples


<input ng-keyup="count = count + 1" ng-init="count = 0" /> Keyup Count: {{ count }}


//in template
<input ng-keyup="MyHandler($event)" />
//in controller
$scope.MyHandler = function($event) {
    var keycode = $event.keyCode;
    var altKeyDown = $event.altKey;
};
Timeout

Start:

function Ctrl($scope, $timeout) {
    $timeout(callbackFunction, delayMilliseconds);
}

Cancel:

$timeout.cancel();
Mouse Events

One Element

<td ng-mousedown="expression"></td>

Examples


<td ng-mousedown="onMouseDown($event)"></td>

Routing

You'll need the angular-route.js library file.

You'll now have one x.html layout template page, used by all other pages, and many partial y.html template files.

Example route: index.html#!/some/path

Index.html

<head ng-app="appName">
    <script src="angular.js" />
    <script src="angular-route.js" />
    <script src="app.module.js" />
    <script src="app.config.js" />
    <script src="phone-list/phone-list.module.js" />
    <script src="phone-list/phone-list.component.js" />
    <script src="phone-detail/phone-detail.module.js" />
    <script src="phone-detail/phone-detail.component.js" />
</head>
<body>
    <div ng-view></div>
</body>

App/app.module.js

angular.module('appName', ['ngRoute', 'phoneDetail']);

App/app.config.js

The template values will replace <div ng-view></div>.

The prefix : means to treat what comes after as a variable.


angular.module('appName').config(['$locationProvider', '$routeProvider', 
    function config($locationProvider, $routerProvider) {
        $locationProvider.hashPrefix('!');
        $routeProvider
            .when('/phones', { template: '<phone-list></phone-list>' })
            .when('/phones/:phoneId', { template: '<phone-detail></phone-detail>' })
            .otherwise('/phones');
    }
]);

App/phone-detail/phone-detail.module.js

While it is not strictly required that phone-detail import ngRoute, since the global app level imported it, it is recommended that all components explicitly import their own dependencies.


angular.module('phoneDetail', ['ngRoute']);

App/phone-detail/phone-detail.component.js

The parameters defined in the route are made available through $routeParams.


angular.module('phoneDetail', {
    template: 'todo: detailed view of phone {{ $ctrl.phoneId }}',
    controller: ['$routeParams', function PhoneDetailController($routeParams) {
        this.phoneId = $routeParams.phoneId;
    }]
});
Animations

You'll need to download angular-animate.js, and include ngAnimate as a module dependency.

Angular animates using CSS Transition, CSS Keyframe Animation, and Javascript Callback Animation.

This example demonstrates show/hide.

Index.html

<div ng-init="checked = true">
    <input type="checkbox" ng-model="checked" /> is visible.
    <div class="content-area sample-show-hide" ng-show="checked">Content...</div>
</div>

Animations.css

.content-area {
    border: 1px solid black;
    margin-top: 10px;
    padding: 10px;
}
.sample-show-hide {
    transition: all linear 0.5s;
}
.sample-show-hide.ng-hide {
    opacity: 0;
}

Transitions

To use CSS Transitions, you must specify a class on th element.

NgRepeat

Adds class ng-enter as it adds elements
Adds class ng-leave as it removes elements
Adds class ng-move when it rearranges elements

Example:

//end of add/move actions
.myClass.ng-enter, .myClass.ng-move {
    transition: all 0.5s linear;
    opacity: 0;
}
//beginning of add/move actions
.myClass.ng-enter.ng-enter-active, .myClass.ng-move.ng-move-active {
    opacity: 1;
}
.myClass.ng-leave {
    animation: 0.5s my-animation;
}
@keyframes my-animation {
    from {
        opacity: 1;
    }
    to {
        opacity: 0;
    }
}

NgView

Supports ng-enter and ng-leave.

NgInclude

Supports ng-enter and ng-leave.

NgSwitch

Supports ng-enter and ng-leave.

NgIf

Supports ng-enter and ng-leave.

NgClass

Supports ng-enter and ng-leave.

NgShow NgHide

Supports ng-enter and ng-leave.

Trigger Animation

Triggering an animation when an element's class changes.


//html
<input type="button" value="set" ng-click="myCssVar = 'css-class'" />
<input type="button" value="clear" ng-click="myCssVar = ''" />
<span ng-class="myCssVar">Text Here</span>
//css
.css-class-add, .css-class-remove {
    transition: all cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.5s;
}
.css-class, .css-class-add.css-class-add-active {
    color: red;
    font-size: larger;
}
.css-class-remove.css-class-remove-active {
    color: black;
    font-size: smaller;
}