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.
You'll need to download angular.js.
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.
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.
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'.
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>
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.
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>
In the example above, ng-app creates a global scope, ng-controller creates a nested scope, and ng-repeat creates another nested scope.
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);
}));
});
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.
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 1 refactored to use components.
<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>
angular.module('appName', []);
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.
<ul>
<li ng-repeat="phone in $ctrl.phones">
<span>{{ phone.name }}</span>
<p>{{ phone.spinnet }}</p>
</li>
</ul>
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);
}));
});
});
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.
<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>
The appName module now registers phoneList as a dependency.
angular.module('appName', ['phoneList']);
angular.module('phoneList', []);
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' }
];
}
});
<ul>
<li ng-repeat="phone in $ctrl.phones">
<span>{{ phone.name }}</span>
<p>{{ phone.spinnet }}</p>
</li>
</ul>
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);
}));
});
});
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]; };
$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: <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
};
};
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>
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>
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>
<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" />
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.
[
{ name:'Nexus', snippet:'fast and furious' },
{ name:'Galaxy', snippet:'to infinity and beyond' },
{ name:'Motorola', snippet:'next next gen' }
]
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;
});
}
});
ng-src keeps the browser from trying to interpreting "{{...}}" as a url.
<img ng-src="{{ x.imageUrl }}" alt="{{ x.name }}" />
//in template
<span ng-style="$ctrl.myStyles"></span>
//in controller
$scope.myStyles = { color:"red", padding:"50px" };
angular.module('core', []);
angular.module('core').filter('checkmark', function() {
return function(input) {
//these are the unicode chars for check-mark and x-mark
return input ? '\u2713' : '\u2718';
};
});
angular.module('appName', ['core']);
Features:<br/>
Infrared {{ $ctrl.phone.connectivity.infrared | checkmark }}<br/>
GPS {{ $ctrl.phone.connectivity.gps | checkmark }}
<ng-keyup ng-keyup="expression"></ng-keyup>
<div ng-keyup="expression"></div>
<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;
};
Start:
function Ctrl($scope, $timeout) {
$timeout(callbackFunction, delayMilliseconds);
}
Cancel:
$timeout.cancel();
<td ng-mousedown="expression"></td>
<td ng-mousedown="onMouseDown($event)"></td>
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
<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>
angular.module('appName', ['ngRoute', 'phoneDetail']);
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');
}
]);
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']);
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;
}]
});
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.
<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>
.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;
}
To use CSS Transitions, you must specify a class on th element.
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;
}
}
Supports ng-enter and ng-leave.
Supports ng-enter and ng-leave.
Supports ng-enter and ng-leave.
Supports ng-enter and ng-leave.
Supports ng-enter and ng-leave.
Supports ng-enter and ng-leave.
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;
}