mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-24 12:05:46 +08:00
docs(all): convert <pre>/</pre> snippets to GFM snippets
This commit is contained in:
committed by
Peter Bacon Darwin
parent
2e641ac49f
commit
f7d28cd377
@@ -64,11 +64,11 @@ You may also want to setup a separate CSS file for defining CSS-based animations
|
||||
Animations in AngularJS are completely based on CSS classes. As long as you have a CSS class attached to a HTML element within
|
||||
your website, you can apply animations to it. Lets say for example that we have an HTML template with a repeater in it like so:
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<div ng-repeat="item in items" class="repeated-item">
|
||||
{{ item.id }}
|
||||
</div>
|
||||
</pre>
|
||||
```
|
||||
|
||||
As you can see, the `.repeated-item` class is present on the element that will be repeated and this class will be
|
||||
used as a reference within our application's CSS and/or JavaScript animation code to tell AngularJS to perform an animation.
|
||||
@@ -80,13 +80,13 @@ it will apply a `ng-move` class name.
|
||||
Taking a look at the following CSS code, we can see some transition and keyframe animation code set for each of those events that
|
||||
occur when ngRepeat triggers them:
|
||||
|
||||
<pre>
|
||||
/*
|
||||
```css
|
||||
/*
|
||||
We're using CSS transitions for when
|
||||
the enter and move events are triggered
|
||||
for the element that has the .repeated-item
|
||||
class
|
||||
*/
|
||||
*/
|
||||
.repeated-item.ng-enter, .repeated-item.ng-move {
|
||||
-webkit-transition:0.5s linear all;
|
||||
-moz-transition:0.5s linear all;
|
||||
@@ -95,22 +95,22 @@ occur when ngRepeat triggers them:
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
The ng-enter-active and ng-move-active
|
||||
are where the transition destination properties
|
||||
are set so that the animation knows what to
|
||||
animate.
|
||||
*/
|
||||
*/
|
||||
.repeated-item.ng-enter.ng-enter-active,
|
||||
.repeated-item.ng-move.ng-move-active {
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
We're using CSS keyframe animations for when
|
||||
the leave event is triggered for the element
|
||||
that has the .repeated-item class
|
||||
*/
|
||||
*/
|
||||
.repeated-item.ng-leave {
|
||||
-webkit-animation:0.5s my_animation;
|
||||
-moz-animation:0.5s my_animation;
|
||||
@@ -118,34 +118,34 @@ occur when ngRepeat triggers them:
|
||||
animation:0.5s my_animation;
|
||||
}
|
||||
|
||||
@keyframes my_animation {
|
||||
@keyframes my_animation {
|
||||
from { opacity:1; }
|
||||
to { opacity:0; }
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
Unfortunately each browser vendor requires
|
||||
its own definition of keyframe animation code...
|
||||
*/
|
||||
@-webkit-keyframes my_animation {
|
||||
*/
|
||||
@-webkit-keyframes my_animation {
|
||||
from { opacity:1; }
|
||||
to { opacity:0; }
|
||||
}
|
||||
|
||||
@-moz-keyframes my_animation {
|
||||
@-moz-keyframes my_animation {
|
||||
from { opacity:1; }
|
||||
to { opacity:0; }
|
||||
}
|
||||
|
||||
@-o-keyframes my_animation {
|
||||
@-o-keyframes my_animation {
|
||||
from { opacity:1; }
|
||||
to { opacity:0; }
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
The same approach to animation can be used using JavaScript code (**jQuery is used within to perform animations**):
|
||||
|
||||
<pre>
|
||||
```js
|
||||
myModule.animation('.repeated-item', function() {
|
||||
return {
|
||||
enter : function(element, done) {
|
||||
@@ -199,7 +199,7 @@ myModule.animation('.repeated-item', function() {
|
||||
removeClass : function(element, className, done) {}
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
With these generated CSS class names present on the element at the time, AngularJS automatically
|
||||
figures out whether to perform a CSS and/or JavaScript animation. If both CSS and JavaScript animation
|
||||
@@ -268,7 +268,7 @@ For a full breakdown of the steps involved during each animation event, refer to
|
||||
Animations within custom directives can also be established by injecting `$animate` directly into your directive and
|
||||
making calls to it when needed.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
myModule.directive('my-directive', ['$animate', function($animate) {
|
||||
return function(element, scope, attrs) {
|
||||
element.bind('click', function() {
|
||||
@@ -280,7 +280,7 @@ myModule.directive('my-directive', ['$animate', function($animate) {
|
||||
});
|
||||
};
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
## More about animations
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ This example shows the recommended path for integrating Angular with what we cal
|
||||
initialization.
|
||||
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" ng-app>
|
||||
<body>
|
||||
@@ -21,7 +21,7 @@ initialization.
|
||||
<script src="angular.js">
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
* Place the `script` tag at the bottom of the page. Placing script tags at the end of the page
|
||||
improves app load time because the HTML loading is not blocked by loading of the `angular.js`
|
||||
@@ -65,7 +65,7 @@ If the {@link api/ng.directive:ngApp `ng-app`} directive is found then Angular w
|
||||
portion of the DOM as an Angular application.
|
||||
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<!doctype html>
|
||||
<html ng-app="optionalModuleName">
|
||||
<body>
|
||||
@@ -73,7 +73,7 @@ If the {@link api/ng.directive:ngApp `ng-app`} directive is found then Angular w
|
||||
<script src="angular.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ or the need to perform an operation before Angular compiles a page.
|
||||
|
||||
Here is an example of manually initializing Angular:
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<body>
|
||||
@@ -100,7 +100,7 @@ Here is an example of manually initializing Angular:
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
Note that we have provided the name of our application module to be loaded into the injector as the second
|
||||
parameter of the {@link api/angular.bootstrap} function. Notice that `angular.bootstrap` will not create modules
|
||||
|
||||
@@ -60,12 +60,12 @@ during the compilation process. The directives can be placed in element names, a
|
||||
names, as well as comments. Here are some equivalent examples of invoking the {@link
|
||||
api/ng.directive:ngBind `ng-bind`} directive.
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<span ng-bind="exp"></span>
|
||||
<span class="ng-bind: exp;"></span>
|
||||
<ng-bind></ng-bind>
|
||||
<!-- directive: ng-bind exp -->
|
||||
</pre>
|
||||
```
|
||||
|
||||
A directive is just a function which executes when the compiler encounters it in the DOM. See {@link
|
||||
api/ng.$compileProvider#methods_directive directive API} for in-depth documentation on how
|
||||
@@ -187,7 +187,7 @@ a model on the compiled scope will be reflected in the DOM.
|
||||
Below is the corresponding code using the `$compile` service.
|
||||
This should help give you an idea of what Angular does internally.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
var $compile = ...; // injected into your code
|
||||
var scope = ...;
|
||||
var parent = ...; // DOM element where the compiled template can be appended
|
||||
@@ -205,7 +205,7 @@ This should help give you an idea of what Angular does internally.
|
||||
|
||||
// Step 4: Append to DOM (optional)
|
||||
parent.appendChild(element);
|
||||
</pre>
|
||||
```
|
||||
|
||||
### The difference between Compile and Link
|
||||
|
||||
@@ -229,14 +229,14 @@ moved to the compile function for performance reasons.
|
||||
|
||||
To understand, let's look at a real-world example with `ngRepeat`:
|
||||
|
||||
<pre>
|
||||
```html
|
||||
Hello {{user}}, you have these actions:
|
||||
<ul>
|
||||
<li ng-repeat="action in user.actions">
|
||||
{{action.description}}
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
```
|
||||
|
||||
When the above example is compiled, the compiler visits every node and looks for directives.
|
||||
|
||||
@@ -295,7 +295,7 @@ One of the most common use cases for directives is to create reusable components
|
||||
|
||||
Below is a pseudo code showing how a simplified dialog component may work.
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<div>
|
||||
<button ng-click="show=true">show</button>
|
||||
|
||||
@@ -306,7 +306,7 @@ Below is a pseudo code showing how a simplified dialog component may work.
|
||||
Body goes here: {{username}} is {{title}}.
|
||||
</dialog>
|
||||
</div>
|
||||
</pre>
|
||||
```
|
||||
|
||||
Clicking on the "show" button will open the dialog. The dialog will have a title, which is
|
||||
data bound to `username`, and it will also have a body which we would like to transclude
|
||||
@@ -314,7 +314,7 @@ into the dialog.
|
||||
|
||||
Here is an example of what the template definition for the `dialog` widget may look like.
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<div ng-show="visible">
|
||||
<h3>{{title}}</h3>
|
||||
<div class="body" ng-transclude></div>
|
||||
@@ -323,7 +323,7 @@ Here is an example of what the template definition for the `dialog` widget may l
|
||||
<button ng-click="onCancel()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</pre>
|
||||
```
|
||||
|
||||
This will not render properly, unless we do some scope magic.
|
||||
|
||||
@@ -334,14 +334,14 @@ the `onOk` and `onCancel` functions to be present in the scope. This limits the
|
||||
widget. To solve the mapping issue we use the `locals` to create local variables which the template
|
||||
expects as follows:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
scope: {
|
||||
title: '@', // the title uses the data-binding from the parent scope
|
||||
onOk: '&', // create a delegate onOk function
|
||||
onCancel: '&', // create a delegate onCancel function
|
||||
visible: '=' // set up visible to accept data-binding
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
Creating local properties on widget scope creates two problems:
|
||||
|
||||
@@ -367,7 +367,7 @@ surprise.
|
||||
|
||||
Therefore the final directive definition looks something like this:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
transclude: true,
|
||||
scope: {
|
||||
title: '@', // the title uses the data-binding from the parent scope
|
||||
@@ -377,5 +377,5 @@ scope: {
|
||||
},
|
||||
restrict: 'E',
|
||||
replace: true
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ is registered.
|
||||
The following example shows a very simple constructor function for a Controller, `GreetingCtrl`,
|
||||
which attaches a `greeting` property containing the string `'Hola!'` to the `$scope`:
|
||||
|
||||
```
|
||||
```js
|
||||
function GreetingCtrl($scope) {
|
||||
$scope.greeting = 'Hola!';
|
||||
}
|
||||
@@ -37,22 +37,22 @@ which attaches a `greeting` property containing the string `'Hola!'` to the `$sc
|
||||
Once the Controller has been attached to the DOM, the `greeting` property can be data-bound to the
|
||||
template:
|
||||
|
||||
```
|
||||
<div ng-controller="GreetingCtrl">
|
||||
{{ greeting }}
|
||||
</div>
|
||||
```js
|
||||
<div ng-controller="GreetingCtrl">
|
||||
{{ greeting }}
|
||||
</div>
|
||||
```
|
||||
|
||||
**NOTE**: Although Angular allows you to create Controller functions in the global scope, this is
|
||||
not recommended. In a real application you should use the `.controller` method of your
|
||||
{@link module Angular Module} for your application as follows:
|
||||
|
||||
```
|
||||
var myApp = angular.module('myApp',[]);
|
||||
```js
|
||||
var myApp = angular.module('myApp',[]);
|
||||
|
||||
myApp.controller('GreetingCtrl', ['$scope', function($scope) {
|
||||
$scope.greeting = 'Hola!';
|
||||
}]);
|
||||
myApp.controller('GreetingCtrl', ['$scope', function($scope) {
|
||||
$scope.greeting = 'Hola!';
|
||||
}]);
|
||||
```
|
||||
|
||||
We have used an **inline injection annotation** to explicitly specify the dependency
|
||||
@@ -68,21 +68,21 @@ then available to be called from the template/view.
|
||||
|
||||
The following example uses a Controller to add a method to the scope, which doubles a number:
|
||||
|
||||
```
|
||||
var myApp = angular.module('myApp',[]);
|
||||
```js
|
||||
var myApp = angular.module('myApp',[]);
|
||||
|
||||
myApp.controller('DoubleCtrl', ['$scope', function($scope) {
|
||||
$scope.double = function(value) { return value * 2; };
|
||||
}]);
|
||||
myApp.controller('DoubleCtrl', ['$scope', function($scope) {
|
||||
$scope.double = function(value) { return value * 2; };
|
||||
}]);
|
||||
```
|
||||
|
||||
Once the Controller has been attached to the DOM, the `double` method can be invoked in an Angular
|
||||
expression in the template:
|
||||
|
||||
```
|
||||
<div ng-controller="DoubleCtrl">
|
||||
Two times <input ng-model="num"> equals {{ double(num) }}
|
||||
</div>
|
||||
```js
|
||||
<div ng-controller="DoubleCtrl">
|
||||
Two times <input ng-model="num"> equals {{ double(num) }}
|
||||
</div>
|
||||
```
|
||||
|
||||
As discussed in the {@link concepts Concepts} section of this guide, any
|
||||
@@ -270,7 +270,7 @@ Although there are many ways to test a Controller, one of the best conventions,
|
||||
involves injecting the {@link api/ng.$rootScope $rootScope} and {@link api/ng.$controller $controller}:
|
||||
|
||||
**Controller Definition:**
|
||||
```
|
||||
```js
|
||||
var myApp = angular.module('myApp',[]);
|
||||
|
||||
myApp.controller('MyController', function($scope) {
|
||||
@@ -282,7 +282,7 @@ involves injecting the {@link api/ng.$rootScope $rootScope} and {@link api/ng.$c
|
||||
```
|
||||
|
||||
**Controller Test:**
|
||||
```
|
||||
```js
|
||||
describe('myController function', function() {
|
||||
|
||||
describe('myController', function() {
|
||||
@@ -310,7 +310,7 @@ describe('myController function', function() {
|
||||
If you need to test a nested Controller you need to create the same scope hierarchy
|
||||
in your test that exists in the DOM:
|
||||
|
||||
```
|
||||
```js
|
||||
describe('state', function() {
|
||||
var mainScope, childScope, grandChildScope;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ In addition to the above elements, scenarios may also contain helper functions t
|
||||
code in the `it` blocks.
|
||||
|
||||
Here is an example of a simple scenario:
|
||||
<pre>
|
||||
```js
|
||||
describe('Buzz Client', function() {
|
||||
it('should filter results', function() {
|
||||
input('user').enter('jacksparrow');
|
||||
@@ -41,7 +41,7 @@ it('should filter results', function() {
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
Note that
|
||||
[`input('user')`](https://github.com/angular/angular.js/blob/master/docs/content/guide/dev_guide.e2e-testing.ngdoc#L119)
|
||||
@@ -190,7 +190,7 @@ be negated with `not()`. For instance: `expect(element('h1').text()).not().toEqu
|
||||
|
||||
Source: https://github.com/angular/angular.js/blob/master/src/ngScenario/matchers.js
|
||||
|
||||
<pre>
|
||||
```js
|
||||
// value and Object comparison following the rules of angular.equals().
|
||||
expect(value).toEqual(value)
|
||||
|
||||
@@ -219,7 +219,7 @@ expect(value).toContain(expected)
|
||||
// number comparison using < and >
|
||||
expect(value).toBeLessThan(expected)
|
||||
expect(value).toBeGreaterThan(expected)
|
||||
</pre>
|
||||
```
|
||||
|
||||
# Example
|
||||
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples.
|
||||
@@ -239,7 +239,7 @@ Imagine the application to be structured into two views:
|
||||
2. a *detail view* which shows the entries' details and contains a delete button. When clicking the
|
||||
delete button, the user is redirected back to the *overview page*.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
beforeEach(function () {
|
||||
var deleteEntry = function () {
|
||||
browser().navigateTo('/entries');
|
||||
@@ -276,24 +276,24 @@ beforeEach(function () {
|
||||
// start deleting entries
|
||||
deleteEntry();
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
In order to understand what is happening, we should emphasize that ngScenario calls are not
|
||||
immediately executed, but queued (in ngScenario terms, we would be talking about adding
|
||||
future actions). If we had only one entry in our table, then the following future actions
|
||||
would be queued:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
// delete entry 1
|
||||
browser().navigateTo('/entries');
|
||||
element('table tbody').query(function (tbody, done) { ... });
|
||||
element('table tbody a');
|
||||
element('.btn-danger').click();
|
||||
</pre>
|
||||
```
|
||||
|
||||
For two entries, ngScenario would have to work on the following queue:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
// delete entry 1
|
||||
browser().navigateTo('/entries');
|
||||
element('table tbody').query(function (tbody, done) { ... });
|
||||
@@ -306,7 +306,7 @@ element('.btn-danger').click();
|
||||
element('table tbody').query(function (tbody, done) { ... });
|
||||
element('table tbody a');
|
||||
element('.btn-danger').click();
|
||||
</pre>
|
||||
```
|
||||
|
||||
# Caveats
|
||||
|
||||
|
||||
@@ -100,25 +100,28 @@ To configure the `$location` service, retrieve the
|
||||
default: `""`
|
||||
|
||||
### Example configuration
|
||||
<pre>
|
||||
```js
|
||||
$locationProvider.html5Mode(true).hashPrefix('!');
|
||||
</pre>
|
||||
```
|
||||
|
||||
## Getter and setter methods
|
||||
|
||||
`$location` service provides getter methods for read-only parts of the URL (absUrl, protocol, host,
|
||||
port) and getter / setter methods for url, path, search, hash:
|
||||
<pre>
|
||||
```js
|
||||
// get the current path
|
||||
$location.path();
|
||||
|
||||
// change the path
|
||||
$location.path('/newValue')
|
||||
</pre>
|
||||
```
|
||||
|
||||
All of the setter methods return the same `$location` object to allow chaining. For example, to
|
||||
change multiple segments in one go, chain setters like this:
|
||||
<pre>$location.path('/newValue').search({key: value});</pre>
|
||||
|
||||
```js
|
||||
$location.path('/newValue').search({key: value});
|
||||
```
|
||||
|
||||
## Replace method
|
||||
|
||||
@@ -127,11 +130,12 @@ time the $location service is synced with the browser, the last history record s
|
||||
instead of creating a new one. This is useful when you want to implement redirection, which would
|
||||
otherwise break the back button (navigating back would retrigger the redirection). To change the
|
||||
current URL without creating a new browser history record you can call:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
$location.path('/someNewPath');
|
||||
$location.replace();
|
||||
// or you can chain these as: $location.path('/someNewPath').replace();
|
||||
</pre>
|
||||
```
|
||||
|
||||
Note that the setters don't update `window.location` immediately. Instead, the `$location` service is
|
||||
aware of the {@link api/ng.$rootScope.Scope scope} life-cycle and coalesces multiple `$location`
|
||||
@@ -209,7 +213,7 @@ In this mode, `$location` uses Hashbang URLs in all browsers.
|
||||
|
||||
### Example
|
||||
|
||||
<pre>
|
||||
```js
|
||||
it('should show example', inject(
|
||||
function($locationProvider) {
|
||||
$locationProvider.html5Mode(false);
|
||||
@@ -231,13 +235,16 @@ it('should show example', inject(
|
||||
$location.absUrl() == 'http://example.com/base/index.html#!/new?x=y'
|
||||
}
|
||||
));
|
||||
</pre>
|
||||
```
|
||||
|
||||
### Crawling your app
|
||||
|
||||
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
|
||||
your document:
|
||||
<pre><meta name="fragment" content="!" /></pre>
|
||||
|
||||
```html
|
||||
<meta name="fragment" content="!" />
|
||||
```
|
||||
|
||||
This will cause crawler bot to request links with `_escaped_fragment_` param so that your server
|
||||
can recognize the crawler and serve a HTML snapshots. For more information about this technique,
|
||||
@@ -258,7 +265,7 @@ having to worry about whether the browser displaying your app supports the histo
|
||||
|
||||
### Example
|
||||
|
||||
<pre>
|
||||
```js
|
||||
it('should show example', inject(
|
||||
function($locationProvider) {
|
||||
$locationProvider.html5Mode(true);
|
||||
@@ -293,7 +300,7 @@ it('should show example', inject(
|
||||
$location.absUrl() == 'http://example.com/#!/foo/bar?x=y'
|
||||
}
|
||||
));
|
||||
</pre>
|
||||
```
|
||||
|
||||
### Fallback for legacy browsers
|
||||
|
||||
@@ -341,7 +348,10 @@ to entry point of your application (e.g. index.html)
|
||||
|
||||
If you want your AJAX application to be indexed by web crawlers, you will need to add the following
|
||||
meta tag to the HEAD section of your document:
|
||||
<pre><meta name="fragment" content="!" /></pre>
|
||||
|
||||
```html
|
||||
<meta name="fragment" content="!" />
|
||||
```
|
||||
|
||||
This statement causes a crawler to request links with an empty `_escaped_fragment_` parameter so that
|
||||
your server can recognize the crawler and serve it HTML snapshots. For more information about this
|
||||
@@ -525,7 +535,7 @@ hashPrefix.
|
||||
When using `$location` service during testing, you are outside of the angular's {@link
|
||||
api/ng.$rootScope.Scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
describe('serviceUnderTest', function() {
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.factory('serviceUnderTest', function($location){
|
||||
@@ -541,7 +551,7 @@ describe('serviceUnderTest', function() {
|
||||
|
||||
}));
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
# Migrating from earlier AngularJS releases
|
||||
|
||||
@@ -22,17 +22,19 @@ by using the {@link api/AUTO.$provide $provide} service in the module configurat
|
||||
function. The following pseudo-code shows both approaches:
|
||||
|
||||
Using the angular.Module api:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var myModule = angular.module('myModule', []);
|
||||
myModule.factory('serviceId', function() {
|
||||
var shinyNewServiceInstance;
|
||||
//factory function body that constructs shinyNewServiceInstance
|
||||
return shinyNewServiceInstance;
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
Using the $provide service:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
angular.module('myModule', [], function($provide) {
|
||||
$provide.factory('serviceId', function() {
|
||||
var shinyNewServiceInstance;
|
||||
@@ -40,7 +42,7 @@ angular.module('myModule', [], function($provide) {
|
||||
return shinyNewServiceInstance;
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
Note that you are not registering a service instance, but rather a factory function that will
|
||||
create this instance when called.
|
||||
@@ -58,7 +60,7 @@ Following is an example of a very simple service. This service depends on the `$
|
||||
stores all notifications; after the third one, the service displays all of the notifications by
|
||||
window alert.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
angular.module('myModule', [], function($provide) {
|
||||
$provide.factory('notify', ['$window', function(win) {
|
||||
var msgs = [];
|
||||
@@ -71,7 +73,7 @@ angular.module('myModule', [], function($provide) {
|
||||
};
|
||||
}]);
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
# Instantiating Angular Services
|
||||
|
||||
@@ -13,7 +13,7 @@ IDs matters: the order of the services in the array will be used when calling th
|
||||
with injected parameters. The names of parameters in factory function don't matter, but by
|
||||
convention they match the service IDs, which has added benefits discussed below.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function myController($loc, $log) {
|
||||
this.firstMethod = function() {
|
||||
// use $location service
|
||||
@@ -26,7 +26,7 @@ function myController($loc, $log) {
|
||||
}
|
||||
// which services to inject ?
|
||||
myController.$inject = ['$location', '$log'];
|
||||
</pre>
|
||||
```
|
||||
|
||||
<doc:example module="MyServiceModule">
|
||||
<doc:source>
|
||||
|
||||
@@ -12,37 +12,37 @@ dropped (see "Inferring `$inject`" but note that that is currently an experiment
|
||||
|
||||
Using the array notation:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function myModuleCfgFn($provide) {
|
||||
$provide.factory('myService', ['dep1', 'dep2', function(dep1, dep2) {}]);
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
Using the $inject property:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function myModuleCfgFn($provide) {
|
||||
var myServiceFactory = function(dep1, dep2) {};
|
||||
myServiceFactory.$inject = ['dep1', 'dep2'];
|
||||
$provide.factory('myService', myServiceFactory);
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
Using DI inference (incompatible with minifiers):
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function myModuleCfgFn($provide) {
|
||||
$provide.factory('myService', function(dep1, dep2) {});
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
Here is an example of two services, one of which depends on the other and both
|
||||
of which depend on other services that are provided by the Angular framework:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
/**
|
||||
* batchLog service allows for messages to be queued in memory and flushed
|
||||
* to the console.log every 50 seconds.
|
||||
@@ -83,7 +83,7 @@ of which depend on other services that are provided by the Angular framework:
|
||||
|
||||
// get the main service to kick off the application
|
||||
angular.injector([batchLogModule]).get('routeTemplateMonitor');
|
||||
</pre>
|
||||
```
|
||||
|
||||
Things to notice in this example:
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ The following is a unit test for the 'notify' service in the 'Dependencies' exam
|
||||
dev_guide.services.creating_services Creating Angular Services}. The unit test example uses Jasmine
|
||||
spy (mock) instead of a real browser alert.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
var mock, notify;
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -47,7 +47,7 @@ it('should clear messages after alert', function() {
|
||||
expect(mock.alert.callCount).toEqual(2);
|
||||
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
@@ -52,7 +52,7 @@ While there is nothing wrong with the `new` operator fundamentally, a problem ar
|
||||
on a constructor. This permanently binds the call site to the type. For example, lets say that we try to
|
||||
instantiate an `XHR` that will retrieve data from the server.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function MyClass() {
|
||||
this.doWork = function() {
|
||||
var xhr = new XHR();
|
||||
@@ -61,7 +61,7 @@ function MyClass() {
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would
|
||||
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
|
||||
@@ -69,20 +69,21 @@ permanently bound to the actual XHR and there is no way to replace it. Yes, we
|
||||
patch, but that is a bad idea for many reasons which are outside the scope of this document.
|
||||
|
||||
Here's an example of how the class above becomes hard to test when resorting to monkey patching:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var oldXHR = XHR;
|
||||
XHR = function MockXHR() {};
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that MockXHR got called with the right arguments
|
||||
XHR = oldXHR; // if you forget this bad things will happen
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
### Global look-up:
|
||||
Another way to approach the problem is to look for the service in a well-known location.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function MyClass() {
|
||||
this.doWork = function() {
|
||||
global.xhr({
|
||||
@@ -92,7 +93,7 @@ function MyClass() {
|
||||
})
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
While no new dependency instance is created, it is fundamentally the same as `new` in
|
||||
that no way exists to intercept the call to `global.xhr` for testing purposes, other then
|
||||
@@ -101,14 +102,15 @@ order to replace it with call to a mock method. For further explanation of why t
|
||||
State & Singletons](http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/)
|
||||
|
||||
The class above is hard to test since we have to change the global state:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var oldXHR = global.xhr;
|
||||
global.xhr = function mockXHR() {};
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
global.xhr = oldXHR; // if you forget this bad things will happen
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
### Service Registry:
|
||||
@@ -116,7 +118,7 @@ global.xhr = oldXHR; // if you forget this bad things will happen
|
||||
It may seem that this can be solved by having a registry of all the services and then
|
||||
having the tests replace the services as needed.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function MyClass() {
|
||||
var serviceRegistry = ????;
|
||||
this.doWork = function() {
|
||||
@@ -127,7 +129,7 @@ function MyClass() {
|
||||
complete:function(response){ ... }
|
||||
})
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
However, where does the serviceRegistry come from? If it is:
|
||||
* `new`-ed up, the test has no chance to reset the services for testing.
|
||||
@@ -135,20 +137,21 @@ However, where does the serviceRegistry come from? If it is:
|
||||
only one global variable exists to be reset).
|
||||
|
||||
The class above is hard to test since we have to change the global state:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var oldServiceLocator = global.serviceLocator;
|
||||
global.serviceLocator.set('xhr', function mockXHR() {});
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
global.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
### Passing in Dependencies:
|
||||
Last, the dependency can be passed in.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function MyClass(xhr) {
|
||||
this.doWork = function() {
|
||||
xhr({
|
||||
@@ -157,7 +160,7 @@ function MyClass(xhr) {
|
||||
complete:function(response){ ... }
|
||||
})
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
This is the preferred method since the code makes no assumptions about the origin of `xhr` and cares
|
||||
instead about whoever created the class responsible for passing it in. Since the creator of the
|
||||
@@ -165,12 +168,13 @@ class should be different code than the user of the class, it separates the resp
|
||||
creation from the logic. This is dependency-injection in a nutshell.
|
||||
|
||||
The class above is testable, since in the test we can write:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
function xhrMock(args) {...}
|
||||
var myClass = new MyClass(xhrMock);
|
||||
myClass.doWork();
|
||||
// assert that xhrMock got called with the right arguments
|
||||
</pre>
|
||||
```
|
||||
|
||||
Notice that no global variables were harmed in the writing of this test.
|
||||
|
||||
@@ -182,7 +186,7 @@ What makes each application unique is its logic, and the logic is what we would
|
||||
for your application contains DOM manipulation, it will be hard to test. See the example
|
||||
below:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function PasswordCtrl() {
|
||||
// get references to DOM elements
|
||||
var msg = $('.ex1 span');
|
||||
@@ -205,12 +209,12 @@ function PasswordCtrl() {
|
||||
.text(strength);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
The code above is problematic from a testability point of view since it requires your test to have the right kind
|
||||
of DOM present when the code executes. The test would look like this:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
var input = $('<input type="text"/>');
|
||||
var span = $('<span>');
|
||||
$('body').html('<div class="ex1">')
|
||||
@@ -222,12 +226,12 @@ input.val('abc');
|
||||
pc.grade();
|
||||
expect(span.text()).toEqual('weak');
|
||||
$('body').empty();
|
||||
</pre>
|
||||
```
|
||||
|
||||
In angular the controllers are strictly separated from the DOM manipulation logic and this results in
|
||||
a much easier testability story as the following example shows:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function PasswordCtrl($scope) {
|
||||
$scope.password = '';
|
||||
$scope.grade = function() {
|
||||
@@ -241,17 +245,17 @@ function PasswordCtrl($scope) {
|
||||
}
|
||||
};
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
and the test is straight forward:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
var $scope = {};
|
||||
var pc = $controller('PasswordCtrl', { $scope: $scope });
|
||||
$scope.password = 'abc';
|
||||
$scope.grade();
|
||||
expect($scope.strength).toEqual('weak');
|
||||
</pre>
|
||||
```
|
||||
|
||||
Notice that the test is not only much shorter, it is also easier to follow what is happening. We say
|
||||
that such a test tells a story, rather then asserting random bits which don't seem to be related.
|
||||
@@ -261,7 +265,7 @@ that such a test tells a story, rather then asserting random bits which don't se
|
||||
format. They are important because they remove the formatting responsibility from the application
|
||||
logic, further simplifying the application logic.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
myModule.filter('length', function() {
|
||||
return function(text){
|
||||
return (''+(text||'')).length;
|
||||
@@ -271,7 +275,7 @@ myModule.filter('length', function() {
|
||||
var length = $filter('length');
|
||||
expect(length(null)).toEqual(0);
|
||||
expect(length('abc')).toEqual(3);
|
||||
</pre>
|
||||
```
|
||||
|
||||
## Directives
|
||||
Directives in angular are responsible for encapsulating complex functionality within custom HTML tags,
|
||||
@@ -282,13 +286,13 @@ you create with directives may be used throughout your application and in many d
|
||||
|
||||
Let's start with an angular app with no dependencies.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
var app = angular.module('myApp', []);
|
||||
</pre>
|
||||
```
|
||||
|
||||
Now we can add a directive to our app.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
app.directive('aGreatEye', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -296,13 +300,13 @@ app.directive('aGreatEye', function () {
|
||||
template: '<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>'
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
This directive is used as a tag `<a-great-eye></a-great-eye>`. It replaces the entire tag with the
|
||||
template `<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>`. Now we are going to write a jasmine unit test to
|
||||
verify this functionality. Note that the expression `{{1 + 1}}` times will also be evaluated in the rendered content.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
describe('Unit testing great quotes', function() {
|
||||
var $compile;
|
||||
var $rootScope;
|
||||
@@ -327,7 +331,7 @@ describe('Unit testing great quotes', function() {
|
||||
expect(element.html()).toContain("lidless, wreathed in flame, 2 times");
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
We inject the $compile service and $rootScope before each jasmine test. The $compile service is used
|
||||
to render the aGreatEye directive. After rendering the directive we ensure that the directive has
|
||||
|
||||
@@ -31,7 +31,7 @@ for test isolation.
|
||||
The third option is the most viable, since it removes the responsibility of locating the
|
||||
dependency from the component. The dependency is simply handed to the component.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function SomeClass(greeter) {
|
||||
this.greeter = greeter;
|
||||
}
|
||||
@@ -39,7 +39,7 @@ dependency from the component. The dependency is simply handed to the component.
|
||||
SomeClass.prototype.doSomething = function(name) {
|
||||
this.greeter.greet(name);
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
In the above example `SomeClass` is not concerned with locating the `greeter` dependency, it
|
||||
is simply handed the `greeter` at runtime.
|
||||
@@ -55,7 +55,7 @@ construction and lookup of dependencies.
|
||||
|
||||
Here is an example of using the injector service:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
// Provide the wiring information in a module
|
||||
angular.module('myModule', []).
|
||||
|
||||
@@ -77,19 +77,20 @@ Here is an example of using the injector service:
|
||||
|
||||
// Request any dependency from the injector
|
||||
var greeter = injector.get('greeter');
|
||||
</pre>
|
||||
```
|
||||
|
||||
Asking for dependencies solves the issue of hard coding, but it also means that the injector needs
|
||||
to be passed throughout the application. Passing the injector breaks the [Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). To remedy this, we turn the
|
||||
dependency lookup responsibility to the injector by declaring the dependencies as in this example:
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<!-- Given this HTML -->
|
||||
<div ng-controller="MyController">
|
||||
<button ng-click="sayHello()">Hello</button>
|
||||
</div>
|
||||
</pre>
|
||||
<pre>
|
||||
```
|
||||
|
||||
```js
|
||||
// And this controller definition
|
||||
function MyController($scope, greeter) {
|
||||
$scope.sayHello = function() {
|
||||
@@ -99,7 +100,7 @@ dependency lookup responsibility to the injector by declaring the dependencies a
|
||||
|
||||
// The 'ng-controller' directive does this behind the scenes
|
||||
injector.instantiate(MyController);
|
||||
</pre>
|
||||
```
|
||||
|
||||
Notice that by having the `ng-controller` instantiate the class, it can satisfy all of the
|
||||
dependencies of `MyController` without the controller ever knowing about the injector. This is
|
||||
@@ -121,11 +122,11 @@ information. These can be used interchangeably as you see fit and are equivalent
|
||||
The simplest way to get hold of the dependencies, is to assume that the function parameter names
|
||||
are the names of the dependencies.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
function MyController($scope, greeter) {
|
||||
...
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
Given a function the injector can infer the names of the service to inject by examining the
|
||||
function declaration and extracting the parameter names. In the above example `$scope`, and
|
||||
@@ -140,12 +141,12 @@ To allow the minifers to rename the function parameters and still be able to inj
|
||||
the function needs to be annotated with the `$inject` property. The `$inject` property is an array
|
||||
of service names to inject.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
var MyController = function(renamed$scope, renamedGreeter) {
|
||||
...
|
||||
}
|
||||
MyController['$inject'] = ['$scope', 'greeter'];
|
||||
</pre>
|
||||
```
|
||||
|
||||
In this scenario the ordering of the values in the '$inject' array must match the ordering of the arguments to inject.
|
||||
Using above code snippet as an example, '$scope' will be injected into 'renamed$scope' and 'greeter' into 'renamedGreeter'.
|
||||
@@ -162,15 +163,15 @@ directives.
|
||||
|
||||
For example:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
someModule.factory('greeter', function($window) {
|
||||
...
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
Results in code bloat due to needing a temporary variable:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
var greeterFactory = function(renamed$window) {
|
||||
...
|
||||
};
|
||||
@@ -178,15 +179,15 @@ Results in code bloat due to needing a temporary variable:
|
||||
greeterFactory.$inject = ['$window'];
|
||||
|
||||
someModule.factory('greeter', greeterFactory);
|
||||
</pre>
|
||||
```
|
||||
|
||||
For this reason the third annotation style is provided as well.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
someModule.factory('greeter', ['$window', function(renamed$window) {
|
||||
...
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
Keep in mind that all of the annotation styles are equivalent and can be used anywhere in Angular
|
||||
where injection is supported.
|
||||
@@ -200,7 +201,7 @@ DI is pervasive throughout Angular. It is typically used in controllers and fact
|
||||
Controllers are classes which are responsible for application behavior. The recommended way of
|
||||
declaring controllers is using the array notation:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
|
||||
...
|
||||
$scope.aMethod = function() {
|
||||
@@ -208,7 +209,7 @@ declaring controllers is using the array notation:
|
||||
}
|
||||
...
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
This avoids the creation of global functions for controllers and also protects against minification.
|
||||
|
||||
@@ -219,7 +220,7 @@ Factory methods are responsible for creating most objects in Angular. Examples a
|
||||
services, and filters. The factory methods are registered with the module, and the recommended way
|
||||
of declaring factories is:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
angular.module('myModule', []).
|
||||
config(['depProvider', function(depProvider){
|
||||
...
|
||||
@@ -236,4 +237,4 @@ of declaring factories is:
|
||||
run(['depService', function(depService) {
|
||||
...
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
@@ -66,7 +66,7 @@ starts, Angular is automatically pre-configured with localization rules for the
|
||||
You can also include the locale specific js file in the index.html page. For example, if one client
|
||||
requires German locale, you would serve index_de-de.html which will look something like this:
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<html ng-app>
|
||||
<head>
|
||||
….
|
||||
@@ -75,7 +75,7 @@ requires German locale, you would serve index_de-de.html which will look somethi
|
||||
….
|
||||
</head>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
**Comparison of the two approaches**
|
||||
Both approaches described above requires you to prepare different index.html pages or js files for
|
||||
|
||||
@@ -26,7 +26,8 @@ To make your Angular application work on IE please make sure that:
|
||||
1. You polyfill JSON.stringify for IE7 and below. You can use
|
||||
[JSON2](https://github.com/douglascrockford/JSON-js) or
|
||||
[JSON3](http://bestiejs.github.com/json3/) polyfills for this.
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
@@ -38,21 +39,23 @@ To make your Angular application work on IE please make sure that:
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
2. add `id="ng-app"` to the root element in conjunction with `ng-app` attribute
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
...
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
3. you **do not** use custom element tags such as `<ng:view>` (use the attribute version
|
||||
`<div ng-view>` instead), or
|
||||
|
||||
4. if you **do use** custom element tags, then you must take these steps to make IE 8 and below happy:
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
<head>
|
||||
@@ -73,7 +76,7 @@ To make your Angular application work on IE please make sure that:
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
The **important** parts are:
|
||||
|
||||
@@ -112,37 +115,37 @@ attribute names. So this requires no special handling in IE: `<div my-tag your:t
|
||||
Suppose you have HTML with unknown tag `mytag` (this could also be `my:tag` or `my-tag` with same
|
||||
result):
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<mytag>some text</mytag>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
It should parse into the following DOM:
|
||||
|
||||
<pre>
|
||||
```
|
||||
#document
|
||||
+- HTML
|
||||
+- BODY
|
||||
+- mytag
|
||||
+- #text: some text
|
||||
</pre>
|
||||
```
|
||||
|
||||
The expected behavior is that the `BODY` element has a child element `mytag`, which in turn has
|
||||
the text `some text`.
|
||||
|
||||
But this is not what IE does (if the above fixes are not included):
|
||||
|
||||
<pre>
|
||||
```
|
||||
#document
|
||||
+- HTML
|
||||
+- BODY
|
||||
+- mytag
|
||||
+- #text: some text
|
||||
+- /mytag
|
||||
</pre>
|
||||
```
|
||||
|
||||
In IE, the behavior is that the `BODY` element has three children:
|
||||
|
||||
@@ -162,7 +165,7 @@ In IE, the behavior is that the `BODY` element has three children:
|
||||
To make CSS selectors work with custom elements, the custom element name must be pre-created with
|
||||
`document.createElement('my-tag')` regardless of XML namespace.
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<html xmlns:ng="needed for ng: namespace">
|
||||
<head>
|
||||
<!--[if lte IE 8]>
|
||||
@@ -192,7 +195,7 @@ To make CSS selectors work with custom elements, the custom element name must be
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ of blocks:
|
||||
application. Only instances and constants can be injected into run blocks. This is to prevent
|
||||
further system configuration during application run time.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
angular.module('myModule', []).
|
||||
config(function(injectables) { // provider-injector
|
||||
// This is an example of config block.
|
||||
@@ -140,14 +140,14 @@ angular.module('myModule', []).
|
||||
// You can only inject instances (not Providers)
|
||||
// into the run blocks
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
## Configuration Blocks
|
||||
|
||||
There are some convenience methods on the module which are equivalent to the config block. For
|
||||
example:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
angular.module('myModule', []).
|
||||
value('a', 123).
|
||||
factory('a', function() { return 123; }).
|
||||
@@ -163,7 +163,7 @@ angular.module('myModule', []).
|
||||
$compileProvider.directive('directiveName', ...);
|
||||
$filterProvider.register('filterName', ...);
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
The configuration blocks get applied in the order in which they are registered. The only exception
|
||||
to it are constant definitions, which are placed at the beginning of all configuration blocks.
|
||||
@@ -196,7 +196,7 @@ and thus script loaders can take advantage of this property and parallelize the
|
||||
Beware that using `angular.module('myModule', [])` will create the module `myModule` and overwrite any
|
||||
existing module named `myModule`. Use `angular.module('myModule')` to retrieve an existing module.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
var myModule = angular.module('myModule', []);
|
||||
|
||||
// add some directives and services
|
||||
@@ -208,7 +208,7 @@ existing module named `myModule`. Use `angular.module('myModule')` to retrieve a
|
||||
|
||||
// throws an error because myOtherModule has yet to be defined
|
||||
var myModule = angular.module('myOtherModule');
|
||||
</pre>
|
||||
```
|
||||
|
||||
# Unit Testing
|
||||
|
||||
@@ -219,7 +219,8 @@ injector, which means that the modules are loaded multiple times per VM. Properl
|
||||
modules can help with unit testing, as in this example:
|
||||
|
||||
In all of these examples we are going to assume this module definition:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
angular.module('greetMod', []).
|
||||
|
||||
factory('alert', function($window) {
|
||||
@@ -235,10 +236,11 @@ In all of these examples we are going to assume this module definition:
|
||||
alert(salutation + ' ' + name + '!');
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
Let's write some tests:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
describe('myApp', function() {
|
||||
// load the relevant application modules then load a special
|
||||
// test module which overrides the $window with a mock version,
|
||||
@@ -272,4 +274,4 @@ describe('myApp', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
@@ -88,7 +88,7 @@ scope is the single source-of-truth for all things view related.
|
||||
From a testability point of view, the separation of the controller and the view is desirable, because it allows us
|
||||
to test the behavior without being distracted by the rendering details.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
it('should say hello', function() {
|
||||
var scopeMock = {};
|
||||
var cntl = new MyController(scopeMock);
|
||||
@@ -101,7 +101,7 @@ to test the behavior without being distracted by the rendering details.
|
||||
scopeMock.sayHello();
|
||||
expect(scopeMock.greeting).toEqual('Hello angular!');
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
## Scope Hierarchies
|
||||
|
||||
@@ -24,7 +24,7 @@ The following code snippet shows a simple Angular template made up of standard H
|
||||
Angular {@link guide/directive directives} and curly-brace bindings
|
||||
with {@link expression expressions}:
|
||||
|
||||
<pre>
|
||||
```html
|
||||
<html ng-app>
|
||||
<!-- Body tag augmented with ngController directive -->
|
||||
<body ng-controller="MyController">
|
||||
@@ -36,7 +36,7 @@ with {@link expression expressions}:
|
||||
<script src="angular.js">
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
In a simple single-page app, the template consists of HTML, CSS, and angular directives contained
|
||||
in just one HTML file (usually `index.html`). In a more complex app, you can display multiple views
|
||||
|
||||
@@ -77,7 +77,8 @@ The HTML page that displays "Nothing here yet!" was constructed with the HTML co
|
||||
The code contains some key Angular elements that we will need as we progress.
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en" ng-app>
|
||||
<head>
|
||||
@@ -93,7 +94,7 @@ __`app/index.html`:__
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ The page now contains a list with information about two phones.
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-0...step-1):
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<ul>
|
||||
<li>
|
||||
<span>Nexus S</span>
|
||||
@@ -36,7 +37,7 @@ __`app/index.html`:__
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
@@ -32,7 +32,8 @@ view.
|
||||
The view component is constructed by Angular from this template:
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<html ng-app="phonecatApp">
|
||||
<head>
|
||||
...
|
||||
@@ -50,7 +51,7 @@ __`app/index.html`:__
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
We replaced the hard-coded phone list with the
|
||||
{@link api/ng.directive:ngRepeat ngRepeat directive} and two
|
||||
@@ -77,7 +78,8 @@ the `PhoneListCtrl` __controller__. The __controller__ is simply a constructor f
|
||||
`$scope` parameter:
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
|
||||
var phonecatApp = angular.module('phonecatApp', []);
|
||||
|
||||
@@ -92,7 +94,7 @@ phonecatApp.controller('PhoneListCtrl', function ($scope) {
|
||||
];
|
||||
});
|
||||
|
||||
</pre>
|
||||
```
|
||||
|
||||
Here we declared a controller called `PhoneListCtrl` and registered it in an AngularJS
|
||||
module, `phonecatApp`. Notice that our `ng-app` directive (on the `<html>` tag) now specifies the `phonecatApp`
|
||||
@@ -130,7 +132,8 @@ developed. If our controller is available on the global namespace then we can si
|
||||
with a mock `scope` object. Take a look at the following unit test for our controller:
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
@@ -143,7 +146,7 @@ describe('PhoneCat controllers', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
The test instantiates `PhoneListCtrl` and verifies that the phones array property on the scope
|
||||
contains three records. This example demonstrates how easy it is to create a unit test for code in
|
||||
@@ -157,7 +160,8 @@ service, `$controller`, which will retrieve your controller by name. Here is th
|
||||
`$controller`:
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
beforeEach(module('phonecatApp'));
|
||||
|
||||
@@ -171,7 +175,7 @@ describe('PhoneCat controllers', function() {
|
||||
}));
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
Don't forget that we need to load up the `phonecatApp` module into the test so that the controller
|
||||
is available to be injected.
|
||||
|
||||
@@ -30,7 +30,8 @@ We made no changes to the controller.
|
||||
## Template
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
@@ -52,7 +53,7 @@ __`app/index.html`:__
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</pre>
|
||||
```
|
||||
|
||||
We added a standard HTML `<input>` tag and used Angular's
|
||||
{@link api/ng.filter:filter filter} function to process the input for the
|
||||
@@ -88,7 +89,8 @@ The search feature was fully implemented via templates and data-binding, so we'l
|
||||
end-to-end test, to verify that the feature works.
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
describe('PhoneCat App', function() {
|
||||
|
||||
describe('Phone list view', function() {
|
||||
@@ -109,7 +111,7 @@ describe('PhoneCat App', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
Even though the syntax of this test looks very much like our controller unit test written with
|
||||
Jasmine, the end-to-end test uses APIs of {@link guide/dev_guide.e2e-testing Angular's end-to-end
|
||||
@@ -168,7 +170,7 @@ ngBindTemplate} directives, which are invisible to the user while the page is lo
|
||||
|
||||
* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
it('should display the current filter value within an element with id "status"',
|
||||
function() {
|
||||
expect(element('#status').text()).toMatch(/Current filter: \s*$/);
|
||||
@@ -180,7 +182,7 @@ ngBindTemplate} directives, which are invisible to the user while the page is lo
|
||||
//alternative version of the last assertion that tests just the value of the binding
|
||||
using('#status').expect(binding('query')).toBe('nexus');
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test
|
||||
pass, edit the `index.html` template to add a `div` or `p` element with `id` `"status"` and content
|
||||
|
||||
@@ -24,7 +24,8 @@ The most important differences between Steps 3 and 4 are listed below. You can s
|
||||
## Template
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
Search: <input ng-model="query">
|
||||
Sort by:
|
||||
<select ng-model="orderProp">
|
||||
@@ -39,7 +40,7 @@ __`app/index.html`:__
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
```
|
||||
|
||||
We made the following changes to the `index.html` template:
|
||||
|
||||
@@ -65,7 +66,8 @@ necessary!
|
||||
## Controller
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var phonecatApp = angular.module('phonecatApp', []);
|
||||
|
||||
phonecatApp.controller('PhoneListCtrl', function ($scope) {
|
||||
@@ -83,7 +85,7 @@ phonecatApp.controller('PhoneListCtrl', function ($scope) {
|
||||
|
||||
$scope.orderProp = 'age';
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
* We modified the `phones` model - the array of phones - and added an `age` property to each phone
|
||||
record. This property is used to order phones by age.
|
||||
@@ -107,7 +109,8 @@ The changes we made should be verified with both a unit test and an end-to-end t
|
||||
the unit test first.
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
@@ -130,7 +133,7 @@ describe('PhoneCat controllers', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
The unit test now verifies that the default ordering property is set.
|
||||
@@ -146,7 +149,8 @@ You should now see the following output in the Karma tab:
|
||||
Let's turn our attention to the end-to-end test.
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
...
|
||||
it('should be possible to control phone order via the drop down select box',
|
||||
function() {
|
||||
@@ -164,7 +168,7 @@ __`test/e2e/scenarios.js`:__
|
||||
"Motorola XOOM\u2122 with Wi-Fi"]);
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
The end-to-end test verifies that the ordering mechanism of the select box is working correctly.
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ The `app/phones/phones.json` file in your project is a dataset that contains a l
|
||||
stored in the JSON format.
|
||||
|
||||
Following is a sample of the file:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"age": 13,
|
||||
@@ -36,7 +37,7 @@ Following is a sample of the file:
|
||||
},
|
||||
...
|
||||
]
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
## Controller
|
||||
@@ -52,7 +53,8 @@ and control) and loosely coupled (dependencies between components are not resolv
|
||||
components themselves, but by the DI subsystem).
|
||||
|
||||
__`app/js/controllers.js:`__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var phonecatApp = angular.module('phonecatApp', []);
|
||||
|
||||
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
|
||||
@@ -62,7 +64,7 @@ phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
|
||||
|
||||
$scope.orderProp = 'age';
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
`$http` makes an HTTP GET request to our web server, asking for `phone/phones.json` (the url is
|
||||
relative to our `index.html` file). The server responds by providing the data in the json file.
|
||||
@@ -115,31 +117,37 @@ There are two ways to overcome issues caused by minification:
|
||||
* You can create a `$inject` property on the controller function which holds an array of strings.
|
||||
Each string in the array is the name of the service to inject for the corresponding parameter.
|
||||
In the case of our example we would write:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
PhoneListCtrl.$inject = ['$scope', '$http'];
|
||||
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
|
||||
</pre>
|
||||
```
|
||||
|
||||
* Use the inline bracket notation which wraps the function to be injected into an array of strings
|
||||
(representing the dependency names) followed by the function to be injected:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
Both of these methods work with any function that can be injected by Angular, so it's up to your
|
||||
project's style guide to decide which one you use.
|
||||
|
||||
When using the second method, it is common to provide the constructor function inline as an
|
||||
anonymous function when registering the controller:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
From this point onward, we're going to use the inline method in the tutorial. With that in mind,
|
||||
let's add the annotations to our `PhoneListCtrl`:
|
||||
|
||||
__`app/js/controllers.js:`__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var phonecatApp = angular.module('phonecatApp', []);
|
||||
|
||||
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
|
||||
@@ -150,7 +158,7 @@ phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
|
||||
|
||||
$scope.orderProp = 'age';
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
@@ -162,7 +170,7 @@ constructor with some kind of fake `$http` implementation. However, the recommen
|
||||
is to create a controller in the test environment in the same way that angular does it in the
|
||||
production code behind the scenes, as follows:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
@@ -182,7 +190,7 @@ describe('PhoneCat controllers', function() {
|
||||
scope = $rootScope.$new();
|
||||
ctrl = $controller('PhoneListCtrl', {$scope: scope});
|
||||
}));
|
||||
</pre>
|
||||
```
|
||||
|
||||
Note: Because we loaded Jasmine and `angular-mocks.js` in our test environment, we got two helper
|
||||
methods {@link api/angular.mock.module module} and {@link api/angular.mock.inject inject} that we'll
|
||||
@@ -219,7 +227,7 @@ the `$httpBackend.flush` method.
|
||||
Now we will make assertions to verify that the `phones` model doesn't exist on `scope` before
|
||||
the response is received:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
it('should create "phones" model with 2 phones fetched from xhr', function() {
|
||||
expect(scope.phones).toBeUndefined();
|
||||
$httpBackend.flush();
|
||||
@@ -227,7 +235,7 @@ the response is received:
|
||||
expect(scope.phones).toEqual([{name: 'Nexus S'},
|
||||
{name: 'Motorola DROID'}]);
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the
|
||||
promise returned by the `$http` service to be resolved with the trained response.
|
||||
@@ -236,11 +244,11 @@ promise returned by the `$http` service to be resolved with the trained response
|
||||
|
||||
Finally, we verify that the default value of `orderProp` is set correctly:
|
||||
|
||||
<pre>
|
||||
```js
|
||||
it('should set the default value of orderProp model', function() {
|
||||
expect(scope.orderProp).toBe('age');
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
You should now see the following output in the Karma tab:
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ Note that the `phones.json` file contains unique ids and image urls for each of
|
||||
urls point to the `app/img/phones/` directory.
|
||||
|
||||
__`app/phones/phones.json`__ (sample snippet):
|
||||
<pre>
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
...
|
||||
@@ -36,13 +37,14 @@ __`app/phones/phones.json`__ (sample snippet):
|
||||
},
|
||||
...
|
||||
]
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
...
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
|
||||
@@ -52,7 +54,7 @@ __`app/index.html`:__
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
To dynamically generate links that will in the future lead to phone detail pages, we used the
|
||||
now-familiar double-curly brace binding in the `href` attribute values. In step 2, we added the
|
||||
@@ -70,7 +72,8 @@ Using the `ngSrc` directive prevents the browser from making an http request to
|
||||
## Test
|
||||
|
||||
__`test/e2e/scenarios.js`__:
|
||||
<pre>
|
||||
|
||||
```js
|
||||
...
|
||||
it('should render phone specific links', function() {
|
||||
input('query').enter('nexus');
|
||||
@@ -78,7 +81,7 @@ __`test/e2e/scenarios.js`__:
|
||||
expect(browser().location().url()).toBe('/phones/nexus-s');
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
We added a new end-to-end test to verify that the app is generating correct links to the phone
|
||||
views that we will implement in the upcoming steps.
|
||||
|
||||
@@ -71,7 +71,8 @@ both module systems can live side by side and fulfil their goals.
|
||||
## The App Module
|
||||
|
||||
__`app/js/app.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var phonecatApp = angular.module('phonecatApp', [
|
||||
'ngRoute',
|
||||
'phonecatControllers'
|
||||
@@ -92,7 +93,7 @@ phonecatApp.config(['$routeProvider',
|
||||
redirectTo: '/phones'
|
||||
});
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
In order to configure our application with routes, we need to create a module for our application.
|
||||
We call this module `phonecatApp`. Notice the second argument passed to `angular.module`:
|
||||
@@ -133,17 +134,19 @@ the module name as the value of the {@link api/ng.directive:ngApp ngApp}
|
||||
directive:
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en" ng-app="phonecatApp">
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
## Controllers
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var phonecatControllers = angular.module('phonecatControllers', []);
|
||||
|
||||
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
|
||||
@@ -159,7 +162,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
|
||||
function($scope, $routeParams) {
|
||||
$scope.phoneId = $routeParams.phoneId;
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
Again, note that we created a new module called `phonecatControllers`. For small AngularJS applications,
|
||||
it's common to create just one module for all of your controllers if there are just a few. For larger apps,
|
||||
@@ -180,7 +183,8 @@ tag to your `index.html` file as shown below.
|
||||
</div>
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en" ng-app="phonecatApp">
|
||||
<head>
|
||||
@@ -196,14 +200,15 @@ __`app/index.html`:__
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
Note that we removed most of the code in the `index.html` template and replaced it with a single
|
||||
line containing a div with the `ng-view` attribute. The code that we removed was placed into the
|
||||
`phone-list.html` template:
|
||||
|
||||
__`app/partials/phone-list.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
@@ -231,7 +236,7 @@ __`app/partials/phone-list.html`:__
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</pre>
|
||||
```
|
||||
|
||||
<div style="display:none">
|
||||
TODO!
|
||||
@@ -241,9 +246,10 @@ TODO!
|
||||
We also added a placeholder template for the phone details view:
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
TBD: detail view for {{phoneId}}
|
||||
</pre>
|
||||
```
|
||||
|
||||
Note how we are using `phoneId` model defined in the `PhoneDetailCtrl` controller.
|
||||
|
||||
@@ -253,7 +259,7 @@ Note how we are using `phoneId` model defined in the `PhoneDetailCtrl` controlle
|
||||
To automatically verify that everything is wired properly, we wrote end-to-end tests that navigate
|
||||
to various URLs and verify that the correct view was rendered.
|
||||
|
||||
<pre>
|
||||
```js
|
||||
...
|
||||
it('should redirect index.html to index.html#/phones', function() {
|
||||
browser().navigateTo('app/index.html');
|
||||
@@ -272,7 +278,7 @@ to various URLs and verify that the correct view was rendered.
|
||||
expect(binding('phoneId')).toBe('nexus-s');
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
|
||||
|
||||
@@ -27,7 +27,8 @@ In addition to `phones.json`, the `app/phones/` directory also contains one json
|
||||
phone:
|
||||
|
||||
__`app/phones/nexus-s.json`:__ (sample snippet)
|
||||
<pre>
|
||||
|
||||
```js
|
||||
{
|
||||
"additionalFeatures": "Contour Display, Near Field Communications (NFC),...",
|
||||
"android": {
|
||||
@@ -46,7 +47,7 @@ __`app/phones/nexus-s.json`:__ (sample snippet)
|
||||
"ram": "512MB"
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
Each of these files describes various properties of the phone using the same data structure. We'll
|
||||
@@ -59,7 +60,8 @@ We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the jso
|
||||
the same way as the phone list controller.
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var phonecatControllers = angular.module('phonecatControllers',[]);
|
||||
|
||||
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
|
||||
@@ -68,7 +70,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$h
|
||||
$scope.phone = data;
|
||||
});
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
To construct the URL for the HTTP request, we use `$routeParams.phoneId` extracted from the current
|
||||
route by the `$route` service.
|
||||
@@ -82,7 +84,8 @@ our model into the view.
|
||||
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<img ng-src="{{phone.images[0]}}" class="phone">
|
||||
|
||||
<h1>{{phone.name}}</h1>
|
||||
@@ -109,7 +112,7 @@ __`app/partials/phone-detail.html`:__
|
||||
<dd>{{phone.additionalFeatures}}</dd>
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
```
|
||||
|
||||
<div style="display: none">
|
||||
TODO!
|
||||
@@ -122,7 +125,8 @@ We wrote a new unit test that is similar to the one we wrote for the `PhoneListC
|
||||
step 5.
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
...
|
||||
describe('PhoneDetailCtrl', function(){
|
||||
var scope, $httpBackend, ctrl;
|
||||
@@ -145,7 +149,7 @@ __`test/unit/controllersSpec.js`:__
|
||||
});
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
You should now see the following output in the Karma tab:
|
||||
|
||||
@@ -156,7 +160,8 @@ We also added a new end-to-end test that navigates to the Nexus S detail page an
|
||||
heading on the page is "Nexus S".
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
...
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
@@ -170,7 +175,7 @@ __`test/e2e/scenarios.js`:__
|
||||
});
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
|
||||
|
||||
@@ -26,13 +26,14 @@ In order to create a new filter, you are going to create a `phonecatFilters` mod
|
||||
your custom filter with this module:
|
||||
|
||||
__`app/js/filters.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
angular.module('phonecatFilters', []).filter('checkmark', function() {
|
||||
return function(input) {
|
||||
return input ? '\u2713' : '\u2718';
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
The name of our filter is "checkmark". The `input` evaluates to either `true` or `false`, and we
|
||||
return one of the two unicode characters we have chosen to represent true (`\u2713` -> ✓) or false (`\u2718` -> ✘).
|
||||
@@ -41,11 +42,12 @@ Now that our filter is ready, we need to register the `phonecatFilters` module a
|
||||
our main `phonecat` module.
|
||||
|
||||
__`app/js/app.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
...
|
||||
angular.module('phonecatApp', ['phonecatFilters']).
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
## Template
|
||||
@@ -54,12 +56,13 @@ Since the filter code lives in the `app/js/filters.js` file, we need to include
|
||||
layout template.
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
...
|
||||
<script src="js/controllers.js"></script>
|
||||
<script src="js/filters.js"></script>
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
The syntax for using filters in Angular templates is as follows:
|
||||
|
||||
@@ -70,7 +73,8 @@ Let's employ the filter in the phone details template:
|
||||
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
...
|
||||
<dl>
|
||||
<dt>Infrared</dt>
|
||||
@@ -79,7 +83,7 @@ __`app/partials/phone-detail.html`:__
|
||||
<dd>{{phone.connectivity.gps | checkmark}}</dd>
|
||||
</dl>
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
|
||||
## Test
|
||||
@@ -87,7 +91,8 @@ __`app/partials/phone-detail.html`:__
|
||||
Filters, like any other component, should be tested and these tests are very easy to write.
|
||||
|
||||
__`test/unit/filtersSpec.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
describe('filter', function() {
|
||||
|
||||
beforeEach(module('phonecatFilters'));
|
||||
@@ -101,7 +106,7 @@ describe('filter', function() {
|
||||
}));
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
We must call `beforeEach(module('phonecatFilters'))` before any of
|
||||
our filter tests execute. This call loads our `phonecatFilters` module into the injector
|
||||
|
||||
@@ -22,7 +22,8 @@ The most important changes are listed below. You can see the full diff on [GitHu
|
||||
## Controller
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
...
|
||||
var phonecatControllers = angular.module('phonecatControllers',[]);
|
||||
|
||||
@@ -37,7 +38,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$h
|
||||
$scope.mainImageUrl = imageUrl;
|
||||
}
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
In the `PhoneDetailCtrl` controller, we created the `mainImageUrl` model property and set its
|
||||
default value to the first phone image URL.
|
||||
@@ -48,7 +49,8 @@ We also created a `setImage` event handler function that will change the value o
|
||||
## Template
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<img ng-src="{{mainImageUrl}}" class="phone">
|
||||
|
||||
...
|
||||
@@ -59,7 +61,7 @@ __`app/partials/phone-detail.html`:__
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
We bound the `ngSrc` directive of the large image to the `mainImageUrl` property.
|
||||
|
||||
@@ -80,7 +82,8 @@ to the first phone image by default. The second test clicks on several thumbnail
|
||||
verifies that the main image changed appropriately.
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
...
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
@@ -100,7 +103,7 @@ __`test/e2e/scenarios.js`:__
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
|
||||
runner to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-10/test/e2e/runner.html).
|
||||
|
||||
@@ -25,17 +25,19 @@ template. Additionally, we also need to load the `angular-resource.js` file, whi
|
||||
`ngResource` module and in it the `$resource` service, that we'll soon use:
|
||||
|
||||
__`app/index.html`.__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
...
|
||||
<script src="js/services.js"></script>
|
||||
<script src="lib/angular/angular-resource.js"></script>
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
## Service
|
||||
|
||||
__`app/js/services.js`.__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var phonecatServices = angular.module('phonecatServices', ['ngResource']);
|
||||
|
||||
phonecatServices.factory('Phone', ['$resource',
|
||||
@@ -44,7 +46,7 @@ phonecatServices.factory('Phone', ['$resource',
|
||||
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
|
||||
});
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
We used the module API to register a custom service using a factory function. We passed in the name
|
||||
of the service - 'Phone' - and the factory function. The factory function is similar to a
|
||||
@@ -57,11 +59,12 @@ lines of code. This client can then be used in our application, instead of the l
|
||||
api/ng.$http $http} service.
|
||||
|
||||
__`app/js/app.js`.__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
...
|
||||
angular.module('phonecatApp', ['ngRoute', 'phonecatControllers','phonecatFilters', 'phonecatServices']).
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
We need to add the 'phonecatServices' module dependency to 'phonecatApp' module's requires array.
|
||||
|
||||
@@ -75,7 +78,8 @@ use than `$http` for interacting with data sources exposed as RESTful resources.
|
||||
now to understand what the code in our controllers is doing.
|
||||
|
||||
__`app/js/controllers.js`.__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
...
|
||||
|
||||
phonecatApp.controller('PhoneListCtrl', ['$scope', 'Phone', function($scope, Phone) {
|
||||
@@ -92,7 +96,7 @@ phonecatApp.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone', fu
|
||||
$scope.mainImageUrl = imageUrl;
|
||||
}
|
||||
}]);
|
||||
</pre>
|
||||
```
|
||||
|
||||
Notice how in `PhoneListCtrl` we replaced:
|
||||
|
||||
@@ -133,7 +137,8 @@ ignores methods.
|
||||
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
beforeEach(function(){
|
||||
@@ -204,7 +209,7 @@ describe('PhoneCat controllers', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
You should now see the following output in the Karma tab:
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ as the `angular-animate.js` file. The animation module, known as `ngAnimate`, is
|
||||
Here's what needs to changed in the index file:
|
||||
|
||||
__`app/index.html`.__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
...
|
||||
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
|
||||
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
@@ -55,7 +56,7 @@ __`app/index.html`.__
|
||||
<!-- for CSS Transitions and/or Keyframe Animations -->
|
||||
<link rel="stylesheet" href="css/animations.css">
|
||||
...
|
||||
</pre>
|
||||
```
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
|
||||
@@ -68,17 +69,19 @@ with `ngResource`.
|
||||
## Module & Animations
|
||||
|
||||
__`app/js/animations.js`.__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
angular.module('phonecatAnimations', ['ngAnimate']).
|
||||
// ...
|
||||
// this module will later be used to define animations
|
||||
// ...
|
||||
</pre>
|
||||
```
|
||||
|
||||
And now let's attach this module to our application module...
|
||||
|
||||
__`app/js/app.js`.__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
// ...
|
||||
angular.module('phonecat', [
|
||||
'ngRoute',
|
||||
@@ -89,7 +92,7 @@ angular.module('phonecat', [
|
||||
'phonecatServices',
|
||||
]).
|
||||
// ...
|
||||
</pre>
|
||||
```
|
||||
|
||||
Now, the phonecat module is animation aware. Let's make some animations!
|
||||
|
||||
@@ -100,7 +103,8 @@ We'll start off by adding CSS transition animations to our `ngRepeat` directive
|
||||
First let's add an extra CSS class to our repeated element so that we can hook into it with our CSS animation code.
|
||||
|
||||
__`app/partials/phone-list.html`.__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<!--
|
||||
Let's change the repeater HTML to include a new CSS class
|
||||
which we will later use for animations:
|
||||
@@ -114,14 +118,15 @@ __`app/partials/phone-list.html`.__
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</pre>
|
||||
```
|
||||
|
||||
Notice how we added the `phone-listing` CSS class? This is all we need in our HTML code to get animations working.
|
||||
|
||||
Now for the actual CSS transition animation code:
|
||||
|
||||
__`app/css/animations.css`__
|
||||
<pre>
|
||||
|
||||
```css
|
||||
.phone-listing.ng-enter,
|
||||
.phone-listing.ng-leave,
|
||||
.phone-listing.ng-move {
|
||||
@@ -155,7 +160,7 @@ __`app/css/animations.css`__
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
As you can see our `phone-listing` CSS class is combined together with the animation hooks that occur when items are
|
||||
inserted into and removed from the list:
|
||||
@@ -200,11 +205,12 @@ In order to do this, we'll have to make some small changes to the HTML code so t
|
||||
animations between view changes.
|
||||
|
||||
__`app/index.html`.__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<div class="view-container">
|
||||
<div ng-view class="view-frame"></div>
|
||||
</div>
|
||||
</pre>
|
||||
```
|
||||
|
||||
With this change, the `ng-view` directive is nested inside a parent element with
|
||||
a `view-container` CSS class. This class adds a `position: relative` style so that the positioning of the `ng-view`
|
||||
@@ -213,7 +219,8 @@ is relative to this parent as it animates transitions.
|
||||
With this in place, let's add the CSS for this transition animation to our `animations.css` file:
|
||||
|
||||
__`app/css/animations.css`.__
|
||||
<pre>
|
||||
|
||||
```css
|
||||
.view-container {
|
||||
position: relative;
|
||||
}
|
||||
@@ -242,34 +249,34 @@ __`app/css/animations.css`.__
|
||||
z-index:99;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@-moz-keyframes fade-in {
|
||||
@-moz-keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@-webkit-keyframes fade-in {
|
||||
@-webkit-keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
@keyframes fade-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
@-moz-keyframes fade-out {
|
||||
@-moz-keyframes fade-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
@-webkit-keyframes fade-out {
|
||||
@-webkit-keyframes fade-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
/* don't forget about the vendor-prefixes! */
|
||||
</pre>
|
||||
/* don't forget about the vendor-prefixes! */
|
||||
```
|
||||
|
||||
Nothing crazy here! Just a simple fade in and fade out effect between pages. The only out of the ordinary thing
|
||||
here is that we're using absolute positioning to position the next page (identified via `ng-enter`) on top of the
|
||||
@@ -306,7 +313,8 @@ profile image and the animation plays.
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first:
|
||||
|
||||
__`app/partials/phone-detail.html`.__
|
||||
<pre>
|
||||
|
||||
```html
|
||||
<!-- We're only changing the top of the file -->
|
||||
<div class="phone-images">
|
||||
<img ng-src="{{img}}"
|
||||
@@ -324,7 +332,7 @@ __`app/partials/phone-detail.html`.__
|
||||
<img ng-src="{{img}}" ng-mouseenter="setImage(img)">
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
```
|
||||
|
||||
Just like with the thumbnails, we're using a repeater to display **all** the profile images as a list, however we're
|
||||
not animating any repeat-related animations. Instead, we're keeping our eye on the ng-class directive since whenever
|
||||
@@ -340,7 +348,8 @@ You may be thinking that we're just going to create another CSS-enabled animatio
|
||||
Although we could do that, let's take the opportunity to learn how to create JavaScript-enabled animations with the `animation()` module method.
|
||||
|
||||
__`app/js/animations.js`.__
|
||||
<pre>
|
||||
|
||||
```js
|
||||
var phonecatAnimations = angular.module('phonecatAnimations', ['ngAnimate']);
|
||||
|
||||
phonecatAnimations.animation('.phone', function() {
|
||||
@@ -393,7 +402,7 @@ phonecatAnimations.animation('.phone', function() {
|
||||
removeClass: animateDown
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
```
|
||||
|
||||
Note that we're using [jQuery](http://jquery.com/) to implement the animation. jQuery
|
||||
isn't required to do JavaScript animations with AngularJS, but we're going to use it because writing
|
||||
|
||||
Reference in New Issue
Block a user