Compare commits

..

No commits in common. "master" and "v1.2.0" have entirely different histories.

216 changed files with 9033 additions and 13432 deletions

1
.gitignore vendored
View file

@ -17,4 +17,3 @@ angular.xcodeproj
libpeerconnection.log libpeerconnection.log
npm-debug.log npm-debug.log
/tmp/ /tmp/
/scripts/bower/bower-*

View file

@ -1,3 +0,0 @@
{
"disallowKeywords": ["with"]
}

View file

@ -1,22 +0,0 @@
// This is an incomplete TODO list of checks we want to start enforcing
//
// The goal is to enable these checks one by one by moving them to .jscs.json along with commits
// that correct the existing code base issues and make the new check pass.
{
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"disallowLeftStickedOperators": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
"disallowRightStickedOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
"requireRightStickedOperators": ["!"],
"requireLeftStickedOperators": [","],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"disallowKeywordsOnNewLine": ["else"],
"disallowTrailingWhitespace": true,
"requireLineFeedAtFileEnd": true,
"validateJSDoc": {
"checkParamNames": true,
"requireParamTypes": true
}
}

View file

@ -3,14 +3,11 @@ node_js:
- 0.10 - 0.10
env: env:
matrix:
- JOB=unit
- JOB=e2e
global: global:
- SAUCE_USERNAME=angular-ci - SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987 - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
- SAUCE_CONNECT_READY_FILE=/tmp/sauce-connect-ready
- LOGS_DIR=/tmp/angular-build/logs - LOGS_DIR=/tmp/angular-build/logs
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
before_script: before_script:
- mkdir -p $LOGS_DIR - mkdir -p $LOGS_DIR
@ -19,10 +16,11 @@ before_script:
- grunt bower - grunt bower
- grunt bower - grunt bower
- grunt package-without-bower - grunt package-without-bower
- ./scripts/travis/wait_for_browser_provider.sh - grunt ci-checks
- ./lib/sauce/sauce_connect_block.sh
script: script:
- ./scripts/travis/build.sh - ./travis_build.sh
after_script: after_script:
- ./scripts/travis/print_logs.sh - ./travis_print_logs.sh

View file

@ -1,773 +1,3 @@
<a name="1.2.11"></a>
# 1.2.11 cryptocurrency-hyperdeflation (2014-02-03)
## Bug Fixes
- **$compile:** retain CSS classes added in cloneAttachFn on asynchronous directives
([5ed721b9](https://github.com/angular/angular.js/commit/5ed721b9b5e95ae08450e1ae9d5202e7f3f79295),
[#5439](https://github.com/angular/angular.js/issues/5439), [#5617](https://github.com/angular/angular.js/issues/5617))
- **$http:** update httpBackend to use ActiveXObject on IE8 if necessary
([ef210e5e](https://github.com/angular/angular.js/commit/ef210e5e119db4f5bfc9d2428b19f9b335c4f976),
[#5677](https://github.com/angular/angular.js/issues/5677), [#5679](https://github.com/angular/angular.js/issues/5679))
- **$q:** make $q.reject support `finally` and `catch`
([074b0675](https://github.com/angular/angular.js/commit/074b0675a1f97dce07f520f1ae6198ed3c604000),
[#6048](https://github.com/angular/angular.js/issues/6048), [#6076](https://github.com/angular/angular.js/issues/6076))
- **filterFilter:** don't interpret dots in predicate object fields as paths
([339a1658](https://github.com/angular/angular.js/commit/339a1658cd9bfa5e322a01c45aa0a1df67e3a842),
[#6005](https://github.com/angular/angular.js/issues/6005), [#6009](https://github.com/angular/angular.js/issues/6009))
- **mocks:** refactor currentSpec to work w/ Jasmine 2
([95f0bf9b](https://github.com/angular/angular.js/commit/95f0bf9b526fda8964527c6d4aef1ad50a47f1f3),
[#5662](https://github.com/angular/angular.js/issues/5662))
- **ngResource:** don't append number to '$' in url param value when encoding URI
([ce1f1f97](https://github.com/angular/angular.js/commit/ce1f1f97f0ebf77941b2bdaf5e8352d33786524d),
[#6003](https://github.com/angular/angular.js/issues/6003), [#6004](https://github.com/angular/angular.js/issues/6004))
<a name="1.2.10"></a>
# 1.2.10 augmented-serendipity (2014-01-24)
## Bug Fixes
- **$parse:** do not use locals to resolve object properties
([f09b6aa5](https://github.com/angular/angular.js/commit/f09b6aa5b58c090e3b8f8811fb7735e38d4b7623),
[#5838](https://github.com/angular/angular.js/issues/5838), [#5862](https://github.com/angular/angular.js/issues/5862))
- **a:** don't call preventDefault on click when a SVGAElement has an xlink:href attribute
([e0209169](https://github.com/angular/angular.js/commit/e0209169bf1463465ad07484421620748a4d3908),
[#5896](https://github.com/angular/angular.js/issues/5896), [#5897](https://github.com/angular/angular.js/issues/5897))
- **input:** use Chromium's email validation regexp
([79e519fe](https://github.com/angular/angular.js/commit/79e519fedaec54390a8bdacfb1926bfce57a1eb6),
[#5899](https://github.com/angular/angular.js/issues/5899), [#5924](https://github.com/angular/angular.js/issues/5924))
- **ngRoute:** pipe preceding route param no longer masks ? or * operator
([fd6bac7d](https://github.com/angular/angular.js/commit/fd6bac7de56f728a89782dc80c78f7d5c21bbc65),
[#5920](https://github.com/angular/angular.js/issues/5920))
## Features
- **$animate:** provide support for a close callback
([ca6b7d0f](https://github.com/angular/angular.js/commit/ca6b7d0fa2e355ebd764230260758cee9a4ebe1e),
[#5685](https://github.com/angular/angular.js/issues/5685), [#5053](https://github.com/angular/angular.js/issues/5053), [#4993](https://github.com/angular/angular.js/issues/4993))
<a name="1.2.9"></a>
# 1.2.9 enchanted-articulacy (2014-01-15)
## Bug Fixes
- **$animate:**
- ensure the final closing timeout respects staggering animations
([ed53100a](https://github.com/angular/angular.js/commit/ed53100a0dbc9119d5dfc8b7248845d4f6989df2))
- prevent race conditions for class-based animations when animating on the same CSS class
([4aa9df7a](https://github.com/angular/angular.js/commit/4aa9df7a7ae533531dfae1e3eb9646245d6b5ff4),
[#5588](https://github.com/angular/angular.js/issues/5588))
- correctly detect and handle CSS transition changes during class addition and removal
([7d5d62da](https://github.com/angular/angular.js/commit/7d5d62dafe11620082c79da35958f8014eeb008c))
- avoid accidentally matching substrings when resolving the presence of className tokens
([524650a4](https://github.com/angular/angular.js/commit/524650a40ed20f01571e5466475749874ee67288))
- **$http:** ensure default headers PUT and POST are different objects
([e1cfb195](https://github.com/angular/angular.js/commit/e1cfb1957feaf89408bccf48fae6f529e57a82fe),
[#5742](https://github.com/angular/angular.js/issues/5742), [#5747](https://github.com/angular/angular.js/issues/5747), [#5764](https://github.com/angular/angular.js/issues/5764))
- **$rootScope:** prevent infinite $digest by checking if asyncQueue is empty when decrementing ttl
([2cd09c9f](https://github.com/angular/angular.js/commit/2cd09c9f0e7766bcd191662841b7b1ffc3b6dc3f),
[#2622](https://github.com/angular/angular.js/issues/2622))
## Features
- **$animate:**
- provide support for DOM callbacks
([dde1b294](https://github.com/angular/angular.js/commit/dde1b2949727c297e214c99960141bfad438d7a4))
- use requestAnimationFrame instead of a timeout to issue a reflow
([4ae3184c](https://github.com/angular/angular.js/commit/4ae3184c5915aac9aa00889aa2153c8e84c14966),
[#4278](https://github.com/angular/angular.js/issues/4278), [#4225](https://github.com/angular/angular.js/issues/4225))
## Breaking Changes
- **$http:** due to [e1cfb195](https://github.com/angular/angular.js/commit/e1cfb1957feaf89408bccf48fae6f529e57a82fe),
it is now necessary to seperately specify default HTTP headers for PUT, POST and PATCH requests, as these no longer share a single object.
To migrate your code, follow the example below:
Before:
// Will apply to POST, PUT and PATCH methods
$httpProvider.defaults.headers.post = {
"X-MY-CSRF-HEADER": "..."
};
After:
// POST, PUT and PATCH default headers must be specified seperately,
// as they do not share data.
$httpProvider.defaults.headers.post =
$httpProvider.defaults.headers.put =
$httpProviders.defaults.headers.patch = {
"X-MY-CSRF-HEADER": "..."
};
<a name="1.2.8"></a>
# 1.2.8 interdimensional-cartography (2014-01-10)
## Bug Fixes
- **$http:**
- return responseText on IE8 for requests with responseType set
([a9cccbe1](https://github.com/angular/angular.js/commit/a9cccbe14f1bd9048f5dab4443f58c804d4259a1),
[#4464](https://github.com/angular/angular.js/issues/4464), [#4738](https://github.com/angular/angular.js/issues/4738), [#5636](https://github.com/angular/angular.js/issues/5636))
- Allow status code 0 from any protocol
([28fc80bb](https://github.com/angular/angular.js/commit/28fc80bba0107075ab371fd0a7634a38891626b2),
[#1356](https://github.com/angular/angular.js/issues/1356), [#5547](https://github.com/angular/angular.js/issues/5547))
- cancelled JSONP requests will not print error in the console
([95e1b2d6](https://github.com/angular/angular.js/commit/95e1b2d6121b4e26cf87dcf6746a7b8cb4c25e7f),
[#5615](https://github.com/angular/angular.js/issues/5615), [#5616](https://github.com/angular/angular.js/issues/5616))
- **$location:** return '/' for root path in hashbang mode
([63cd873f](https://github.com/angular/angular.js/commit/63cd873fef3207deef30c7a7ed66f4b8f647dc12),
[#5650](https://github.com/angular/angular.js/issues/5650), [#5712](https://github.com/angular/angular.js/issues/5712))
- **$parse:** fix CSP nested property evaluation, and issue that prevented its tests from failing
([3b1a4fe0](https://github.com/angular/angular.js/commit/3b1a4fe0c83c7898ecd7261ab4213998ee7be0ec),
[#5591](https://github.com/angular/angular.js/issues/5591), [#5592](https://github.com/angular/angular.js/issues/5592))
- **closure:** add Closure externs for angular.$q.Promise.finally
([caeb7402](https://github.com/angular/angular.js/commit/caeb7402651702cd13df2f1594e9827439a8b760),
[#4757](https://github.com/angular/angular.js/issues/4757))
- **ngMock window.inject:** Remove Error 'stack' property changes
([7e916455](https://github.com/angular/angular.js/commit/7e916455b36dc9ca4d4afc1e44cade90006d00e3))
## Features
- **select:** allow multiline ng-options
([43a2f3d0](https://github.com/angular/angular.js/commit/43a2f3d0bf435e3626cd679caff4281cfb3415bd),
[#5602](https://github.com/angular/angular.js/issues/5602))
<a name="1.2.7"></a>
# 1.2.7 emoji-clairvoyance (2014-01-03)
## Bug Fixes
- **$animate:**
- ensue class-based animations are always skipped before structural post-digest tasks are run
([bc492c0f](https://github.com/angular/angular.js/commit/bc492c0fc17257ddf2bc5964e205379aa766b3d8),
[#5582](https://github.com/angular/angular.js/issues/5582))
- remove trailing `s` from computed transition duration styles
([50bf0296](https://github.com/angular/angular.js/commit/50bf029625d603fc652f0f413e709f43803743db))
- **$http:**
([3d38fff8](https://github.com/angular/angular.js/commit/3d38fff8b4ea2fd60fadef2028ea4dcddfccb1a4))
- use ActiveX XHR when making PATCH requests on IE8
([6c17d02b](https://github.com/angular/angular.js/commit/6c17d02bc4cc02f478775d62e1f9f77da9da82ad),
[#2518](https://github.com/angular/angular.js/issues/2518), [#5043](https://github.com/angular/angular.js/issues/5043))
- fix 'type mismatch' error on IE8 after each request
([fd9a03e1](https://github.com/angular/angular.js/commit/fd9a03e147aac7e952c6dda1f381fd4662276ba2))
- Ignore multiple calls to onreadystatechange with readyState=4
([4f572366](https://github.com/angular/angular.js/commit/4f57236614415eea919221ea5f99c4d8689b3267),
[#5426](https://github.com/angular/angular.js/issues/5426))
- **$injector:** remove the `INSTANTIATING` flag properly when done
([186a5912](https://github.com/angular/angular.js/commit/186a5912288acfff0ee59dae29af83c37c987921),
[#4361](https://github.com/angular/angular.js/issues/4361), [#5577](https://github.com/angular/angular.js/issues/5577))
- **$location:**
- remove base href domain if the URL begins with '//'
([760f2fb7](https://github.com/angular/angular.js/commit/760f2fb73178e56c37397b3c5876f7dac96f0455),
[#5606](https://github.com/angular/angular.js/issues/5606))
- fix $location.path() behaviour when $locationChangeStart is triggered by the browser
([cf686285](https://github.com/angular/angular.js/commit/cf686285c22d528440e173fdb65ad1052d96df3c),
[#4989](https://github.com/angular/angular.js/issues/4989), [#5089](https://github.com/angular/angular.js/issues/5089), [#5118](https://github.com/angular/angular.js/issues/5118), [#5580](https://github.com/angular/angular.js/issues/5580))
- re-assign history after BFCache back on Android browser
([bddd46c8](https://github.com/angular/angular.js/commit/bddd46c8ecf49cfe6c999cd6b4a69b7d7e1f9a33),
[#5425](https://github.com/angular/angular.js/issues/5425))
- **$resource:** prevent URL template from collapsing into an empty string
([131e4014](https://github.com/angular/angular.js/commit/131e4014b831ac81b7979c4523da81ebc5861c70),
[#5455](https://github.com/angular/angular.js/issues/5455), [#5493](https://github.com/angular/angular.js/issues/5493))
- **$sanitize:** consider the `size` attribute as a valid/allowed attribute
([056c8493](https://github.com/angular/angular.js/commit/056c8493521988dbb330c6636135b505737da918),
[#5522](https://github.com/angular/angular.js/issues/5522))
- **Scope:** don't let watch deregistration mess up the dirty-checking digest loop
([884ef0db](https://github.com/angular/angular.js/commit/884ef0dbcdfe614cedc824d079361b53e675d033),
[#5525](https://github.com/angular/angular.js/issues/5525))
- **input:**
- use apply on the change event only when one isn't already in progress
([a80049fd](https://github.com/angular/angular.js/commit/a80049fd0ac858eeeb645a4209cb2a661d0b4c33),
[#5293](https://github.com/angular/angular.js/issues/5293))
- prevent double $digest when using jQuery trigger.
([1147f219](https://github.com/angular/angular.js/commit/1147f21999edf9a434cd8d24865a6455e744d858),
[#5293](https://github.com/angular/angular.js/issues/5293))
- **ngRepeat:** allow for more flexible coding style in ngRepeat expression
([c9705b75](https://github.com/angular/angular.js/commit/c9705b755645a4bfe066243f2ba15a733c3787e1),
[#5537](https://github.com/angular/angular.js/issues/5537), [#5598](https://github.com/angular/angular.js/issues/5598))
- **ngRoute:** instantiate controller when template is empty
([498365f2](https://github.com/angular/angular.js/commit/498365f219f65d6c29bdf2f03610a4d3646009bb),
[#5550](https://github.com/angular/angular.js/issues/5550))
- **ngShow/ngHide, ngIf:** functions with zero args should be truthy
([01c5be46](https://github.com/angular/angular.js/commit/01c5be4681e34cdc5f5c461b7a618fefe8038919),
[#5414](https://github.com/angular/angular.js/issues/5414))
## Performance Improvements
- **Scope:** limit propagation of $broadcast to scopes that have listeners for the event
([80e7a455](https://github.com/angular/angular.js/commit/80e7a4558490f7ffd33d142844b9153a5ed00e86),
[#5341](https://github.com/angular/angular.js/issues/5341), [#5371](https://github.com/angular/angular.js/issues/5371))
<a name="1.2.6"></a>
# 1.2.6 taco-salsafication (2013-12-19)
## Bug Fixes
- **$animate:** use a scheduled timeout in favor of a fallback property to close transitions
([54637a33](https://github.com/angular/angular.js/commit/54637a335f885110efaa702a3bab29c77644b36c),
[#5255](https://github.com/angular/angular.js/issues/5255), [#5241](https://github.com/angular/angular.js/issues/5241), [#5405](https://github.com/angular/angular.js/issues/5405))
- **$compile:** remove invalid IE exceptional case for `href`
([c7a1d1ab](https://github.com/angular/angular.js/commit/c7a1d1ab0b663edffc1ac7b54deea847e372468d),
[#5479](https://github.com/angular/angular.js/issues/5479))
- **$location:** parse xlink:href for SVGAElements
([bc3ff2ce](https://github.com/angular/angular.js/commit/bc3ff2cecd0861766a9e8606f3cc2c582d9875df),
[#5472](https://github.com/angular/angular.js/issues/5472), [#5198](https://github.com/angular/angular.js/issues/5198), [#5199](https://github.com/angular/angular.js/issues/5199), [#4098](https://github.com/angular/angular.js/issues/4098), [#1420](https://github.com/angular/angular.js/issues/1420))
- **$log:** should work in IE8
([4f5758e6](https://github.com/angular/angular.js/commit/4f5758e6669222369889c9e789601d25ff885530),
[#5400](https://github.com/angular/angular.js/issues/5400))
- **$parse:** return `undefined` if an intermetiate property's value is `null`
([26d43cac](https://github.com/angular/angular.js/commit/26d43cacdc106765bd928d41600352198f887aef),
[#5480](https://github.com/angular/angular.js/issues/5480))
- **closure:** add type definition for `Scope#$watchCollection`
([8f329ffb](https://github.com/angular/angular.js/commit/8f329ffb829410e1fd8f86a766929134e736e3e5),
[#5475](https://github.com/angular/angular.js/issues/5475))
- **forEach:** allow looping over result of `querySelectorAll` in IE8
([274a6734](https://github.com/angular/angular.js/commit/274a6734ef1fff543cc50388a0958d1988baeb57))
- **input:** do not hold input for composition on Android
([3dc18037](https://github.com/angular/angular.js/commit/3dc18037e8db8766641a4d39f0fee96077db1fcb),
[#5308](https://github.com/angular/angular.js/issues/5308))
- **jqLite:** support unbind self within handler
([2f91cfd0](https://github.com/angular/angular.js/commit/2f91cfd0d2986899c38641100c1851b2f9d3888a))
- **ngRepeat:** allow multiline expressions
([cbb3ce2c](https://github.com/angular/angular.js/commit/cbb3ce2c309052b951d0cc87e4c6daa9c48a3dd8),
[#5000](https://github.com/angular/angular.js/issues/5000))
- **select:** invalidate when `multiple`, `required`, and model is `[]`
([5c97731a](https://github.com/angular/angular.js/commit/5c97731a22ed87d64712e673efea0e8a05eae65f),
[#5337](https://github.com/angular/angular.js/issues/5337))
## Features
- **jqLite:** provide support for `element.one()`
([937caab6](https://github.com/angular/angular.js/commit/937caab6475e53a7ea0206e992f8a52449232e78))
- **ngAnimate:** provide configuration support to match specific className values to trigger animations
([cef084ad](https://github.com/angular/angular.js/commit/cef084ade9072090259d8c679751cac3ffeaed51),
[#5357](https://github.com/angular/angular.js/issues/5357), [#5283](https://github.com/angular/angular.js/issues/5283))
## Performance Improvements
- **compile:** add class 'ng-scope' before cloning and other micro-optimizations
([f3a796e5](https://github.com/angular/angular.js/commit/f3a796e522afdbd3b640d14426edb2fbfab463c5),
[#5471](https://github.com/angular/angular.js/issues/5471))
- **$parse:** use a faster path when the number of path parts is low
([f4462319](https://github.com/angular/angular.js/commit/864b2596b246470cca9d4e223eaed720f4462319))
- use faster check for `$$` prefix
([06c5cfc7](https://github.com/angular/angular.js/commit/cb29632a5802e930262919b3db64ca4806c5cfc7))
<a name="1.2.5"></a>
# 1.2.5 singularity-expansion (2013-12-13)
## Bug Fixes
- **$compile:** allow literals in isolate scope references
([43072e38](https://github.com/angular/angular.js/commit/43072e3812e32b89b97ad03144577cba50d4b776),
[#5296](https://github.com/angular/angular.js/issues/5296))
- **angular-mocks:** use copy of mock data in $httpBackend
([f69dc162](https://github.com/angular/angular.js/commit/f69dc16241c8b631123ad0b09674f0a5e0ff32fe))
- **closure:** add missing FormController extern definitions
([1d5e18b0](https://github.com/angular/angular.js/commit/1d5e18b062c3e33b2a8d96aa58d905ed2cd48649),
[#5303](https://github.com/angular/angular.js/issues/5303))
- **ngInclude:** add template to DOM before linking other directives
([30a8b7d0](https://github.com/angular/angular.js/commit/30a8b7d0b5d4882c2bf3b20eb696a02f5b667726),
[#5247](https://github.com/angular/angular.js/issues/5247))
- **ngView:** add template to DOM before linking other directives
([f8944efe](https://github.com/angular/angular.js/commit/f8944efe70b81e02704df9b53ea2546c80c73d3b))
## Performance Improvements
- **$injector:** remove invoke optimization that doesn't work
([05e4fd34](https://github.com/angular/angular.js/commit/05e4fd3488b89e670c36869f18defe26deac2efa),
[#5388](https://github.com/angular/angular.js/issues/5388))
- **$resource:** use shallow copy instead of angular.copy
([fcd2a813](https://github.com/angular/angular.js/commit/fcd2a8131a3cb3e59a616bf31e61510b5c3a97d3),
[#5300](https://github.com/angular/angular.js/issues/5300))
- **a:** do not link when href or name exists in template
([f3de5b6e](https://github.com/angular/angular.js/commit/f3de5b6eac90baf649506072162f36dbc6d2f028),
[#5362](https://github.com/angular/angular.js/issues/5362))
- **jqLite:** implement and use the `empty` method in place of `html()`
([3410f65e](https://github.com/angular/angular.js/commit/3410f65e790a81d457b4f4601a1e760a6f8ede5e),
[#4457](https://github.com/angular/angular.js/issues/4457))
## Breaking Changes
- **angular-mocks:** due to [f69dc162](https://github.com/angular/angular.js/commit/f69dc16241c8b631123ad0b09674f0a5e0ff32fe),
some tests that rely on identity comparison rather than equality comparison in checking mock http responses will be broken,
since now each mock response is a copy of the original response. This is usually fixable by changing a `.toBe()` comparison
to `toEqual()` inside of tests.
<a name="1.2.4"></a>
# 1.2.4 wormhole-blaster (2013-12-06)
## Bug Fixes
- **$animate:**
- ensure animations work with directives that share a transclusion
([958d3d56](https://github.com/angular/angular.js/commit/958d3d56b1899a2cfc7b18c0292e5a1d8c64d0a5),
[#4716](https://github.com/angular/angular.js/issues/4716), [#4871](https://github.com/angular/angular.js/issues/4871), [#5021](https://github.com/angular/angular.js/issues/5021), [#5278](https://github.com/angular/angular.js/issues/5278))
- ensure ms durations are properly rounded
([93901bdd](https://github.com/angular/angular.js/commit/93901bdde4bb9f0ba114ebb33b8885808e1823e1),
[#5113](https://github.com/angular/angular.js/issues/5113), [#5162](https://github.com/angular/angular.js/issues/5162))
- **$compile:**
- update cloned elements if the template arrives after the cloning
([b0972a2e](https://github.com/angular/angular.js/commit/b0972a2e75909e41dbac6e4413ada7df2d51df3a))
- ensure the isolated local watch `lastValue` is always in sync
([2d0f6ccb](https://github.com/angular/angular.js/commit/2d0f6ccba896fe34141d6d4f59eef6fba580c5c2),
[#5182](https://github.com/angular/angular.js/issues/5182))
- **$rootScope:**
- ensure that when the $destroy event is broadcast on $rootScope that it does something
([d802ed1b](https://github.com/angular/angular.js/commit/d802ed1b3680cfc1751777fac465b92ee29944dc),
[#5169](https://github.com/angular/angular.js/issues/5169))
- ensure the phase is cleared within a digest if an exception is raised by a watcher
([d3c486dd](https://github.com/angular/angular.js/commit/d3c486dd6dfa8d5dca32a3e28aa685fb7260c878))
- **$sanitize:** don't rely on YARR regex engine executing immediately in order to prevent object mutation
([81b81856](https://github.com/angular/angular.js/commit/81b81856ee43d2876927c4e1f774affa87e99707),
[#5193](https://github.com/angular/angular.js/issues/5193), [#5192](https://github.com/angular/angular.js/issues/5192))
- **closure:** closure compiler shouldn't rename .defaults.transformRequest
([f01087f8](https://github.com/angular/angular.js/commit/f01087f802839637843115cbcf99702e09d866f6))
- **input:** ensure ngModelWatch() triggers second digest pass when appropriate
([b6d54393](https://github.com/angular/angular.js/commit/b6d5439343b9801f7f2a009d0de09cba9aa21a1d),
[#5258](https://github.com/angular/angular.js/issues/5258), [#5282](https://github.com/angular/angular.js/issues/5282))
- **isElement:** return boolean value rather than `truthy` value.
([2dbb6f9a](https://github.com/angular/angular.js/commit/2dbb6f9a54eb5ff5847eed11c85ac4cf119eb41c),
[#4519](https://github.com/angular/angular.js/issues/4519), [#4534](https://github.com/angular/angular.js/issues/4534))
- **jqLite:** ignore incompatible nodes on find()
([1169b544](https://github.com/angular/angular.js/commit/1169b5445691e1495354d235a3badf05240e3904),
[#4120](https://github.com/angular/angular.js/issues/4120))
- **ngInit:** evaluate ngInit before ngInclude
([0e50810c](https://github.com/angular/angular.js/commit/0e50810c53428f4c1f5bfdba9599df54cb7a6c6e),
[#5167](https://github.com/angular/angular.js/issues/5167), [#5208](https://github.com/angular/angular.js/issues/5208))
- **ngSanitize:** prefer textContent to innerText to avoid layout trashing
([bf1972dc](https://github.com/angular/angular.js/commit/bf1972dc1e8ffbeaddfa53df1d49bc5a2177f09c))
## Performance Improvements
- **$parse:** micro-optimization for ensureSafeObject function
([689dfb16](https://github.com/angular/angular.js/commit/689dfb167924a61aef444ce7587fb987d8080990),
[#5246](https://github.com/angular/angular.js/issues/5246))
- **Scope:** short-circuit after dirty-checking last dirty watcher
([d070450c](https://github.com/angular/angular.js/commit/d070450cd2b3b3a3aa34b69d3fa1f4cc3be025dd),
[#5272](https://github.com/angular/angular.js/issues/5272), [#5287](https://github.com/angular/angular.js/issues/5287))
<a name="1.2.3"></a>
# 1.2.3 unicorn-zapper (2013-11-27)
## Bug Fixes
- **$animate:**
- ensure blocked keyframe animations are unblocked before the DOM operation
([2efe8230](https://github.com/angular/angular.js/commit/2efe82309ac8ff4f67df8b6e40a539ea31e15804),
[#5106](https://github.com/angular/angular.js/issues/5106))
- ensure animations are disabled during bootstrap to prevent unwanted structural animations
([eed23332](https://github.com/angular/angular.js/commit/eed2333298412fbad04eda97ded3487c845b9eb9),
[#5130](https://github.com/angular/angular.js/issues/5130))
- **$sanitize:** use the same whitelist mechanism as `$compile` does
([33352348](https://github.com/angular/angular.js/commit/333523483f3ce6dd3177b697a5e5a7177ca364c8),
[#3748](https://github.com/angular/angular.js/issues/3748))
- **input:** react to form auto completion, through the `change` event, on modern browsers
([a090400f](https://github.com/angular/angular.js/commit/a090400f09d7993d102f527609879cdc74abae60),
[#1460](https://github.com/angular/angular.js/issues/1460))
- **$attrs:** add `$attrs.$attr` to externs so that it isn't renamed on js minification
([bcca8054](https://github.com/angular/angular.js/commit/bcca80548dde85ffe3838c943ba8e5c2deb1c721))
## Features
No new features in this release
## Breaking Changes
There are no breaking changes in this release (promise!)
<a name="1.2.2"></a>
# 1.2.2 consciousness-inertia (2013-11-22)
## Bug Fixes
- **$animate:**
- ensure keyframe animations are blocked around the reflow
([6760d7a3](https://github.com/angular/angular.js/commit/6760d7a315d7ea5cbd4f8ab74b200f754a2041f4),
[#5018](https://github.com/angular/angular.js/issues/5018))
- ensure transition animations are unblocked before the dom operation occurs
([062fbed8](https://github.com/angular/angular.js/commit/062fbed8fc3f7bc55433f8c6915c27520e6f63c5),
[#5014](https://github.com/angular/angular.js/issues/5014),
[#4265](https://github.com/angular/angular.js/issues/4265))
- ensure addClass/removeClass animations do not snap during reflow
([76e4db6f](https://github.com/angular/angular.js/commit/76e4db6f3d15199ac1fbe85f9cfa6079a1c4fa56),
[#4892](https://github.com/angular/angular.js/issues/4892))
- ensure the DOM operation isn't run twice
([7067a8fb](https://github.com/angular/angular.js/commit/7067a8fb0b18d5b5489006e1960cee721a88b4d2),
[#4949](https://github.com/angular/angular.js/issues/4949))
- **$compile:**
- secure form[action] & iframe[srcdoc]
([0421cb42](https://github.com/angular/angular.js/commit/0421cb4200e672818ed10996e92311404c150c3a),
[#4927](https://github.com/angular/angular.js/issues/4927),
[#4933](https://github.com/angular/angular.js/issues/4933))
- ensure CSS classes are added and removed only when necessary
([0cd7e8f2](https://github.com/angular/angular.js/commit/0cd7e8f22721f62b62440bb059ae764ebbe7b42a))
- **$httpBackend:** only IE8 and below can't use `script.onload` for JSONP
([a3172a28](https://github.com/angular/angular.js/commit/a3172a285fd74b5aa6c8d68a4988c767c06f549c),
[#4523](https://github.com/angular/angular.js/issues/4523),
[#4527](https://github.com/angular/angular.js/issues/4527),
[#4922](https://github.com/angular/angular.js/issues/4922))
- **$parse:** allow for new lines in expr when promise unwrapping is on
([40647b17](https://github.com/angular/angular.js/commit/40647b179c473f3f470bb1b3237d6f006269582f),
[#4718](https://github.com/angular/angular.js/issues/4718))
- **$resource:** Always return a resource instance when calling class methods on resources.
([f6ecf9a3](https://github.com/angular/angular.js/commit/f6ecf9a3c9090593faf5fa50586c99a56b51c776),
[#4545](https://github.com/angular/angular.js/issues/4545),
[#5061](https://github.com/angular/angular.js/issues/5061))
- **httpBackend:** should not read response data when request is aborted
([6f1050df](https://github.com/angular/angular.js/commit/6f1050df4fa885bd59ce85adbef7350ea93911a3),
[#4913](https://github.com/angular/angular.js/issues/4913),
[#4940](https://github.com/angular/angular.js/issues/4940))
- **loader:** expose `$$minErr` to modules such as`ngResource`
([9e89a31b](https://github.com/angular/angular.js/commit/9e89a31b129e40c805178535c244899ffafb77d8),
[#5050](https://github.com/angular/angular.js/issues/5050))
- **ngAnimate:**
- correctly retain and restore existing styles during and after animation
([c42d0a04](https://github.com/angular/angular.js/commit/c42d0a041890b39fc98afd357ec1307a3a36208d),
[#4869](https://github.com/angular/angular.js/issues/4869))
- use a fallback CSS property that doesn't break existing styles
([1d50663b](https://github.com/angular/angular.js/commit/1d50663b38ba042e8d748ffa6d48cfb5e93cfd7e),
[#4902](https://github.com/angular/angular.js/issues/4902),
[#5030](https://github.com/angular/angular.js/issues/5030))
- **ngClass:** ensure that ngClass only adds/removes the changed classes
([6b8bbe4d](https://github.com/angular/angular.js/commit/6b8bbe4d90640542eed5607a8c91f6b977b1d6c0),
[#4960](https://github.com/angular/angular.js/issues/4960),
[#4944](https://github.com/angular/angular.js/issues/4944))
- **ngController:** fix issue with ngInclude on the same element
([6288cf5c](https://github.com/angular/angular.js/commit/6288cf5ca471b0615a026fdb4db3ba242c9d8f88),
[#4431](https://github.com/angular/angular.js/issues/4431))
- **ngInclude:**
- Don't throw when the ngInclude element contains content with directives.
([0a7cbb33](https://github.com/angular/angular.js/commit/0a7cbb33b06778833a4d99b1868cc07690a827a7))
- allow ngInclude to load scripts when jQuery is included
([c47abd0d](https://github.com/angular/angular.js/commit/c47abd0dd7490576f4b84ee51ebaca385c1036da),
[#3756](https://github.com/angular/angular.js/issues/3756))
- **ngMock:** fixes httpBackend expectation with body object
([4d16472b](https://github.com/angular/angular.js/commit/4d16472b918a3482942d76f1e273a5aa01f65e83),
[#4956](https://github.com/angular/angular.js/issues/4956))
- **ngView:** Don't throw when the ngView element contains content with directives.
([e6521e74](https://github.com/angular/angular.js/commit/e6521e7491242504250b57dd0ee66af49e653c33),
[#5069](https://github.com/angular/angular.js/issues/5069))
- **tests:** Correct tests for IE11
([57924234](https://github.com/angular/angular.js/commit/579242346c4202ea58fc2cae6df232289cbea0bb),
[#5046](https://github.com/angular/angular.js/issues/5046))
- **input:** hold listener during text composition
([a4e6d962](https://github.com/angular/angular.js/commit/a4e6d962d78b26f5112d48c4f88c1e6234d0cae7),
[#4684](https://github.com/angular/angular.js/issues/4684))
<a name="1.2.1"></a>
# 1.2.1 underscore-empathy (2013-11-14)
## Bug Fixes
- **$compile:**
- accessing controllers of transcluded directives from children
([90f87072](https://github.com/angular/angular.js/commit/90f87072e83234ae366cfeb3c281503c31dad738),
[#4935](https://github.com/angular/angular.js/issues/4935))
- correctly handle interpolated style in replace templates
([e1254b26](https://github.com/angular/angular.js/commit/e1254b266dfa2d4e3756e4317152dbdbcabe44be),
[#4882](https://github.com/angular/angular.js/issues/4882))
- **$resource:** don't use $parse for @dotted.member
([9577702e](https://github.com/angular/angular.js/commit/9577702e8d2519c1a60f5ac4058e63bd7b919815))
- **bootstrap:** make IE8 happy
([a61b65d0](https://github.com/angular/angular.js/commit/a61b65d01b468502fe53d68818949d3fcc9f20f6))
- **loader:** don't rely on internal APIs
([8425e9fe](https://github.com/angular/angular.js/commit/8425e9fe383c17f6a5589c778658c5fc0570ae8f),
[#4437](https://github.com/angular/angular.js/issues/4437), [#4874](https://github.com/angular/angular.js/issues/4874))
- **minErr:** remove references to internal APIs
([94764ee0](https://github.com/angular/angular.js/commit/94764ee08910726db1db7a1101c3001500306dea))
- **ngIf:** don't create multiple elements when changing from a truthy value to another thruthy value
([4612705e](https://github.com/angular/angular.js/commit/4612705ec297bc6ba714cb7a98f1be6aff77c4b8),
[#4852](https://github.com/angular/angular.js/issues/4852))
- **urlUtils:**
- make removal of windows drive from path safer
([89f435de](https://github.com/angular/angular.js/commit/89f435de847635e3ec339726e6f83cf3f0ee9091),
[#4939](https://github.com/angular/angular.js/issues/4939))
- return right path for file:// on windows
([f925e8ca](https://github.com/angular/angular.js/commit/f925e8caa6c51a7d45ca9ead30601ec2e9d4464c),
[#4680](https://github.com/angular/angular.js/issues/4680))
## Features
- **$parse:** revert hiding "private" properties
([4ab16aaa](https://github.com/angular/angular.js/commit/4ab16aaaf762e9038803da1f967ac8cb6650727d),
[#4926](https://github.com/angular/angular.js/issues/4926), [#4842](https://github.com/angular/angular.js/issues/4842), [#4865](https://github.com/angular/angular.js/issues/4865), [#4859](https://github.com/angular/angular.js/issues/4859), [#4849](https://github.com/angular/angular.js/issues/4849))
<a name="1.2.0"></a>
# 1.2.0 timely-delivery (2013-11-08)
## Features
- **animations:**
- ensure CSS transitions can work with inherited CSS class definitions
([9d69a0a7](https://github.com/angular/angular.js/commit/9d69a0a7c75c937c0a49bb705d31252326b052df))
- provide support for staggering animations with CSS
([74848307](https://github.com/angular/angular.js/commit/74848307443c00ab07552336c56ddfa1e9ef6eff))
- **$parse:** secure expressions by hiding "private" properties
([3d6a89e8](https://github.com/angular/angular.js/commit/3d6a89e8888b14ae5cb5640464e12b7811853c7e))
- **docs:**
- provide index pages for each angular module
([a7e12b79](https://github.com/angular/angular.js/commit/a7e12b7959212f2fa88fe17d5a045cc9d8b22922))
- add forward slash shortcut key for search bar
([74912802](https://github.com/angular/angular.js/commit/74912802c644ca929e39a7583cb7a9a05f12e91f))
- **jqLite:** expose isolateScope() getter similar to scope()
([27e9340b](https://github.com/angular/angular.js/commit/27e9340b3c25b512e45213b39811098d07e12e3b))
- **misc:** add externs file for Closure Compiler
([9d0a6977](https://github.com/angular/angular.js/commit/9d0a69772c39bfc751ca2000c3b4b3381e51fe93))
## Bug Fixes
- **$animate:**
- don't force animations to be enabled
([98adc9e0](https://github.com/angular/angular.js/commit/98adc9e0383dc05efad168f30a0725cb67f5eda8))
- only apply the fallback property if any transition animations are detected
([94700807](https://github.com/angular/angular.js/commit/9470080762aecca5285d0f5cac4ae01540bbad4c))
- avoid hanging animations if the active CSS transition class is missing
([b89584db](https://github.com/angular/angular.js/commit/b89584db10b63f346cbfd03f67fb92504e5bf362),
[#4732](https://github.com/angular/angular.js/issues/4732), [#4490](https://github.com/angular/angular.js/issues/4490))
- ensure staggering animations understand multiple delay values
([41a2d5b3](https://github.com/angular/angular.js/commit/41a2d5b30f4feb90651eb577cf44852a6d2be72c))
- ensure the active class is not applied if cancelled during reflow
([e53ff431](https://github.com/angular/angular.js/commit/e53ff431e1472c0b2d5405d267d4e403ca31087e),
[#4699](https://github.com/angular/angular.js/issues/4699))
- use direct DOM comparison when checking for $rootElement
([d434eabe](https://github.com/angular/angular.js/commit/d434eabec3955f8d56c859c93befe711bfa1de27),
[#4679](https://github.com/angular/angular.js/issues/4679))
- ensure former nodes are fully cleaned up when a follow-up structural animation takes place
([7f0767ac](https://github.com/angular/angular.js/commit/7f0767acaba1ec3c8849244a604b0d1c8c376446),
[#4435](https://github.com/angular/angular.js/issues/4435))
- ensure enable/disable animations work when the document node is used
([6818542c](https://github.com/angular/angular.js/commit/6818542c694aec6c811fb2fe2f86f7d16544c39b),
[#4669](https://github.com/angular/angular.js/issues/4669))
- skip unnecessary addClass/removeClass animations
([76b628bc](https://github.com/angular/angular.js/commit/76b628bcb3511210d312ed667e5c14d908a9fed1),
[#4401](https://github.com/angular/angular.js/issues/4401), [#2332](https://github.com/angular/angular.js/issues/2332))
- ensure animations work properly when the $rootElement is being animated
([2623de14](https://github.com/angular/angular.js/commit/2623de1426219dc799f63a3d155911f93fc03461),
[#4397](https://github.com/angular/angular.js/issues/4397), [#4231](https://github.com/angular/angular.js/issues/4231))
- only cancel class-based animations if the follow-up class contains CSS transition/keyframe animation code
([f5289fe8](https://github.com/angular/angular.js/commit/f5289fe84ffc1f2368dae7bd14c420abbe76749e),
[#4463](https://github.com/angular/angular.js/issues/4463), [#3784](https://github.com/angular/angular.js/issues/3784))
- **$compile:**
- don't leak isolate scope state when replaced directive is used multiple times
([b5af198f](https://github.com/angular/angular.js/commit/b5af198f0d5b0f2b3ddb31ea12f700f3e0616271))
- correct isolate scope distribution to controllers
([3fe4491a](https://github.com/angular/angular.js/commit/3fe4491a6bf57ddeb312b8a30cf1706f6f1d2355))
- replaced element has isolate scope
([97c7a4e3](https://github.com/angular/angular.js/commit/97c7a4e3791d7cb05c3317cc5f0c49ab93810bf6))
- only pass isolate scope to children that belong to the isolate directive
([d0efd5ee](https://github.com/angular/angular.js/commit/d0efd5eefcc0aaf167c766513e152b74dd31bafe))
- make isolate scope truly isolate
([909cabd3](https://github.com/angular/angular.js/commit/909cabd36d779598763cc358979ecd85bb40d4d7),
[#1924](https://github.com/angular/angular.js/issues/1924), [#2500](https://github.com/angular/angular.js/issues/2500))
- don't instantiate controllers twice for element transclude directives
([18ae985c](https://github.com/angular/angular.js/commit/18ae985c3a3147b589c22f6ec21bacad2f578e2b),
[#4654](https://github.com/angular/angular.js/issues/4654))
- attribute bindings should not break due to terminal directives
([79223eae](https://github.com/angular/angular.js/commit/79223eae5022838893342c42dacad5eca83fabe8),
[#4525](https://github.com/angular/angular.js/issues/4525), [#4528](https://github.com/angular/angular.js/issues/4528), [#4649](https://github.com/angular/angular.js/issues/4649))
- instantiate controlers when re-entering compilation
([faf5b980](https://github.com/angular/angular.js/commit/faf5b980da09da2b4c28f1feab33f87269f9f0ba),
[#4434](https://github.com/angular/angular.js/issues/4434), [#4616](https://github.com/angular/angular.js/issues/4616))
- **$injector:** allow a constructor function to return a function
([c22adbf1](https://github.com/angular/angular.js/commit/c22adbf160f32c1839fbb35382b7a8c6bcec2927))
- **$parse:** check function call context to be safe
([6d324c76](https://github.com/angular/angular.js/commit/6d324c76f0d3ad7dae69ce01b14e0564938fb15e),
[#4417](https://github.com/angular/angular.js/issues/4417))
- **angular-mocks:** add inline dependency annotation
([6d23591c](https://github.com/angular/angular.js/commit/6d23591c31f2b41097ceaa380af09998e4a62f09),
[#4448](https://github.com/angular/angular.js/issues/4448))
- **animateSpec:** run digest to enable animations before tests
([aea76f0d](https://github.com/angular/angular.js/commit/aea76f0d5c43dc17f1319d0a45d2ce50fddf72e4))
- **bootstrap-prettify:** share $animate and $$postDigestQueue with demo apps
([1df3da36](https://github.com/angular/angular.js/commit/1df3da361d62726bf1dafe629a7fca845b6a8733))
- **csp:**
- fix csp auto-detection and stylesheet injection
([08f376f2](https://github.com/angular/angular.js/commit/08f376f2ea3d3bb384f10e3c01f7d48ed21ce351),
[#917](https://github.com/angular/angular.js/issues/917), [#2963](https://github.com/angular/angular.js/issues/2963), [#4394](https://github.com/angular/angular.js/issues/4394), [#4444](https://github.com/angular/angular.js/issues/4444))
- don't inline css in csp mode
([a86cf20e](https://github.com/angular/angular.js/commit/a86cf20e67202d614bbcaf038c5e04db94483256)
- **docModuleComponents:** implement anchor scroll when content added
([eb51b024](https://github.com/angular/angular.js/commit/eb51b024c9b77527420014cdf7dbb292b5b9dd6b),
[#4703](https://github.com/angular/angular.js/issues/4703))
- **input:** keep track of min/max attars on-the-fly
([4b653aea](https://github.com/angular/angular.js/commit/4b653aeac1aca7ac551738870a2446b6810ca0df))
- **ngAnimate:** fix cancelChildAnimations throwing exception
([b9557b0a](https://github.com/angular/angular.js/commit/b9557b0a86206d938a738ea470736d011dff7e1a),
[#4548](https://github.com/angular/angular.js/issues/4548))
- **ngClassSpec:** clear animation enable fn from postDigestQueue
([ffa9d0a6](https://github.com/angular/angular.js/commit/ffa9d0a6db137cba4090e569b8ed4e25a711314e))
- **ngEventDirectives:** parse expression only once during compile phase.
([9a828738](https://github.com/angular/angular.js/commit/9a828738cd2e959bc2a198989e96c8e416d28b71))
- **ngIf:**
- destroy child scope when destroying DOM
([9483373c](https://github.com/angular/angular.js/commit/9483373c331343648e079420b3eb1f564d410ff2))
- ngIf removes elements dynamically added to it
([e19067c9](https://github.com/angular/angular.js/commit/e19067c9bbac3c3bb450c80f73eb5518bd0db1a1))
- **ngInclude:** only run anchorScroll after animation is done
([d378f550](https://github.com/angular/angular.js/commit/d378f5500ab2eef0779338336c6a95656505ebb8),
[#4723](https://github.com/angular/angular.js/issues/4723))
- **ngMock:** throw more descriptive errors for $animate.flushNext()
([6fb19157](https://github.com/angular/angular.js/commit/6fb191570ee72f087e8bb6b1d8f5eea0f585886c))
- **ngModel:** deregister from the form on scope not DOM destruction
([8f989d65](https://github.com/angular/angular.js/commit/8f989d652f70fd147f66a18411070c7b939e242e),
[#4226](https://github.com/angular/angular.js/issues/4226), [#4779](https://github.com/angular/angular.js/issues/4779))
- **ngScenario:** correctly disable animations for end 2 end tests
([9d004585](https://github.com/angular/angular.js/commit/9d0045856351e9db48ddf66f66e210d9cc53d24a))
- **ngView:**
- only run anchorScroll after animation is done
([da344daa](https://github.com/angular/angular.js/commit/da344daa4023556f8abbef6d8ad87a16362b5861))
- ensure the new view element is placed after the old view element
([3f568b22](https://github.com/angular/angular.js/commit/3f568b22f9bec09192588e3cae937db5c2e757f9),
[#4362](https://github.com/angular/angular.js/issues/4362))
- **ngdocs:**
- create mock Doc objects correctly
([d4493fda](https://github.com/angular/angular.js/commit/d4493fda2c4c2ff1fdfc264bfb479741abc781c7))
- `shortDescription()` should not error if no `description`
([4c8fa353](https://github.com/angular/angular.js/commit/4c8fa353245b9c32261860caff18f002d294e19f))
- remove the side search bar
([6c20ec19](https://github.com/angular/angular.js/commit/6c20ec193f11aa647be1b2ad2ac5b3e7c2894bd7))
## Breaking Changes
- **$compile:**
- due to [d0efd5ee](https://github.com/angular/angular.js/commit/d0efd5eefcc0aaf167c766513e152b74dd31bafe),
Child elements that are defined either in the application template or in some other
directives template do not get the isolate scope. In theory, nobody should rely on this behavior, as
it is very rare - in most cases the isolate directive has a template.
- due to [909cabd3](https://github.com/angular/angular.js/commit/909cabd36d779598763cc358979ecd85bb40d4d7),
Directives without isolate scope do not get the isolate scope from an isolate directive on the
same element. If your code depends on this behavior (non-isolate directive needs to access state
from within the isolate scope), change the isolate directive to use scope locals to pass these explicitly.
**Before**
```
<input ng-model="$parent.value" ng-isolate>
.directive('ngIsolate', function() {
return {
scope: {},
template: '{{value}}'
};
});
```
**After**
```
<input ng-model="value" ng-isolate>
.directive('ngIsolate', function() {
return {
scope: {value: '=ngModel'},
template: '{{value}}
};
});
```
Closes [#1924](https://github.com/angular/angular.js/issues/1924) and
[#2500](https://github.com/angular/angular.js/issues/2500)
- due to [79223eae](https://github.com/angular/angular.js/commit/79223eae5022838893342c42dacad5eca83fabe8),
Previously, the interpolation priority was `-100` in 1.2.0-rc.2, and `100` before 1.2.0-rc.2.
Before this change the binding was setup in the post-linking phase.
Now the attribute interpolation (binding) executes as a directive with priority 100 and the
binding is set up in the pre-linking phase.
Closes [#4525](https://github.com/angular/angular.js/issues/4525),
[#4528](https://github.com/angular/angular.js/issues/4528), and
[#4649](https://github.com/angular/angular.js/issues/4649)
- **$parse:** due to [3d6a89e8](https://github.com/angular/angular.js/commit/3d6a89e8888b14ae5cb5640464e12b7811853c7e),
This commit introduces the notion of "private" properties (properties
whose names begin and/or end with an underscore) on the scope chain.
These properties will not be available to Angular expressions (i.e. {{
}} interpolation in templates and strings passed to `$parse`) They are
freely available to JavaScript code (as before).
**Motivation**
Angular expressions execute in a limited context. They do not have
direct access to the global scope, `window`, `document` or the Function
constructor. However, they have direct access to names/properties on
the scope chain. It has been a long standing best practice to keep
sensitive APIs outside of the scope chain (in a closure or your
controller.) That's easier said that done for two reasons:
1. JavaScript does not have a notion of private properties so if you need
someone on the scope chain for JavaScript use, you also expose it to
Angular expressions
2. the new "controller as" syntax that's now in increased usage exposes the
entire controller on the scope chain greatly increaing the exposed surface.
Though Angular expressions are written and controlled by the developer, they:
1. Typically deal with user input
2. Don't get the kind of test coverage that JavaScript code would
This commit provides a way, via a naming convention, to
allow publishing/restricting properties from controllers/scopes to
Angular expressions enabling one to only expose those properties that
are actually needed by the expressions.
- **csp:** due to [08f376f2](https://github.com/angular/angular.js/commit/08f376f2ea3d3bb384f10e3c01f7d48ed21ce351),
triggering ngCsp directive via `ng:csp` attribute is not supported any more.
Please use `data-ng-csp` instead.
- **jqLite:** due to [27e9340b](https://github.com/angular/angular.js/commit/27e9340b3c25b512e45213b39811098d07e12e3b),
`jqLite.scope()` (connonly used through `angular.element(node).scope()`) does not return the
isolate scope on the element that triggered directive with isolate scope. Use
`jqLite.isolateScope()` instead.
<a name="1.2.0-rc.3"></a> <a name="1.2.0-rc.3"></a>
# 1.2.0-rc.3 ferocious-twitch (2013-10-14) # 1.2.0-rc.3 ferocious-twitch (2013-10-14)
@ -4595,6 +3825,3 @@ with the `$route` service
[module]: http://docs-next.angularjs.org/api/angular.mock.module [module]: http://docs-next.angularjs.org/api/angular.mock.module
[guide2.di]: http://docs-next.angularjs.org/guide/dev_guide.di [guide2.di]: http://docs-next.angularjs.org/guide/dev_guide.di
[jqLite2]: http://docs.angularjs.org/#!/api/angular.element [jqLite2]: http://docs.angularjs.org/#!/api/angular.element
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/CHANGELOG.md?pixel)](https://github.com/igrigorik/ga-beacon)

View file

@ -29,124 +29,32 @@ duplication of work, and help you to craft the change so that it is successfully
project. project.
* **Small Changes** can be crafted and submitted to [GitHub Repository][github] as a Pull Request. * **Small Changes** can be crafted and submitted to [GitHub Repository][github] as a Pull Request.
## Want a Doc Fix?
If you want to help improve the docs, it's a good idea to let others know what you're working on to
minimize duplication of effort. Before starting, check out the issue queue for [Milestone:Docs Only](https://github.com/angular/angular.js/issues?milestone=24&state=open).
Comment on an issue to let others know what you're working on, or create a new issue if your work
doesn't fit within the scope of any of the existing doc fix projects.
For large fixes, please build and test the documentation before submitting the PR to be sure you haven't
accidentally introduced any layout or formatting issues.You should also make sure that your commit message
is labeled "docs:" and follows the **Git Commit Guidelines** outlined below.
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly.
## Submission Guidelines ## Submission Guidelines
### Submitting an Issue ### Submitting an Issue
Before you submit your issue search the archive, maybe your question was already answered. Before you submit your issue follow the following guidelines:
* Search the archive first, it's likely that your question was already answered.
* A live example demonstrating the issue, will get an answer faster.
* Create one using [Plunker][plunker] or [JSFiddle][jsfiddle].
* If you get help, help others. Good karma rulez!
If your issue appears to be a bug, and hasn't been reported, open a new issue. If your issue appears to be a bug, and hasn't been reported, open a new issue.
Help us to maximize the effort we can spend fixing issues and adding new Help us to maximize the effort we can spend fixing issues and adding new
features, by not reporting duplicate issues. Providing the following information will increase the features, by not reporting duplicate issues.
chances of your issue being dealt with quickly:
* **Overview of the issue** - if an error is being thrown a non-minified stack trace helps
* **Motivation for or Use Case** - explain why this is a bug for you
* **Angular Version(s)** - is it a regression?
* **Browsers and Operating System** - is this a problem with all browsers or only IE8?
* **Reproduce the error** - provide a live example (using [Plunker][plunker] or
[JSFiddle][jsfiddle]) or a unambiguous set of steps.
* **Related issues** - has a similar issue been reported before?
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
causing the problem (line of code or commit)
Here is a great example of a well defined issue: https://github.com/angular/angular.js/issues/5069
**If you get help, help others. Good karma rulez!**
### Submitting a Pull Request ### Submitting a Pull Request
Before you submit your pull request consider the following guidelines: Before you submit your pull request follow the following guidelines:
* Search [GitHub](https://github.com/angular/angular.js/pulls) for an open or closed Pull Request * Search GitHub for an open or closed Pull Request that relates to your submission. You don't want
that relates to your submission. You don't want to duplicate effort. to duplicate effort.
* Please sign our [Contributor License Agreement (CLA)](#signing-the-cla) before sending pull
requests. We cannot accept code without this.
* Make your changes in a new git branch * Make your changes in a new git branch
* Follow our Coding Rules
```shell * Follow our Git Commit Guidelines
git checkout -b my-fix-branch master * Build your changes locally and on Travis (by pushing to GitHub) to ensure all the tests pass.
``` * Sign the Contributor License Agreement (CLA). We cannot accept code without this.
* Create your patch, including appropriate test cases.
* Follow our [Coding Rules](#coding-rules)
* Commit your changes and create a descriptive commit message (the
commit message is used to generate release notes, please check out our
[commit message conventions](#commit-message-format) and our commit message presubmit hook
`validate-commit-msg.js`):
```shell
git commit -a
```
* Build your changes locally to ensure all the tests pass
```shell
grunt test
```
* Push your branch to Github:
```shell
git push origin my-fix-branch
```
* In Github, send a pull request to `angular:master`.
* If we suggest changes then you can modify your branch, rebase and force a new push to your GitHub * If we suggest changes then you can modify your branch, rebase and force a new push to your GitHub
repository to update the Pull Request: repository to update the Pull Request.
```shell
git rebase master -i
git push -f
```
That's it! Thank you for your contribution!
When the patch is reviewed and merged, you can safely delete your branch and pull the changes
from the main (upstream) repository:
* Delete the remote branch on Github:
```shell
git push origin --delete my-fix-branch
```
* Check out the master branch:
```shell
git checkout master -f
```
* Delete the local branch:
```shell
git branch -D my-fix-branch
```
* Update your master with the latest upstream version:
```shell
git pull --ff upstream master
```
### GitHub Pull Request Helper
We track Pull Requests by attaching labels and assigning to milestones. For some reason GitHub
does not provide a good UI for managing labels on Pull Requests (unlike Issues). We have developed
a simple Chrome Extension that enables you to view (and manage if you have permission) the labels
on Pull Requests. You can get the extension from the Chrome WebStore -
[GitHub PR Helper][github-pr-helper]
## Coding Rules ## Coding Rules
To ensure consistency throughout the source code, keep these rules in mind as you are working: To ensure consistency throughout the source code, keep these rules in mind as you are working:
@ -198,7 +106,6 @@ Must be one of the following:
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
semi-colons, etc) semi-colons, etc)
* **refactor**: A code change that neither fixes a bug or adds a feature * **refactor**: A code change that neither fixes a bug or adds a feature
* **perf**: A code change that improves performance
* **test**: Adding missing tests * **test**: Adding missing tests
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
generation generation
@ -239,6 +146,56 @@ You can find out more detailed information about contributing in the
[AngularJS documentation][contributing]. [AngularJS documentation][contributing].
## Submitting Your Changes
To create and submit a change:
1. Please sign our [Contributor License Agreement (CLA)](#signing-the-cla) before sending pull
requests.
2. Create and checkout a new branch off the master branch for your changes:
```shell
git checkout -b my-fix-branch master
```
3. Create your patch, including appropriate test cases.
4. Commit your changes and create a descriptive commit message (the commit message is used to
generate release notes, please check out our [commit message conventions](#commit-message-format)
and our commit message presubmit hook `validate-commit-msg.js`):
```shell
git commit -a
```
5. Push your branch to Github:
```shell
git push origin my-fix-branch
```
6. In Github, send a pull request to `angular:master`.
That's it! Thank you for your contribution!
When the patch is reviewed and merged, you can safely delete your branch and pull the changes
from the main (upstream) repository:
```shell
# Delete the remote branch on Github:
git push origin :my-fix-branch
# Check out the master branch:
git checkout master
# Delete the local branch:
git branch -D my-fix-branch
# Update your master with the latest upstream version:
git pull --ff upstream master
```
[github]: https://github.com/angular/angular.js [github]: https://github.com/angular/angular.js
[Google Closure I18N library]: https://code.google.com/p/closure-library/source/browse/closure/goog/i18n/ [Google Closure I18N library]: https://code.google.com/p/closure-library/source/browse/closure/goog/i18n/
@ -257,6 +214,3 @@ You can find out more detailed information about contributing in the
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html [individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html [corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit# [commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
[github-pr-helper]: https://chrome.google.com/webstore/detail/github-pr-helper/mokbklfnaddkkbolfldepnkfmanfhpen
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/CONTRIBUTING.md?pixel)](https://github.com/igrigorik/ga-beacon)

View file

@ -4,8 +4,16 @@ var path = require('path');
module.exports = function(grunt) { module.exports = function(grunt) {
//grunt plugins //grunt plugins
require('load-grunt-tasks')(grunt); grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-jasmine-node');
grunt.loadNpmTasks('grunt-ddescribe-iit');
grunt.loadNpmTasks('grunt-merge-conflict');
grunt.loadNpmTasks('grunt-parallel');
grunt.loadNpmTasks('grunt-shell');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadTasks('lib/grunt'); grunt.loadTasks('lib/grunt');
var NG_VERSION = util.getVersion(); var NG_VERSION = util.getVersion();
@ -78,7 +86,9 @@ module.exports = function(grunt) {
jqlite: 'karma-jqlite.conf.js', jqlite: 'karma-jqlite.conf.js',
jquery: 'karma-jquery.conf.js', jquery: 'karma-jquery.conf.js',
docs: 'karma-docs.conf.js', docs: 'karma-docs.conf.js',
modules: 'karma-modules.conf.js' modules: 'karma-modules.conf.js',
//NOTE run grunt test:e2e instead and it will start a webserver for you
end2end: 'karma-e2e.conf.js'
}, },
@ -90,56 +100,51 @@ module.exports = function(grunt) {
}, },
runprotractor: {
normal: 'protractor-conf.js'
},
clean: { clean: {
build: ['build'], build: ['build'],
tmp: ['tmp'] tmp: ['tmp']
}, },
jshint: { jshint: {
options: {
jshintrc: true,
},
ng: { ng: {
files: { src: files['angularSrc'] }, files: { src: files['angularSrc'] },
options: { jshintrc: 'src/.jshintrc' }
}, },
ngAnimate: { ngAnimate: {
files: { src: 'src/ngAnimate/**/*.js' }, files: { src: 'src/ngAnimate/**/*.js' },
options: { jshintrc: 'src/ngAnimate/.jshintrc' }
}, },
ngCookies: { ngCookies: {
files: { src: 'src/ngCookies/**/*.js' }, files: { src: 'src/ngCookies/**/*.js' },
options: { jshintrc: 'src/ngCookies/.jshintrc' }
}, },
ngLocale: { ngLocale: {
files: { src: 'src/ngLocale/**/*.js' }, files: { src: 'src/ngLocale/**/*.js' },
options: { jshintrc: 'src/ngLocale/.jshintrc' }
}, },
ngMock: { ngMock: {
files: { src: 'src/ngMock/**/*.js' }, files: { src: 'src/ngMock/**/*.js' },
options: { jshintrc: 'src/ngMock/.jshintrc' }
}, },
ngResource: { ngResource: {
files: { src: 'src/ngResource/**/*.js' }, files: { src: 'src/ngResource/**/*.js' },
options: { jshintrc: 'src/ngResource/.jshintrc' }
}, },
ngRoute: { ngRoute: {
files: { src: 'src/ngRoute/**/*.js' }, files: { src: 'src/ngRoute/**/*.js' },
options: { jshintrc: 'src/ngRoute/.jshintrc' }
}, },
ngSanitize: { ngSanitize: {
files: { src: 'src/ngSanitize/**/*.js' }, files: { src: 'src/ngSanitize/**/*.js' },
options: { jshintrc: 'src/ngSanitize/.jshintrc' }
}, },
ngScenario: { ngScenario: {
files: { src: 'src/ngScenario/**/*.js' }, files: { src: 'src/ngScenario/**/*.js' },
options: { jshintrc: 'src/ngScenario/.jshintrc' }
}, },
ngTouch: { ngTouch: {
files: { src: 'src/ngTouch/**/*.js' }, files: { src: 'src/ngTouch/**/*.js' },
} options: { jshintrc: 'src/ngTouch/.jshintrc' }
},
jscs: {
src: ['src/**/*.js', 'test/**/*.js'],
options: {
config: ".jscs.json"
} }
}, },
@ -173,7 +178,7 @@ module.exports = function(grunt) {
}, },
mocks: { mocks: {
dest: 'build/angular-mocks.js', dest: 'build/angular-mocks.js',
src: util.wrap(files['angularModules']['ngMock'], 'module'), src: files['angularModules']['ngMock'],
strict: false strict: false
}, },
sanitize: { sanitize: {
@ -270,28 +275,18 @@ module.exports = function(grunt) {
write: { write: {
versionTXT: {file: 'build/version.txt', val: NG_VERSION.full}, versionTXT: {file: 'build/version.txt', val: NG_VERSION.full},
versionJSON: {file: 'build/version.json', val: JSON.stringify(NG_VERSION)} versionJSON: {file: 'build/version.json', val: JSON.stringify(NG_VERSION)}
},
bump: {
options: {
files: ['package.json'],
commit: false,
createTag: false,
push: false
}
} }
}); });
//alias tasks //alias tasks
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']); grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:e2e']);
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']); grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']); grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['tests:modules']); grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['tests:modules']);
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']); grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['tests:jqlite', 'tests:jquery', 'tests:modules']); grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['tests:jqlite', 'tests:jquery', 'tests:modules']);
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'runprotractor:normal']); grunt.registerTask('test:e2e', 'Run the end to end tests with Karma and keep a test server running in the background', ['connect:testserver', 'tests:end2end']);
grunt.registerTask('test:e2e', 'Alias for test:protractor', ['test:protractor']);
grunt.registerTask('test:docgen', ['jasmine_node']); grunt.registerTask('test:docgen', ['jasmine_node']);
grunt.registerTask('test:promises-aplus',['build:promises-aplus-adapter','shell:promises-aplus-tests']); grunt.registerTask('test:promises-aplus',['build:promises-aplus-adapter','shell:promises-aplus-tests']);
@ -299,6 +294,6 @@ module.exports = function(grunt) {
grunt.registerTask('webserver', ['connect:devserver']); grunt.registerTask('webserver', ['connect:devserver']);
grunt.registerTask('package', ['bower','clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']); grunt.registerTask('package', ['bower','clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']);
grunt.registerTask('package-without-bower', ['clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']); grunt.registerTask('package-without-bower', ['clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']);
grunt.registerTask('ci-checks', ['ddescribe-iit', 'merge-conflict', 'jshint', 'jscs']); grunt.registerTask('ci-checks', ['ddescribe-iit', 'merge-conflict', 'jshint', 'test:docgen']);
grunt.registerTask('default', ['package']); grunt.registerTask('default', ['package']);
}; };

View file

@ -1,6 +1,6 @@
The MIT License The MIT License
Copyright (c) 2010-2014 Google, Inc. http://angularjs.org Copyright (c) 2010-2012 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,23 +0,0 @@
Using AngularJS with the Closure Compiler
=========================================
The Closure Compiler project contains externs definitions for AngularJS
JavaScript in its `contrib/externs` directory.
The definitions contain externs for use with the Closure compiler (aka
JSCompiler). Passing these files to the --externs parameter of a compiler
pass allows using type annotations for AngularJS objects. For example,
Angular's $scope objects can be annotated as:
```js
/** @type {angular.Scope} */
var scope = $scope;
```
This allows JSCompiler to type check accesses to scope, give warnings about
missing methods or incorrect arguments, and also prevents renaming of property
accesses with advanced compilation.
The externs are incomplete and maintained on an as-needed basis, but strive to
be correct. Externs for individual modules should be added in separate files.
See https://developers.google.com/closure/compiler/

View file

@ -7,7 +7,7 @@ syntax to express your applications components clearly and succinctly. It au
synchronizes data from your UI (view) with your JavaScript objects (model) through 2-way data synchronizes data from your UI (view) with your JavaScript objects (model) through 2-way data
binding. To help you structure your application better and make it easy to test, AngularJS teaches binding. To help you structure your application better and make it easy to test, AngularJS teaches
the browser how to do dependency injection and inversion of control. Oh yeah and it also helps with the browser how to do dependency injection and inversion of control. Oh yeah and it also helps with
server-side communication, taming async callbacks with promises and deferreds; and makes client-side server-side communication, taming async callbacks with promises and deferreds; and make client-side
navigation and deeplinking with hashbang urls or HTML5 pushState a piece of cake. The best of all: navigation and deeplinking with hashbang urls or HTML5 pushState a piece of cake. The best of all:
it makes development fun! it makes development fun!
@ -38,7 +38,3 @@ To execute end-to-end (e2e) tests, use:
To learn more about the grunt tasks, run `grunt --help` and also read our To learn more about the grunt tasks, run `grunt --help` and also read our
[contribution guidelines](http://docs.angularjs.org/misc/contribute). [contribution guidelines](http://docs.angularjs.org/misc/contribute).
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/README.md?pixel)](https://github.com/igrigorik/ga-beacon)

View file

@ -1,63 +0,0 @@
# Triage new issues/PRs on github
This document shows the steps the Angular team is using to triage issues.
The labels are used later on for planning releases.
## Tips ##
* install [github pr helper extension](https://github.com/petebacondarwin/github-pr-helper) and become 356% more productive
* Label "resolution:*"
* these tags can be used for labeling a closed issue/PR with a reason why it was closed. (we can add reasons as we need them, right there are only a few rejection reasons. it doesn't make sense to label issues that were fixed or prs that were merged)
## Automatic processing ##
We have automatic tools (e.g. Mary Poppins) that automatically add comments / labels to issues and PRs.
The following is done automatically and should not be done manually:
* Label "cla: yes" or "cla: no" for pull requests
## Process ##
1. Open list of [non triaged issues](https://github.com/angular/angular.js/issues?direction=desc&milestone=none&page=1&sort=created&state=open)
1. Assign yourself: Pick an issue that is not assigned to anyone and assign it to you
1. Assign milestone:
* "Docs only" milestone - for documentation PR -> **Done**.
* Current/next milestone - regressions
* 1.2.x - everything else
1. Label "GH: *" (to be automated via Mary Poppins)
* PR - issue is a PR
* issue - otherwise
1. Bugs:
* Label "Type: Bug"
* Label "Type: Regression" - if the bug is a regression
* Duplicate? - Check if there are comments pointing out that this is a dupe, if they do exist verify that this is indeed a dupe and close it and go to the last step
* Reproducible? - Steps to reproduce the bug are clear, if not ask for clarification (ideally plunker or fiddle)
* Reproducible on master? - http://code.angularjs.org/snapshot/
1. Non bugs:
* Label "Type: Feature" or "Type: Chore" or "Type: Perf"
* Label "needs: breaking change" - if needed
* Label "needs: public api" - if a new public api is needed
* Understandable? - verify if the description of the request is clear. if not ask for clarification
* Goals of angular core? - Often new features should be implemented as a third-party module rather than an addition to the core.
1. Label "component: *"
* In rare cases, it's ok to have multiple components.
1. Label "impact: *"
* small - obscure issue affecting one or handful of developers
* medium - impacts some usage patterns
* large - impacts most or all of angular apps
1. Label "complexity: *"
* small - trivial change
* medium - non-trivial but straightforward change
* large - changes to many components in angular or any changes to $compile, ngRepeat or other "fun" components
1. Label "PRs plz!" for "GH: issue"
* if complexity is small or medium and the problem as well as solution are well captured in the issue
1. Label "origin: google" for issues from Google
1. Label "high priority" for security issues, major performance regressions or memory leaks
1. Unassign yourself from the issue
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/TRIAGING.md?pixel)](https://github.com/igrigorik/ga-beacon)

2
angularFiles.js vendored
View file

@ -27,7 +27,6 @@ angularFiles = {
'src/ng/parse.js', 'src/ng/parse.js',
'src/ng/q.js', 'src/ng/q.js',
'src/ng/rootScope.js', 'src/ng/rootScope.js',
'src/ng/sanitizeUri.js',
'src/ng/sce.js', 'src/ng/sce.js',
'src/ng/sniffer.js', 'src/ng/sniffer.js',
'src/ng/timeout.js', 'src/ng/timeout.js',
@ -67,7 +66,6 @@ angularFiles = {
], ],
'angularLoader': [ 'angularLoader': [
'src/minErr.js',
'src/loader.js' 'src/loader.js'
], ],

View file

@ -142,7 +142,6 @@ var writeChangelog = function(stream, commits, version) {
var sections = { var sections = {
fix: {}, fix: {},
feat: {}, feat: {},
perf: {},
breaks: {} breaks: {}
}; };
@ -170,7 +169,6 @@ var writeChangelog = function(stream, commits, version) {
stream.write(util.format(HEADER_TPL, version, version, currentDate())); stream.write(util.format(HEADER_TPL, version, version, currentDate()));
printSection(stream, 'Bug Fixes', sections.fix); printSection(stream, 'Bug Fixes', sections.fix);
printSection(stream, 'Features', sections.feat); printSection(stream, 'Features', sections.feat);
printSection(stream, 'Performance Improvements', sections.perf);
printSection(stream, 'Breaking Changes', sections.breaks, false); printSection(stream, 'Breaking Changes', sections.breaks, false);
} }
@ -188,7 +186,7 @@ var getPreviousTag = function() {
var generate = function(version, file) { var generate = function(version, file) {
getPreviousTag().then(function(tag) { getPreviousTag().then(function(tag) {
console.log('Reading git log since', tag); console.log('Reading git log since', tag);
readGitLog('^fix|^feat|^perf|BREAKING', tag).then(function(commits) { readGitLog('^fix|^feat|BREAKING', tag).then(function(commits) {
console.log('Parsed', commits.length, 'commits'); console.log('Parsed', commits.length, 'commits');
console.log('Generating changelog to', file || 'stdout', '(', version, ')'); console.log('Generating changelog to', file || 'stdout', '(', version, ')');
writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version); writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version);

80
changelog.tmp.md Normal file
View file

@ -0,0 +1,80 @@
<a name="v1.0.0rc3"></a>
# v1.0.0rc3 (2012-03-27)
## Bug Fixes
- **$compile:**
- create new (isolate) scopes for directives on root elements ([5390fb37](https://github.com/angular/angular.js/commit/5390fb37d2c01937922613fc57df4986af521787), closes [#817](https://github.com/angular/angular.js/issues/817))
- don't touch static element attributes ([9cb2195e](https://github.com/angular/angular.js/commit/9cb2195e61a78e99020ec19d687a221ca88b5900))
- Merge interpolated css class when replacing an element ([f49eaf8b](https://github.com/angular/angular.js/commit/f49eaf8bf2df5f4e0e82d6c89e849a4f82c8d414))
- **$http:**
- don't send Content-Type header when no data ([1a5bebd9](https://github.com/angular/angular.js/commit/1a5bebd927ecd22f9c34617642fdf58fe3f62efb), closes [#749](https://github.com/angular/angular.js/issues/749))
- **$log:**
- avoid console.log.apply calls in IE ([15213ec2](https://github.com/angular/angular.js/commit/15213ec212769837cb2b7e781ffc5bfd598d27ca), closes [#805](https://github.com/angular/angular.js/issues/805))
- **$resource:**
- support escaping of ':' in resource url ([6d6f8753](https://github.com/angular/angular.js/commit/6d6f875345e01f2c6c63ef95164f6f39e923da15))
- **compiler:**
- allow transclusion of root elements ([9918b748](https://github.com/angular/angular.js/commit/9918b748be01266eb10db39d51b4d3098d54ab66))
- **e2e runner:**
- fix typo that caused errors on IE8 ([ee5a5352](https://github.com/angular/angular.js/commit/ee5a5352fd4b94cedee6ef20d4bf2d43ce77e00b), closes [#806](https://github.com/angular/angular.js/issues/806))
- **forEach:**
- should ignore prototypically inherited properties ([8d7e6948](https://github.com/angular/angular.js/commit/8d7e6948496ff26ef1da8854ba02fcb8eebfed61), closes [#813](https://github.com/angular/angular.js/issues/813))
- **forms:**
- Remove double registering of form ([1faafa31](https://github.com/angular/angular.js/commit/1faafa31582c4e9413f48dc7d12f5b681f9fe9fd))
- Set ng-valid/ng-invalid correctly ([08bfea18](https://github.com/angular/angular.js/commit/08bfea183a850b29da270eac47f80b598cbe600f))
- **init:**
- use jQuery#ready for init if available ([cb2ad9ab](https://github.com/angular/angular.js/commit/cb2ad9abf24e6f855cc749efe3155bd7987ece9d), closes [#818](https://github.com/angular/angular.js/issues/818))
- **json:**
- added support for iso8061 timezone ([5ac14f63](https://github.com/angular/angular.js/commit/5ac14f633a69f49973b5512780c6ec7752405967))
- **matchers.toHaveClass:**
- Correct reference to angular.mock.dump ([f701ce08](https://github.com/angular/angular.js/commit/f701ce08f9d63be05fc3b92f57ad473e1e749b2d))
- **ng-switch:**
- properly destroy child scopes ([2315d9b3](https://github.com/angular/angular.js/commit/2315d9b3610994b36c44e4a97fb1427d59471ce8))
- **ngDocSpec:**
- fix broken tests ([53b6f522](https://github.com/angular/angular.js/commit/53b6f522a56eea314cbd084816e08f24b2c7879f))
- **ngForm:**
- alias name||ngForm ([823adb23](https://github.com/angular/angular.js/commit/823adb231995e917bc060bfa49453e2a96bac2b6))
- **ngRepeat:**
- correct variable reference in error message ([935c1018](https://github.com/angular/angular.js/commit/935c1018da05dbf3124b2dd33619c4a3c82d7a2a))
- **ngView:**
- controller not published ([21e74c2d](https://github.com/angular/angular.js/commit/21e74c2d2e8e985b23711785287feb59965cbd90))
- **q:**
- resolve all of nothing to nothing ([ac75079e](https://github.com/angular/angular.js/commit/ac75079e2113949d5d64adbcf23d56f3cf295d41))
- **select:**
- multiselect failes to update view on selection insert ([6ecac8e7](https://github.com/angular/angular.js/commit/6ecac8e71a84792a434d21db2c245b3648c55f18))
## Features
- **$compile:**
- do not interpolate boolean attributes, rather evaluate them ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- **$controller:**
- support controller registration via $controllerProvider ([d54dfecb](https://github.com/angular/angular.js/commit/d54dfecb00fba41455536c5ddd55310592fdaf84))
- **$route:**
- when matching consider trailing slash as optional ([a4fe51da](https://github.com/angular/angular.js/commit/a4fe51da3ba0dc297ecd389e230d6664f250c9a6), closes [#784](https://github.com/angular/angular.js/issues/784))
- **assertArgFn:**
- should support array annotated fns ([4b8d9260](https://github.com/angular/angular.js/commit/4b8d926062eb4d4483555bdbdec4656f585ab40b))
- **http:**
- added params parameter ([73c85930](https://github.com/angular/angular.js/commit/73c8593077155a9f2e8ef42efd4c497eba0bef4f))
- **injector:**
- infer _foo_ as foo ([f13dd339](https://github.com/angular/angular.js/commit/f13dd3393dfb7a33565c9360342c193bc0bddcb6))
- **input.radio:**
- Allow value attribute to be interpolated ([ade6c452](https://github.com/angular/angular.js/commit/ade6c452753145c84884d17027a7865bf4b34b0c))
- **jqLite:**
- make injector() and scope() work with the document object ([5fdab52d](https://github.com/angular/angular.js/commit/5fdab52dd7c269f99839f4fa6b5854d9548269fa))
- add .controller() method ([6c5a05ad](https://github.com/angular/angular.js/commit/6c5a05ad49a1e083570c3dfe331403398f899dbe))
- **ngValue:**
- allow radio inputs to have non string values ([09e175f0](https://github.com/angular/angular.js/commit/09e175f02cca0f4a295fd0c9b980cd8f432e722b), closes [#816](https://github.com/angular/angular.js/issues/816))
- **scope:**
- broadcast $destroy event on scope destruction ([9b1aff90](https://github.com/angular/angular.js/commit/9b1aff905b638aa274a5fc8f88662df446d374bd))
- **scope.$eval:**
- Allow passing locals to the expression ([192ff61f](https://github.com/angular/angular.js/commit/192ff61f5d61899e667c6dbce4d3e6e399429d8b))
## Breaking Changes
- boolean attrs are evaluated rather than interpolated ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- ng-bind-attr directive removed ([55027132](https://github.com/angular/angular.js/commit/55027132f3d57e5dcf94683e6e6bd7b0aae0087d))
- any app that depends on this service and its fallback to Modernizr, please ([aaedefb9](https://github.com/angular/angular.js/commit/aaedefb92e6bec6626e173e5155072c91471596a))

17
closure/README.md Normal file
View file

@ -0,0 +1,17 @@
This file contains externs for use with the Closure compiler (aka JSCompiler).
Passing these files to the --externs parameter of a compiler pass allows using
type annotations for AngularJS objects. For example, Angular's $scope objects
can be annotated as:
```js
/** @type {angular.Scope} */
var scope = $scope;
```
This allows JSCompiler to type check accesses to scope, give warnings about
missing methods or incorrect arguments, and also prevents renaming of property
accesses with advanced compilation.
The externs are incomplete and maintained on an as-needed basis, but strive to
be correct. Externs for individual modules should be added in separate files.
See https://developers.google.com/closure/compiler/

1912
closure/angular.js vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -9,3 +9,14 @@
ng\:form { ng\:form {
display: block; display: block;
} }
/* The styles below ensure that the CSS transition will ALWAYS
* animate and close. A nasty bug occurs with CSS transitions where
* when the active class isn't set, or if the active class doesn't
* contain any styles to transition to, then, if ngAnimate is used,
* it will appear as if the webpage is broken due to the forever hanging
* animations. The clip (!ie) and zoom (ie) CSS properties are used
* since they trigger a transition without making the browser
* animate anything and they're both highly underused CSS properties */
.ng-animate-start { clip:rect(0, auto, auto, 0); -ms-zoom:1.0001; }
.ng-animate-active { clip:rect(-1px, auto, auto, 0); -ms-zoom:1; }

View file

@ -5,7 +5,7 @@ describe('Docs Annotations', function() {
var body; var body;
beforeEach(function() { beforeEach(function() {
body = angular.element(document.body); body = angular.element(document.body);
body.empty(); body.html('');
}); });
var normalizeHtml = function(html) { var normalizeHtml = function(html) {

View file

@ -13,7 +13,8 @@ describe("docsSearch", function() {
results[0] = { section: 'tutorial', shortName: 'item one', keywords: 'item, one, 1' }; results[0] = { section: 'tutorial', shortName: 'item one', keywords: 'item, one, 1' };
results[1] = { section: 'tutorial', shortName: 'item man', keywords: 'item, man' }; results[1] = { section: 'tutorial', shortName: 'item man', keywords: 'item, man' };
results[2] = { section: 'api', shortName: 'item other', keywords: 'item, other' }; results[2] = { section: 'api', shortName: 'item other', keywords: 'item, other' };
results[3] = { section: 'api', shortName: 'ngRepeat', keywords: 'item, other' }; results[3] = { section: 'cookbook', shortName: 'item cookbook', keywords: 'item, other' };
results[4] = { section: 'api', shortName: 'ngRepeat', keywords: 'item, other' };
$provide.value('NG_PAGES', results); $provide.value('NG_PAGES', results);
$provide.factory('lunrSearch', function() { $provide.factory('lunrSearch', function() {
@ -40,14 +41,19 @@ describe("docsSearch", function() {
expect(items['api'].length).toBe(2); expect(items['api'].length).toBe(2);
})); }));
it("should place cookbook items in the tutorial", inject(function(docsSearch) {
var items = docsSearch('item');
expect(items['tutorial'].length).toBe(3);
}));
it("should return all results without a search", inject(function(docsSearch) { it("should return all results without a search", inject(function(docsSearch) {
var items = docsSearch(); var items = docsSearch();
expect(items['tutorial'].length).toBe(2); expect(items['tutorial'].length).toBe(3);
expect(items['api'].length).toBe(2); expect(items['api'].length).toBe(2);
})); }));
it("should store values with and without a ng prefix", inject(function(docsSearch) { it("should store values with and without a ng prefix", inject(function(docsSearch) {
expect(interceptedLunrResults[3].title).toBe('ngRepeat repeat'); expect(interceptedLunrResults[4].title).toBe('ngRepeat repeat');
})); }));
}); });

View file

@ -28,7 +28,7 @@ function escape(text) {
function setHtmlIe8SafeWay(element, html) { function setHtmlIe8SafeWay(element, html) {
var newElement = angular.element('<pre>' + html + '</pre>'); var newElement = angular.element('<pre>' + html + '</pre>');
element.empty(); element.html('');
element.append(newElement.contents()); element.append(newElement.contents());
return element; return element;
} }
@ -215,7 +215,17 @@ directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location',
}]; }];
this.html5Mode = angular.noop; this.html5Mode = angular.noop;
}); });
$provide.decorator('$timeout', ['$rootScope', '$delegate', function($rootScope, $delegate) {
return angular.extend(function(fn, delay) {
if (delay && delay > 50) {
return setTimeout(function() {
$rootScope.$apply(fn);
}, delay);
} else {
return $delegate.apply(this, arguments);
}
}, $delegate);
}]);
$provide.decorator('$rootScope', ['$delegate', function($delegate) { $provide.decorator('$rootScope', ['$delegate', function($delegate) {
embedRootScope = $delegate; embedRootScope = $delegate;

View file

@ -193,7 +193,7 @@ directive.table = function() {
return { return {
restrict: 'E', restrict: 'E',
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
if (!attrs['class']) { if (!attrs.class) {
element.addClass('table table-bordered table-striped code-table'); element.addClass('table table-bordered table-striped code-table');
} }
} }

View file

@ -5,8 +5,8 @@
# AngularJS API Docs # AngularJS API Docs
Welcome to the AngularJS API docs page. These pages contain the AngularJS reference materials for version <strong ng-bind="version"></strong>. Welcome to the AngularJS API docs page. These pages contain the AngularJS reference materials for version <strong ng-bind="version"></strong>.
The documentation is organized into **{@link guide/module modules}** which contain various components of an AngularJS application. The documentation is organized into **modules** which contain various components of an AngularJS application.
These components are {@link guide/directive directives}, {@link guide/dev_guide.services services}, {@link guide/filter filters}, {@link guide/providers providers}, {@link guide/templates types}, global APIs and testing mocks. These components are directives, services, filters, providers, types, global APIs and testing mocks.
<div class="alert alert-info"> <div class="alert alert-info">
**Angular Namespaces `$` and `$$`** **Angular Namespaces `$` and `$$`**
@ -63,7 +63,7 @@ This module is provided by default and contains the core components of AngularJS
</td> </td>
<td> <td>
<p> <p>
The core filters available in the ng module are used to transform template data before it is rendered within directives and expressions. The core filters available in the ng module are used to transform template data before it is renders within directives and expressions.
</p> </p>
<p> <p>
Some examples include: Some examples include:

View file

@ -3,9 +3,6 @@
@description @description
# ng (core module) # ng (core module)
The ng module is loaded by default when an AngularJS application is started. The module itself The ng module is loaded by default when an AngularJS application is started. The module itself contains the essential components to for an AngularJS application to function. The table below lists a high level breakdown of each of the services/factories, filters, directives and testing components available within this core module.
contains the essential components for an AngularJS application to function. The table below
lists a high level breakdown of each of the services/factories, filters, directives and testing
components available within this core module.
<div doc-module-components="ng"></div> <div doc-module-components="ng"></div>

View file

@ -0,0 +1,122 @@
@ngdoc overview
@name Cookbook: Advanced Form
@description
Here we extend the basic form example to include common features such as reverting, dirty state
detection, and preventing invalid form submission.
<doc:example>
<doc:source>
<script>
function UserForm($scope) {
var master = {
name: 'John Smith',
address:{
line1: '123 Main St.',
city:'Anytown',
state:'AA',
zip:'12345'
},
contacts:[
{type:'phone', value:'1(234) 555-1212'}
]
};
$scope.state = /^\w\w$/;
$scope.zip = /^\d\d\d\d\d$/;
$scope.cancel = function() {
$scope.form = angular.copy(master);
};
$scope.save = function() {
master = $scope.form;
$scope.cancel();
};
$scope.addContact = function() {
$scope.form.contacts.push({type:'', value:''});
};
$scope.removeContact = function(index) {
$scope.form.contacts.splice(index, 1);
};
$scope.isCancelDisabled = function() {
return angular.equals(master, $scope.form);
};
$scope.isSaveDisabled = function() {
return $scope.myForm.$invalid || angular.equals(master, $scope.form);
};
$scope.cancel();
}
</script>
<div ng-controller="UserForm">
<form name="myForm">
<label>Name:</label><br/>
<input type="text" ng-model="form.name" required/> <br/><br/>
<label>Address:</label> <br/>
<input type="text" ng-model="form.address.line1" size="33" required/> <br/>
<input type="text" ng-model="form.address.city" size="12" required/>,
<input type="text" ng-model="form.address.state" size="2"
ng-pattern="state" required/>
<input type="text" ng-model="form.address.zip" size="5"
ng-pattern="zip" required/><br/><br/>
<label>Contacts:</label>
[ <a href="" ng-click="addContact()">add</a> ]
<div ng-repeat="contact in form.contacts">
<select ng-model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" ng-model="contact.value" required/>
[ <a href="" ng-click="removeContact($index)">X</a> ]
</div>
<button ng-click="cancel()" ng-disabled="isCancelDisabled()">Cancel</button>
<button ng-click="save()" ng-disabled="isSaveDisabled()">Save</button>
</form>
<hr/>
Debug View:
<pre>form={{form}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should enable save button', function() {
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.name').enter('');
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
element(':button:contains(Save)').click();
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
});
it('should enable cancel button', function() {
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
element(':button:contains(Cancel)').click();
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
expect(element(':input[ng\\:model="form.name"]').val()).toEqual('John Smith');
});
</doc:scenario>
</doc:example>
#Things to notice
* Cancel & save buttons are only enabled if the form is dirty — there is something to cancel or
save.
* Save button is only enabled if there are no validation errors on the form.
* Cancel reverts the form changes back to original state.
* Save updates the internal model of the form.
* Debug view shows the two models. One presented to the user form and the other being the pristine
copy master.

View file

@ -0,0 +1,63 @@
@ngdoc overview
@name Cookbook: Resources - Buzz
@description
External resources are URLs that provide JSON data, which are then rendered with the help of
templates. Angular has a resource factory that can be used to give names to the URLs and then
attach behavior to them. For example you can use the
{@link http://code.google.com/apis/buzz/v1/getting_started.html#background-operations| Google Buzz
API}
to retrieve Buzz activity and comments.
<doc:example>
<doc:source>
<script>
BuzzController.$inject = ['$scope', '$resource'];
function BuzzController($scope, $resource) {
$scope.userId = 'googlebuzz';
$scope.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt: 'json', callback: 'JSON_CALLBACK'},
{ get: {method: 'JSONP', params: {visibility: '@self'}},
replies: {method: 'JSONP', params: {visibility: '@self', comments: '@comments'}}
});
$scope.fetch = function() {
$scope.activities = $scope.Activity.get({userId:this.userId});
}
$scope.expandReplies = function(activity) {
activity.replies = $scope.Activity.replies({userId: this.userId, activityId: activity.id});
}
};
</script>
<div ng-controller="BuzzController">
<input ng-model="userId"/>
<button ng-click="fetch()">fetch</button>
<hr/>
<div class="buzz" ng-repeat="item in activities.data.items">
<h1 style="font-size: 15px;">
<img ng-src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a ng-href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
<a href ng-click="expandReplies(item)" style="float: right;">
Expand replies: {{item.links.replies[0].count}}
</a>
</h1>
{{item.object.content | html}}
<div class="reply" ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
<img ng-src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a ng-href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
{{reply.content | html}}
</div>
</div>
</div>
</doc:source>
<doc:scenario>
xit('fetch buzz and expand', function() {
element(':button:contains(fetch)').click();
expect(repeater('div.buzz').count()).toBeGreaterThan(0);
element('.buzz a:contains(Expand replies):first').click();
expect(repeater('div.reply').count()).toBeGreaterThan(0);
});
</doc:scenario>
</doc:example>

View file

@ -0,0 +1,151 @@
@ngdoc overview
@name Cookbook: Deep Linking
@description
Deep linking allows you to encode the state of the application in the URL so that it can be
bookmarked and the application can be restored from the URL to the same state.
While Angular does not force you to deal with bookmarks in any particular way, it has services
which make the common case described here very easy to implement.
# Assumptions
Your application consists of a single HTML page which bootstraps the application. We will refer
to this page as the chrome.
Your application is divided into several screens (or views) which the user can visit. For example,
the home screen, settings screen, details screen, etc. For each of these screens, we would like to
assign a URL so that it can be bookmarked and later restored. Each of these screens will be
associated with a controller which define the screen's behavior. The most common case is that the
screen will be constructed from an HTML snippet, which we will refer to as the partial. Screens can
have multiple partials, but a single partial is the most common construct. This example makes the
partial boundary visible using a blue line.
You can make a routing table which shows which URL maps to which partial view template and which
controller.
# Example
In this example we have a simple app which consist of two screens:
* Welcome: url `welcome` Show the user contact information.
* Settings: url `settings` Show an edit screen for user contact information.
<example module="deepLinking" deps="angular-route.js, angular-sanitize.js">
<file name="script.js">
angular.module('deepLinking', ['ngRoute', 'ngSanitize'])
.config(function($routeProvider) {
$routeProvider.
when("/welcome", {templateUrl:'welcome.html', controller:WelcomeCntl}).
when("/settings", {templateUrl:'settings.html', controller:SettingsCntl});
});
AppCntl.$inject = ['$scope', '$route']
function AppCntl($scope, $route) {
$scope.$route = $route;
// initialize the model to something useful
$scope.person = {
name:'anonymous',
contacts:[{type:'email', url:'anonymous@example.com'}]
};
}
function WelcomeCntl($scope) {
$scope.greet = function() {
alert("Hello " + $scope.person.name);
};
}
function SettingsCntl($scope, $location) {
$scope.cancel = function() {
$scope.form = angular.copy($scope.person);
};
$scope.save = function() {
angular.copy($scope.form, $scope.person);
$location.path('/welcome');
};
$scope.cancel();
}
</file>
<file name="style.css">
[ng-view] {
border: 1px solid blue;
margin: 0;
padding:1em;
}
.partial-info {
background-color: blue;
color: white;
padding: 3px;
}
</file>
<file name="index.html">
<div ng-controller="AppCntl">
<h1>Your App Chrome</h1>
[ <a href="welcome">Welcome</a> | <a href="settings">Settings</a> ]
<hr/>
<span class="partial-info">
Partial: {{$route.current.template}}
</span>
<div ng-view></div>
<small>Your app footer </small>
</div>
</file>
<file name="settings.html">
<label>Name:</label>
<input type="text" ng:model="form.name" required>
<div ng:repeat="contact in form.contacts">
<select ng:model="contact.type">
<option>url</option>
<option>email</option>
<option>phone</option>
</select>
<input type="text" ng:model="contact.url">
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<div>
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
</div>
<button ng:click="cancel()">Cancel</button>
<button ng:click="save()">Save</button>
</file>
<file name="welcome.html">
Hello {{person.name}},
<div>
Your contact information:
<div ng:repeat="contact in person.contacts">{{contact.type}}:
<span ng-bind-html="contact.url|linky"></span>
</div>
</div>
</file>
<file name="scenario.js">
it('should navigate to URL', function() {
element('a:contains(Welcome)').click();
expect(element('[ng-view]').text()).toMatch(/Hello anonymous/);
element('a:contains(Settings)').click();
input('form.name').enter('yourname');
element(':button:contains(Save)').click();
element('a:contains(Welcome)').click();
expect(element('[ng-view]').text()).toMatch(/Hello yourname/);
});
</file>
</example>
# Things to notice
* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
initialization of the {@link api/ngRoute.$route $route} service with the proper URL
routes.
* The {@link api/ngRoute.$route $route} service then watches the URL and instantiates the
appropriate controller when the URL changes.
* The {@link api/ngRoute.directive:ngView ngView} widget loads the
view when the URL changes. It also sets the view scope to the newly instantiated controller.
* Changing the URL is sufficient to change the controller and view. It makes no difference whether
the URL is changed programmatically or by the user.

View file

@ -0,0 +1,114 @@
@ngdoc overview
@name Cookbook: Form
@description
A web application's main purpose is to present and gather data. For this reason Angular strives
to make both of these operations trivial. This example shows off how you can build a simple form to
allow a user to enter data.
<doc:example>
<doc:source>
<script>
function FormController($scope) {
var user = $scope.user = {
name: 'John Smith',
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
contacts:[{type:'phone', value:'1(234) 555-1212'}]
};
$scope.state = /^\w\w$/;
$scope.zip = /^\d\d\d\d\d$/;
$scope.addContact = function() {
user.contacts.push({type:'email', value:''});
};
$scope.removeContact = function(contact) {
for (var i = 0, ii = user.contacts.length; i < ii; i++) {
if (contact === user.contacts[i]) {
$scope.user.contacts.splice(i, 1);
}
}
};
}
</script>
<div ng-controller="FormController" class="example">
<label>Name:</label><br>
<input type="text" ng-model="user.name" required/> <br><br>
<label>Address:</label><br>
<input type="text" ng-model="user.address.line1" size="33" required> <br>
<input type="text" ng-model="user.address.city" size="12" required>,
<input type="text" ng-model="user.address.state"
ng-pattern="state" size="2" required>
<input type="text" ng-model="user.address.zip" size="5"
ng-pattern="zip" required><br><br>
<label>Phone:</label>
[ <a href="" ng-click="addContact()">add</a> ]
<div ng-repeat="contact in user.contacts">
<select ng-model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" ng-model="contact.value" required>
[ <a href="" ng-click="removeContact(contact)">X</a> ]
</div>
<hr/>
Debug View:
<pre>user={{user | json}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should show debug', function() {
expect(binding('user')).toMatch(/John Smith/);
});
it('should add contact', function() {
using('.example').element('a:contains(add)').click();
using('.example div:last').input('contact.value').enter('you@example.org');
expect(binding('user')).toMatch(/\(234\) 555\-1212/);
expect(binding('user')).toMatch(/you@example.org/);
});
it('should remove contact', function() {
using('.example').element('a:contains(X)').click();
expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
});
it('should validate zip', function() {
expect(using('.example').
element(':input[ng\\:model="user.address.zip"]').
prop('className')).not().toMatch(/ng-invalid/);
using('.example').input('user.address.zip').enter('abc');
expect(using('.example').
element(':input[ng\\:model="user.address.zip"]').
prop('className')).toMatch(/ng-invalid/);
});
it('should validate state', function() {
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.not().toMatch(/ng-invalid/);
using('.example').input('user.address.state').enter('XXX');
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.toMatch(/ng-invalid/);
});
</doc:scenario>
</doc:example>
# Things to notice
* The user data model is initialized {@link api/ng.directive:ngController controller} and is
available in the {@link api/ng.$rootScope.Scope scope} with the initial data.
* For debugging purposes we have included a debug view of the model to better understand what
is going on.
* The {@link api/ng.directive:input input directives} simply refer
to the model and are data-bound.
* The inputs validate. (Try leaving them blank or entering non digits in the zip field)
* In your application you can simply read from or write to the model and the form will be updated.
* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
reflected in the view.

View file

@ -0,0 +1,39 @@
@ngdoc overview
@name Cookbook: Hello World
@description
<doc:example>
<doc:source>
<script>
function HelloCntl($scope) {
$scope.name = 'World';
}
</script>
<div ng-controller="HelloCntl">
Your name: <input type="text" ng-model="name"/>
<hr/>
Hello {{name || "World"}}!
</div>
</doc:source>
<doc:scenario>
it('should change the binding when user enters text', function() {
expect(binding('name')).toEqual('World');
input('name').enter('angular');
expect(binding('name')).toEqual('angular');
});
</doc:scenario>
</doc:example>
# Things to notice
Take a look through the source and note:
* The script tag that {@link guide/bootstrap bootstraps} the Angular environment.
* The text {@link api/ng.directive:input input form control} which is
bound to the greeting name text.
* There is no need for listener registration and event firing on change events.
* The implicit presence of the `name` variable which is in the root {@link api/ng.$rootScope.Scope scope}.
* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
* The concept of {@link guide/databinding data binding}, which reflects any
changes to the
input field in the greeting text.

View file

@ -0,0 +1,58 @@
@ngdoc overview
@name Cookbook
@description
Welcome to the Angular cookbook. Here we will show you typical uses of Angular by example.
# Hello World
{@link helloworld Hello World}: The simplest possible application that demonstrates the
classic Hello World!
# Basic Form
{@link form Basic Form}: Displaying forms to the user for editing is the bread and butter
of web applications. Angular makes forms easy through bidirectional data binding.
# Advanced Form
{@link advancedform Advanced Form}: Taking the form example to the next level and
providing advanced features such as dirty detection, form reverting and submit disabling if
validation errors exist.
# Model View Controller
{@link mvc MVC}: Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
to separate the behavior (JavaScript controller) from the presentation (HTML view). This
separation aids in maintainability and testability of your project.
# Multi-page App and Deep Linking
{@link deeplinking Deep Linking}: An AJAX application never navigates away from the
first page it loads. Instead, it changes the DOM of its single page. Eliminating full-page reloads
is what makes AJAX apps responsive, but it creates a problem in that apps with a single URL
prevent you from emailing links to a particular screen within your application.
Deep linking tries to solve this by changing the URL anchor without reloading a page, thus
allowing you to send links to specific screens in your app.
# Services
{@link api/ng Services}: Services are long lived objects in your applications that are
available across controllers. A collection of useful services are pre-bundled with Angular but you
will likely add your own. Services are initialized using dependency injection, which resolves the
order of initialization. This safeguards you from the perils of global state (a common way to
implement long lived objects).
# External Resources
{@link buzz Resources}: Web applications must be able to communicate with the external
services to get and update data. Resources are the abstractions of external URLs which are
specially tailored to Angular data binding.

View file

@ -0,0 +1,128 @@
@ngdoc overview
@name Cookbook: MVC
@description
MVC allows for a clean and testable separation between the behavior (controller) and the view
(HTML template). A Controller is just a JavaScript class which is grafted onto the scope of the
view. This makes it very easy for the controller and the view to share the model.
The model is a set of objects and primitives that are referenced from the Scope ($scope) object.
This makes it very easy to test the controller in isolation since one can simply instantiate the
controller and test without a view, because there is no connection between the controller and the
view.
<doc:example>
<doc:source>
<script>
function TicTacToeCntl($scope, $location) {
$scope.cellStyle= {
'height': '20px',
'width': '20px',
'border': '1px solid black',
'text-align': 'center',
'vertical-align': 'middle',
'cursor': 'pointer'
};
$scope.reset = function() {
$scope.board = [
['', '', ''],
['', '', ''],
['', '', '']
];
$scope.nextMove = 'X';
$scope.winner = '';
setUrl();
};
$scope.dropPiece = function(row, col) {
if (!$scope.winner && !$scope.board[row][col]) {
$scope.board[row][col] = $scope.nextMove;
$scope.nextMove = $scope.nextMove == 'X' ? 'O' : 'X';
setUrl();
}
};
$scope.reset();
$scope.$watch(function() { return $location.search().board;}, readUrl);
function setUrl() {
var rows = [];
angular.forEach($scope.board, function(row) {
rows.push(row.join(','));
});
$location.search({board: rows.join(';') + '/' + $scope.nextMove});
}
function grade() {
var b = $scope.board;
$scope.winner =
row(0) || row(1) || row(2) ||
col(0) || col(1) || col(2) ||
diagonal(-1) || diagonal(1);
function row(row) { return same(b[row][0], b[row][1], b[row][2]);}
function col(col) { return same(b[0][col], b[1][col], b[2][col]);}
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
function same(a, b, c) { return (a==b && b==c) ? a : '';};
}
function readUrl(value) {
if (value) {
value = value.split('/');
$scope.nextMove = value[1];
angular.forEach(value[0].split(';'), function(row, col){
$scope.board[col] = row.split(',');
});
grade();
}
}
}
</script>
<h3>Tic-Tac-Toe</h3>
<div ng-controller="TicTacToeCntl">
Next Player: {{nextMove}}
<div class="winner" ng-show="winner">Player {{winner}} has won!</div>
<table class="board">
<tr ng-repeat="row in board track by $index" style="height:15px;">
<td ng-repeat="cell in row track by $index" ng-style="cellStyle"
ng-click="dropPiece($parent.$index, $index)">{{cell}}</td>
</tr>
</table>
<button ng-click="reset()">reset board</button>
</div>
</doc:source>
<doc:scenario>
it('should play a game', function() {
piece(1, 1);
expect(binding('nextMove')).toEqual('O');
piece(3, 1);
expect(binding('nextMove')).toEqual('X');
piece(1, 2);
piece(3, 2);
piece(1, 3);
expect(element('.winner').text()).toEqual('Player X has won!');
});
function piece(row, col) {
element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
}
</doc:scenario>
</doc:example>
# Things to notice
* The controller is defined in JavaScript and has no reference to the rendering logic.
* The controller is instantiated by Angular and injected into the view.
* The controller can be instantiated in isolation (without a view) and the code will still execute.
This makes it very testable.
* The HTML view is a projection of the model. In the above example, the model is stored in the
board variable.
* All of the controller's properties (such as board and nextMove) are available to the view.
* Changing the model changes the view.
* The view can call any controller function.
* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
hash so the browser's back button will undo game steps. See deep-linking. This example calls {@link
api/ng.$rootScope.Scope#methods_$watch $watch()} to set up a listener that invokes `readUrl()` when needed.

View file

@ -11,7 +11,7 @@ For these reasons binding to event handler attributes (all attributes that start
An example code that would allow XSS vulnerability by evaluating user input in the window context could look like this: An example code that would allow XSS vulnerability by evaluating user input in the window context could look like this:
``` ```
<input ng-model="username"> <input ng-mode="username">
<div onclick="{{username}}">click me</div> <div onclick="{{username}}">click me</div>
``` ```

View file

@ -6,7 +6,7 @@
When a directive is declared with `template` (or `templateUrl`) and `replace` mode on, the template When a directive is declared with `template` (or `templateUrl`) and `replace` mode on, the template
must have exactly one root element. That is, the text of the template property or the content must have exactly one root element. That is, the text of the template property or the content
referenced by the templateUrl must be contained within a single html element. referenced by the templateUrl must be contained within a single html element.
For example, `<p>blah <em>blah</em> blah</p>` instead of simply `blah <em>blah</em> blah`. For example, '<p>blah <em>blah</em> blah</p>' instead of simply 'blah <em>blah</em> blah'.
Otherwise, the replacement operation would result in a single element (the directive) being replaced Otherwise, the replacement operation would result in a single element (the directive) being replaced
with multiple elements or nodes, which is unsupported and not commonly needed in practice. with multiple elements or nodes, which is unsupported and not commonly needed in practice.

View file

@ -7,4 +7,3 @@ This error occurs in browsers that do not support XmlHttpRequest. AngularJS
supports Safari, Chrome, Firefox, Opera, IE8 and higher, and mobile browsers supports Safari, Chrome, Firefox, Opera, IE8 and higher, and mobile browsers
(Android, Chrome Mobile, iOS Safari). To avoid this error, use an officially (Android, Chrome Mobile, iOS Safari). To avoid this error, use an officially
supported browser. supported browser.

View file

@ -4,4 +4,4 @@
@description @description
This error occurs when 'ngPattern' is passed an expression that isn't a regular expression or doesn't have the expected format. This error occurs when 'ngPattern' is passed an expression that isn't a regular expression or doesn't have the expected format.
For more information on valid expression syntax, see 'ngPattern' in {@link api/ng.directive:input input} directive docs. For more information on valid expression syntax, see 'ngPattern' in {@link api/ng.directive:select input} directive docs.

View file

@ -13,7 +13,7 @@ For example the issue can be triggered by this *invalid* code:
<div ng-repeat="value in [4, 4]"></div> <div ng-repeat="value in [4, 4]"></div>
``` ```
To resolve this error either ensure that the items in the collection have unique identity or use the `track by` syntax to specify how to track the association between models and DOM. To resolve this error either ensure that the items in the collection have unique identity of use the `track by` syntax to specify how to track the association between models and DOM.
To resolve the example above can be resolved by using `track by $index`, which will cause the items to be keyed by their position in the array instead of their value: To resolve the example above can be resolved by using `track by $index`, which will cause the items to be keyed by their position in the array instead of their value:

View file

@ -3,9 +3,9 @@
@fullName Orphan ngTransclude Directive @fullName Orphan ngTransclude Directive
@description @description
Occurs when an `ngTransclude` occurs without a transcluded ancestor element. Occurs when an `ngTransclude` occurs without a transcluded ancesstor element.
This error often occurs when you have forgotten to set `transclude: true` in some directive definition, and then used `ngTransclude` in the directive's template. This error often occurs when you have forgotten to set `transclude: true` in some directive definition, and then used `ngTranslude` in the driective's template.
To resolve, either remove the offending `ngTransclude` or check that `transclude: true` is included in the intended directive definition. To resolve, either remove the offending `ngTransclude` or check that `transclude: true` is included in the intended directive definition.

View file

@ -0,0 +1,50 @@
@ngdoc error
@name $parse:isecprv
@fullName Referencing private Field in Expression
@description
Occurs when an Angular expression attempts to access a private field.
Fields with names that begin or end with an underscore are considered
private fields.  Angular expressions are not allowed to reference such
fields on the scope chain.  This only applies to Angular expressions
(e.g. {{ }} interpolation and calls to `$parse` with a string expression
argument) Javascript itself has no such notion.
To resolve this error, use an alternate non-private field if available
or make the field public (by removing any leading and trailing
underscore characters from its name.)
Example expression that would result in this error:
```html
<div>{{user._private_field}}</div>
```
Background:
Though Angular expressions are written and controlled by the developer
and are trusted, they do represent an attack surface due to the
following two factors:
- they typically deal with user input which is generally high risk
- they often don't get the kind of attention and test coverage that
JavaScript code would.
If these expression were evaluated in a context with full trust, an
attacker, though unable to change the expression itself, can feed it
unexpected and dangerous input that could result in a security
breach/exploit.
As such, Angular expressions are evaluated in a limited context.  They
do not have direct access to the global scope, Window, Document, the
Function constructor or "private" properties (names beginning or ending
with an underscore character) on the scope chain.  They should get their
work done via public properties and methods exposed on the scope chain
(keep in mind that this includes controllers as well as they are
published on the scope via the "controller as" syntax.)
As a best practise, only "publish" properties on the scopes and
controllers that must be available to Angular expressions.  All other
members should either be in closures or be "private" by giving them
names with a leading or trailing underscore character.

View file

@ -1,27 +0,0 @@
@ngdoc error
@name $resource:badmember
@fullName Syntax error in param value using @member lookup
@description
Occurs when there is a syntax error when attempting to extract a param
value from the data object.
Here's an example of valid syntax for `params` or `paramsDefault`:
````javascript
{
bar: '@foo.bar'
}
````
The part following the `@`, `foo.bar` in this case, should be a simple
dotted member lookup using only ASCII identifiers. This error occurs
when there is an error in that expression. The following are all syntax
errors
| Value | Error |
|---------|----------------|
| `@` | Empty expression following `@`. |
| `@1.a` | `1` is an invalid javascript identifier. |
| `@.a` | Leading `.` is invalid. |
| `@a[1]` | Only dotted lookups are supported (no index operator) |

View file

@ -21,19 +21,19 @@ Below is a quick example of animations being enabled for `ngShow` and `ngHide`:
<label> <label>
<input type="checkbox" ng-model="checked" style="float:left; margin-right:10px;"> Is Visible... <input type="checkbox" ng-model="checked" style="float:left; margin-right:10px;"> Is Visible...
</label> </label>
<div class="check-element sample-show-hide" ng-show="checked" style="clear:both;"> <div class="check-element animate-show-hide" ng-show="checked" style="clear:both;">
Visible... Visible...
</div> </div>
</div> </div>
</file> </file>
<file name="animations.css"> <file name="animations.css">
.sample-show-hide { .animate-show-hide {
padding:10px; padding:10px;
border:1px solid black; border:1px solid black;
background:white; background:white;
} }
.sample-show-hide.ng-hide-add, .sample-show-hide.ng-hide-remove { .animate-show-hide.ng-hide-add, .animate-show-hide.ng-hide-remove {
-webkit-transition:all linear 0.5s; -webkit-transition:all linear 0.5s;
-moz-transition:all linear 0.5s; -moz-transition:all linear 0.5s;
-o-transition:all linear 0.5s; -o-transition:all linear 0.5s;
@ -41,13 +41,13 @@ Below is a quick example of animations being enabled for `ngShow` and `ngHide`:
display:block!important; display:block!important;
} }
.sample-show-hide.ng-hide-add.ng-hide-add-active, .animate-show-hide.ng-hide-add.ng-hide-add-active,
.sample-show-hide.ng-hide-remove { .animate-show-hide.ng-hide-remove {
opacity:0; opacity:0;
} }
.sample-show-hide.ng-hide-add, .animate-show-hide.ng-hide-add,
.sample-show-hide.ng-hide-remove.ng-hide-remove-active { .animate-show-hide.ng-hide-remove.ng-hide-remove-active {
opacity:1; opacity:1;
} }
</file> </file>
@ -258,7 +258,7 @@ The table below explains in detail which animation events are triggered
| {@link api/ng.directive:ngInclude#usage_animations ngInclude} | enter and leave | | {@link api/ng.directive:ngInclude#usage_animations ngInclude} | enter and leave |
| {@link api/ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave | | {@link api/ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave |
| {@link api/ng.directive:ngIf#usage_animations ngIf} | enter and leave | | {@link api/ng.directive:ngIf#usage_animations ngIf} | enter and leave |
| {@link api/ng.directive:ngClass#usage_animations ngClass or &#123;&#123;class&#125;&#125;} | add and remove | | {@link api/ng.directive:ngShow#usage_animations ngClass or &#123;&#123;class&#125;&#125;} | add and remove |
| {@link api/ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) | | {@link api/ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
For a full breakdown of the steps involved during each animation event, refer to the {@link api/ngAnimate.$animate API docs}. For a full breakdown of the steps involved during each animation event, refer to the {@link api/ngAnimate.$animate API docs}.

View file

@ -190,7 +190,6 @@ This should help give you an idea of what Angular does internally.
<pre> <pre>
var $compile = ...; // injected into your code var $compile = ...; // injected into your code
var scope = ...; var scope = ...;
var parent = ...; // DOM element where the compiled template can be appended
var html = '<div ng-bind="exp"></div>'; var html = '<div ng-bind="exp"></div>';
@ -201,10 +200,7 @@ This should help give you an idea of what Angular does internally.
var linkFn = $compile(template); var linkFn = $compile(template);
// Step 3: link the compiled template with the scope. // Step 3: link the compiled template with the scope.
var element = linkFn(scope); linkFn(scope);
// Step 4: Append to DOM (optional)
parent.appendChild(element);
</pre> </pre>
### The difference between Compile and Link ### The difference between Compile and Link
@ -354,7 +350,7 @@ Creating local properties on widget scope creates two problems:
To solve the issue of lack of isolation, the directive declares a new `isolated` scope. An To solve the issue of lack of isolation, the directive declares a new `isolated` scope. An
isolated scope does not prototypically inherit from the parent scope, and therefore we don't have isolated scope does not prototypically inherit from the child scope, and therefore we don't have
to worry about accidentally clobbering any properties. to worry about accidentally clobbering any properties.
However `isolated` scope creates a new problem: if a transcluded DOM is a child of the widget However `isolated` scope creates a new problem: if a transcluded DOM is a child of the widget

View file

@ -12,7 +12,7 @@ For a more in-depth explanation, have a look at the {@link tutorial/ tutorial}.
|{@link concepts#template Template} | HTML with additional markup | |{@link concepts#template Template} | HTML with additional markup |
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements | |{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|{@link concepts#model Model} | the data that is shown to the user and with which the user interacts | |{@link concepts#model Model} | the data that is shown to the user and with which the user interacts |
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it | |{@link concepts#scope Scope} | context where the model is stored so that directives and expressions can access it |
|{@link concepts#expression Expressions} | access variables and functions from the scope | |{@link concepts#expression Expressions} | access variables and functions from the scope |
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions | |{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|{@link concepts#filter Filter} | formats the value of an expression for display to the user | |{@link concepts#filter Filter} | formats the value of an expression for display to the user |
@ -99,8 +99,8 @@ The concept behind this is <a name="databinding">"{@link databinding two-way dat
# Adding UI logic: Controllers # Adding UI logic: Controllers
Let's add some more logic to the example that allows us to enter and calculate the costs in Let's add some more logic to the example to
different currencies and also pay the invoice. allow to enter and calculate the costs in different currencies and also pay the invoice.
<example module="invoice1"> <example module="invoice1">
<file name="invoice1.js"> <file name="invoice1.js">
@ -184,7 +184,7 @@ The following graphic shows how everything works together after we introduced th
# View independent business logic: Services # View independent business logic: Services
Right now, the `InvoiceController` contains all logic of our example. When the application grows it Right now, the `InvoiceController` contains all logic of our example. When the application grows it
is a good practice to move view independent logic from the controller into a so called is a good practise to move view independent logic from the controller into a so called
<a name="service">"{@link dev_guide.services service}"</a>, so it can be reused by other parts <a name="service">"{@link dev_guide.services service}"</a>, so it can be reused by other parts
of the application as well. Later on, we could also change that service to load the exchange rates of the application as well. Later on, we could also change that service to load the exchange rates
from the web, e.g. by calling the Yahoo Finance API, without changing the controller. from the web, e.g. by calling the Yahoo Finance API, without changing the controller.
@ -270,10 +270,10 @@ When Angular starts, it will use the configuration of the module with the name d
including the configuration of all modules that this module depends on. including the configuration of all modules that this module depends on.
In the example above: In the example above:
The template contains the directive `ng-app="invoice2"`. This tells Angular The template contains the directive `ng-app="invoice"`. This tells Angular
to use the `invoice` module as the main module for the application. to use the `invoice` module as the main module for the application.
The code snippet `angular.module('invoice2', ['finance2'])` specifies that the `invoice2` module depends on the The code snippet `angular.module('invoice', ['finance'])` specifies that the `invoice` module depends on the
`finance2` module. By this, Angular uses the `InvoiceController` as well as the `currencyConverter` service. `finance` module. By this, Angular uses the `InvoiceController` as well as the `currencyConverter` service.
Now that Angular knows of all the parts of the application, it needs to create them. Now that Angular knows of all the parts of the application, it needs to create them.
In the previous section we saw that controllers are created using a factory function. In the previous section we saw that controllers are created using a factory function.

View file

@ -168,7 +168,7 @@ starts with capital letter and ends with "Ctrl" or "Controller".
- Assigning a property to `$scope` creates or updates the model. - Assigning a property to `$scope` creates or updates the model.
- Controller methods can be created through direct assignment to scope (see the `chiliSpicy` method) - Controller methods can be created through direct assignment to scope (see the `chiliSpicy` method)
- The Controller methods and properties are available in the template (for the `<div>` element and - The Controller methods and properties are available in the template (for the `<div>` element and
its children). and its children).
## Spicy Arguments Example ## Spicy Arguments Example
@ -200,7 +200,7 @@ previous example.
Notice that the `SpicyCtrl` Controller now defines just one method called `spicy`, which takes one Notice that the `SpicyCtrl` Controller now defines just one method called `spicy`, which takes one
argument called `spice`. The template then refers to this Controller method and passes in a string argument called `spice`. The template then refers to this Controller method and passes in a string
constant `'chili'` in the binding for the first button and a model property `customSpice` (bound to an constant `'chili'` in the binding for the first button and a model property `spice` (bound to an
input box) in the second button. input box) in the second button.
## Scope Inheritance Example ## Scope Inheritance Example

View file

@ -19,7 +19,7 @@ testable.
To register a service, you must have a module that this service will be part of. Afterwards, you To register a service, you must have a module that this service will be part of. Afterwards, you
can register the service with the module either via the {@link api/angular.Module Module api} or can register the service with the module either via the {@link api/angular.Module Module api} or
by using the {@link api/AUTO.$provide $provide} service in the module configuration by using the {@link api/AUTO.$provide $provide} service in the module configuration
function. The following pseudo-code shows both approaches: function.The following pseudo-code shows both approaches:
Using the angular.Module api: Using the angular.Module api:
<pre> <pre>

View file

@ -53,19 +53,18 @@ function myController(scope, notifyService) {
myController.$inject = ['$scope','notify']; myController.$inject = ['$scope','notify'];
</script> </script>
<div id="simple" ng-controller="myController"> <div ng-controller="myController">
<p>Let's try this simple notify service, injected into the controller...</p> <p>Let's try this simple notify service, injected into the controller...</p>
<input ng-init="message='test'" ng-model="message" > <input ng-init="message='test'" ng-model="message" >
<button ng-click="callNotify(message);">NOTIFY</button> <button ng-click="callNotify(message);">NOTIFY</button>
<p>(you have to click 3 times to see an alert)</p> <p>(you have to click 3 times to see an alert)</p>
</div> </div>
</doc:source> </doc:source>
<doc:protractor> <doc:scenario>
it('should test service', function() { it('should test service', function() {
expect(element(by.id('simple')).element(by.model('message')).getAttribute('value')) expect(element(':input[ng\\:model="message"]').val()).toEqual('test');
.toEqual('test');
}); });
</doc:protractor> </doc:scenario>
</doc:example> </doc:example>
## Implicit Dependency Injection ## Implicit Dependency Injection
@ -96,7 +95,7 @@ function myController($scope, notify) {
}; };
} }
</script> </script>
<div id="implicit" ng-controller="myController"> <div ng-controller="myController">
<p>Let's try the notify service, that is implicitly injected into the controller...</p> <p>Let's try the notify service, that is implicitly injected into the controller...</p>
<input ng-init="message='test'" ng-model="message"> <input ng-init="message='test'" ng-model="message">
<button ng-click="callNotify(message);">NOTIFY</button> <button ng-click="callNotify(message);">NOTIFY</button>

View file

@ -50,18 +50,19 @@ of which depend on other services that are provided by the Angular framework:
* @param {*} message Message to be logged. * @param {*} message Message to be logged.
*/ */
function batchLogModule($provide){ function batchLogModule($provide){
$provide.factory('batchLog', ['$interval', '$log', function($interval, $log) { $provide.factory('batchLog', ['$timeout', '$log', function($timeout, $log) {
var messageQueue = []; var messageQueue = [];
function log() { function log() {
if (messageQueue.length) { if (messageQueue.length) {
$log.log('batchLog messages: ', messageQueue); $log('batchLog messages: ', messageQueue);
messageQueue = []; messageQueue = [];
} }
$timeout(log, 50000);
} }
// start periodic checking // start periodic checking
$interval(log, 50000); log();
return function(message) { return function(message) {
messageQueue.push(message); messageQueue.push(message);
@ -81,13 +82,13 @@ of which depend on other services that are provided by the Angular framework:
}]); }]);
} }
// get the main service to kick off the application // get the main service to kick of the application
angular.injector([batchLogModule]).get('routeTemplateMonitor'); angular.injector([batchLogModule]).get('routeTemplateMonitor');
</pre> </pre>
Things to notice in this example: Things to notice in this example:
* The `batchLog` service depends on the built-in {@link api/ng.$interval $interval} and * The `batchLog` service depends on the built-in {@link api/ng.$timeout $timeout} and
{@link api/ng.$log $log} services, and allows messages to be logged into the {@link api/ng.$log $log} services, and allows messages to be logged into the
`console.log` in batches. `console.log` in batches.
* The `routeTemplateMonitor` service depends on the built-in {@link api/ngRoute.$route * The `routeTemplateMonitor` service depends on the built-in {@link api/ngRoute.$route

View file

@ -13,7 +13,7 @@ Angular sets these CSS classes. It is up to your application to provide useful s
* `ng-binding` * `ng-binding`
- **Usage:** angular applies this class to any element that is attached to a data binding, via `ng-bind` or - **Usage:** angular applies this class to any element that is attached to a data binding, via `ng-bind` or
`{{}}` curly braces, for example. (see {@link guide/databinding databinding} guide) {{}} curly braces, for example. (see {@link guide/databinding databinding} guide)
* `ng-invalid`, `ng-valid` * `ng-invalid`, `ng-valid`
- **Usage:** angular applies this class to an input widget element if that element's input does - **Usage:** angular applies this class to an input widget element if that element's input does

View file

@ -13,44 +13,45 @@ Unit testing as the name implies is about testing individual units of code. Unit
answer questions such as "Did I think about the logic correctly?" or "Does the sort function order the list answer questions such as "Did I think about the logic correctly?" or "Does the sort function order the list
in the right order?" in the right order?"
In order to answer such a question it is very important that we can isolate the unit of code under test. In order to answer such question it is very important that we can isolate the unit of code under test.
That is because when we are testing the sort function we don't want to be forced into creating That is because when we are testing the sort function we don't want to be forced into creating
related pieces such as the DOM elements, or making any XHR calls in getting the data to sort. related pieces such as the DOM elements, or making any XHR calls in getting the data to sort.
While this may seem obvious it can be very difficult to call an individual function on a While
typical project. The reason is that the developers often mix concerns resulting in a this may seem obvious it usually is very difficult to be able to call an individual function on a
piece of code which does everything. It makes an XHR request, it sorts the response data and then it typical project. The reason is that the developers often mix concerns, and they end up with a
piece of code which does everything. It reads the data from XHR, it sorts it and then it
manipulates the DOM. manipulates the DOM.
With Angular we try to make it easy for you to do the right thing, and so we With Angular we try to make it easy for you to do the right thing, and so we
provide dependency injection for your XHR (which you can mock out) and we created abstractions which provide dependency injection for your XHR (which you can mock out) and we created abstraction which
allow you to sort your model without having to resort to manipulating the DOM. So that in the end, allow you to sort your model without having to resort to manipulating the DOM. So that in the end,
it is easy to write a sort function which sorts some data, so that your test can create a data set, it is easy to write a sort function which sorts some data, so that your test can create a data set,
apply the function, and assert that the resulting model is in the correct order. The test does not apply the function, and assert that the resulting model is in the correct order. The test does not
have to wait for the XHR response to arrive, create the right kind of test DOM, nor assert that your have to wait for XHR, or create the right kind of DOM, or assert that your function has mutated the
function has mutated the DOM in the right way. DOM in the right way.
## With great power comes great responsibility ## With great power comes great responsibility
Angular is written with testability in mind, but it still requires that you do the right thing. Angular is written with testability in mind, but it still requires that you
We tried to make the right thing easy, but Angular is not magic. If you don't follow these guidelines do the right thing. We tried to make the right thing easy, but Angular is not magic, which means if
you may very well end up with an untestable application. you don't follow these guidelines you may very well end up with an untestable application.
## Dependency Injection ## Dependency Injection
There are several ways in which you can get a hold of a dependency. You can: There are several ways in which you can get a hold of a dependency:
1. Create it using the `new` operator. 1. You could create it using the `new` operator.
2. Look for it in a well-known place, also known as a global singleton. 2. You could look for it in a well known place, also known as global singleton.
3. Ask a registry (also known as service registry) for it. (But how do you get a hold of 3. You could ask a registry (also known as service registry) for it. (But how do you get a hold of
the registry? Most likely by looking it up in a well known place. See #2.) the registry? Most likely by looking it up in a well known place. See #2)
4. Expect it to be handed to you. 4. You could expect that it be handed to you.
Out of the four options in the list above, only the last one is testable. Let's look at why: Out of the four options in the list above, only the last one is testable. Let's look at why:
### Using the `new` operator ### Using the `new` operator
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new` While there is nothing wrong with the `new` operator fundamentally the issue is that calling a new
on a constructor. This permanently binds the call site to the type. For example, lets say that we try to on a constructor permanently binds the call site to the type. For example lets say that we are
instantiate an `XHR` that will retrieve data from the server. trying to instantiate an `XHR` so that we can get some data from the server.
<pre> <pre>
function MyClass() { function MyClass() {
@ -63,12 +64,12 @@ function MyClass() {
} }
</pre> </pre>
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would The issue becomes that in tests, we would very much like to instantiate a `MockXHR` which would
allow us to return fake data and simulate network failures. By calling `new XHR()` we are allow us to return fake data and simulate network failures. By calling `new XHR()` we are
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey permanently bound to the actual XHR, and there is no good way to replace it. Yes there is monkey
patch, but that is a bad idea for many reasons which are outside the scope of this document. patching. 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: The class above is hard to test since we have to resort to monkey patching:
<pre> <pre>
var oldXHR = XHR; var oldXHR = XHR;
XHR = function MockXHR() {}; XHR = function MockXHR() {};
@ -80,7 +81,7 @@ XHR = oldXHR; // if you forget this bad things will happen
### Global look-up: ### Global look-up:
Another way to approach the problem is to look for the service in a well-known location. Another way to approach the problem is to look for the service in a well known location.
<pre> <pre>
function MyClass() { function MyClass() {
@ -94,14 +95,14 @@ function MyClass() {
} }
</pre> </pre>
While no new dependency instance is created, it is fundamentally the same as `new` in While no new instance of the dependency is being 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 that there is no good way to intercept the call to `global.xhr` for testing purposes, other then
through monkey patching. The basic issue for testing is that a global variable needs to be mutated in through monkey patching. The basic issue for testing is that a global variable needs to be mutated in
order to replace it with call to a mock method. For further explanation of why this is bad see: {@link order to replace it with call to a mock method. For further explanation why this is bad see: {@link
http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/ Brittle Global http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/ Brittle Global
State & Singletons} State & Singletons}
The class above is hard to test since we have to change the global state: The class above is hard to test since we have to change global state:
<pre> <pre>
var oldXHR = global.xhr; var oldXHR = global.xhr;
global.xhr = function mockXHR() {}; global.xhr = function mockXHR() {};
@ -114,7 +115,7 @@ global.xhr = oldXHR; // if you forget this bad things will happen
### Service Registry: ### Service Registry:
It may seem that this can be solved by having a registry of all the services and then It may seem as that this can be solved by having a registry for all of the services, and then
having the tests replace the services as needed. having the tests replace the services as needed.
<pre> <pre>
@ -130,12 +131,12 @@ function MyClass() {
} }
</pre> </pre>
However, where does the serviceRegistry come from? If it is: However, where does the serviceRegistry come from? if it is:
* `new`-ed up, the test has no chance to reset the services for testing. * `new`-ed up, the test has no chance to reset the services for testing
* a global look-up then the service returned is global as well (but resetting is easier, since * global look-up, then the service returned is global as well (but resetting is easier, since
only one global variable exists to be reset). there is only one global variable to be reset).
The class above is hard to test since we have to change the global state: The class above is hard to test since we have to change global state:
<pre> <pre>
var oldServiceLocator = global.serviceLocator; var oldServiceLocator = global.serviceLocator;
global.serviceLocator.set('xhr', function mockXHR() {}); global.serviceLocator.set('xhr', function mockXHR() {});
@ -147,7 +148,7 @@ global.serviceLocator = oldServiceLocator; // if you forget this bad things will
### Passing in Dependencies: ### Passing in Dependencies:
Last, the dependency can be passed in. Lastly the dependency can be passed in.
<pre> <pre>
function MyClass(xhr) { function MyClass(xhr) {
@ -160,12 +161,12 @@ function MyClass(xhr) {
} }
</pre> </pre>
This is the preferred method since the code makes no assumptions about the origin of `xhr` and cares This is the preferred way since the code makes no assumptions as to where the `xhr` comes from,
instead about whoever created the class responsible for passing it in. Since the creator of the rather that whoever created the class was responsible for passing it in. Since the creator of the
class should be different code than the user of the class, it separates the responsibility of class should be different code than the user of the class, it separates the responsibility of
creation from the logic. This is dependency-injection in a nutshell. creation from the logic, and that is what dependency-injection is in a nutshell.
The class above is testable, since in the test we can write: The class above is very testable, since in the test we can write:
<pre> <pre>
function xhrMock(args) {...} function xhrMock(args) {...}
var myClass = new MyClass(xhrMock); var myClass = new MyClass(xhrMock);
@ -175,12 +176,12 @@ myClass.doWork();
Notice that no global variables were harmed in the writing of this test. Notice that no global variables were harmed in the writing of this test.
Angular comes with {@link di dependency injection} built-in, making the right thing Angular comes with {@link di dependency injection} built in which makes the right thing
easy to do, but you still need to do it if you wish to take advantage of the testability story. easy to do, but you still need to do it if you wish to take advantage of the testability story.
## Controllers ## Controllers
What makes each application unique is its logic, and the logic is what we would like to test. If the logic What makes each application unique is its logic, which is what we would like to test. If the logic
for your application contains DOM manipulation, it will be hard to test. See the example for your application is mixed in with DOM manipulation, it will be hard to test as in the example
below: below:
<pre> <pre>
@ -208,7 +209,7 @@ function PasswordCtrl() {
} }
</pre> </pre>
The code above is problematic from a testability point of view since it requires your test to have the right kind 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: of DOM present when the code executes. The test would look like this:
<pre> <pre>
@ -222,11 +223,11 @@ var pc = new PasswordCtrl();
input.val('abc'); input.val('abc');
pc.grade(); pc.grade();
expect(span.text()).toEqual('weak'); expect(span.text()).toEqual('weak');
$('body').empty(); $('body').html('');
</pre> </pre>
In angular the controllers are strictly separated from the DOM manipulation logic and this results in In angular the controllers are strictly separated from the DOM manipulation logic which results in
a much easier testability story as the following example shows: a much easier testability story as can be seen in this example:
<pre> <pre>
function PasswordCtrl($scope) { function PasswordCtrl($scope) {
@ -244,7 +245,7 @@ function PasswordCtrl($scope) {
} }
</pre> </pre>
and the test is straight forward: and the test is straight forward
<pre> <pre>
var $scope = {}; var $scope = {};
@ -254,11 +255,11 @@ $scope.grade();
expect($scope.strength).toEqual('weak'); expect($scope.strength).toEqual('weak');
</pre> </pre>
Notice that the test is not only much shorter, it is also easier to follow what is happening. We say Notice that the test is not only much shorter but it is easier to follow what is going on. We say
that such a test tells a story, rather then asserting random bits which don't seem to be related. that such a test tells a story, rather then asserting random bits which don't seem to be related.
## Filters ## Filters
{@link api/ng.$filterProvider Filters} are functions which transform the data into a user readable {@link api/ng.$filterProvider Filters} are functions which transform the data into user readable
format. They are important because they remove the formatting responsibility from the application format. They are important because they remove the formatting responsibility from the application
logic, further simplifying the application logic. logic, further simplifying the application logic.
@ -281,7 +282,7 @@ you create with directives may be used throughout your application and in many d
### Simple HTML Element Directive ### Simple HTML Element Directive
Let's start with an angular app with no dependencies. Lets start with an angular app with no dependencies.
<pre> <pre>
var app = angular.module('myApp', []); var app = angular.module('myApp', []);

View file

@ -146,7 +146,7 @@ of service names to inject.
var MyController = function(renamed$scope, renamedGreeter) { var MyController = function(renamed$scope, renamedGreeter) {
... ...
} }
MyController['$inject'] = ['$scope', 'greeter']; MyController.$inject = ['$scope', 'greeter'];
</pre> </pre>
In this scenario the ordering of the values in the '$inject' array must match the ordering of the arguments to inject. In this scenario the ordering of the values in the '$inject' array must match the ordering of the arguments to inject.

View file

@ -56,9 +56,9 @@ The following also **matches** `ngModel`:
Angular **normalizes** an element's tag and attribute name to determine which elements match which Angular **normalizes** an element's tag and attribute name to determine which elements match which
directives. We typically refer to directives by their case-sensitive directives. We typically refer to directives by their case-sensitive
{@link http://en.wikipedia.org/wiki/CamelCase camelCase} **normalized** name (e.g. `ngModel`). {@link http://en.wikipedia.org/wiki/CamelCase camelCase} **normalized** name (e.g. `ngModel`).
However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case However, in the DOM, we refer to directives by case-insensitive forms, typically using
forms, typically using {@link http://en.wikipedia.org/wiki/Letter_case#Computers dash-delimited} {@link http://en.wikipedia.org/wiki/Letter_case#Computers dash-delimited} attributes on DOM elements
attributes on DOM elements (e.g. `ng-model`). (e.g. `ng-model`).
The **normalization** process is as follows: The **normalization** process is as follows:
@ -84,26 +84,24 @@ Here are some equivalent examples of elements that match `ngBind`:
<span x-ng-bind="name"></span> <br/> <span x-ng-bind="name"></span> <br/>
</div> </div>
</file> </file>
<file name="protractorTest.js"> <file name="scenario.js">
it('should show off bindings', function() { it('should show off bindings', function() {
expect(element(by.css('div[ng-controller="Ctrl1"] span[ng-bind]')).getText()) expect(element('div[ng-controller="Ctrl1"] span[ng-bind]').text())
.toBe('Max Karl Ernst Ludwig Planck (April 23, 1858 October 4, 1947)'); .toBe('Max Karl Ernst Ludwig Planck (April 23, 1858 October 4, 1947)');
}); });
</file> </file>
</example> </example>
<div class="alert alert-success"> <div class="alert alert-success">
**Best Practice:** Prefer using the dash-delimited format (e.g. `ng-bind` for `ngBind`). **Best Practice:** Prefer using the dash-delimited format (e.g. `ng-bind` for `ngBind`).
If you want to use an HTML validating tool, you can instead use the `data`-prefixed version (e.g. If you want to use an HTML validating tool, you can instead use the `data`-prefixed version (e.g. `data-ng-bind` for `ngBind`).
`data-ng-bind` for `ngBind`).
The other forms shown above are accepted for legacy reasons but we advise you to avoid them. The other forms shown above are accepted for legacy reasons but we advise you to avoid them.
</div> </div>
`$compile` can match directives based on element names, attributes, class names, as well as comments. `$compile` can match directives based on element names, attributes, class names, as well as comments.
All of the Angular-provided directives match attribute name, tag name, comments, or class name. All of the Angular-provided directives match attribute name, tag name, comments, or class name.
The following demonstrates the various ways a directive (`myDir` in this case) can be referenced The following demonstrates the various ways a directive (`myDir` in this case) can be referenced from within a template:
from within a template:
```html ```html
<my-dir></my-dir> <my-dir></my-dir>
@ -118,22 +116,20 @@ Doing so generally makes it easier to determine what directives a given element
</div> </div>
<div class="alert alert-success"> <div class="alert alert-success">
**Best Practice:** Comment directives were commonly used in places where the DOM API limits the **Best Practice:** Comment directives were commonly used in places where the DOM API limits (e.g. inside `<table>` elements)
ability to create directives that spanned multiple elements (e.g. inside `<table>` elements). to create directives that spanned multiple elements. AngularJS 1.2 introduces
AngularJS 1.2 introduces {@link api/ng.directive:ngRepeat `ng-repeat-start` and `ng-repeat-end`} {@link api/ng.directive:ngRepeat `ng-repeat-start` and `ng-repeat-end`} as a better solution to this problem.
as a better solution to this problem. Developers are encouraged to use this over custom comment Developers are encouraged to use this over custom comment directives when possible.
directives when possible.
</div> </div>
### Text and attribute bindings ### Text and attribute bindings
During the compilation process the {@link api/ng.$compile compiler} matches text and attributes During the compilation process the {@link api/ng.$compile compiler} matches text and attributes using the
using the {@link api/ng.$interpolate $interpolate} service to see if they contain embedded {@link api/ng.$interpolate $interpolate} service to see if they contain embedded expressions. These expressions
expressions. These expressions are registered as {@link api/ng.$rootScope.Scope#methods_$watch watches} are registered as {@link api/ng.$rootScope.Scope#methods_$watch watches} and will update as part of normal {@link
and will update as part of normal {@link api/ng.$rootScope.Scope#methods_$digest digest} cycle. An api/ng.$rootScope.Scope#methods_$digest digest} cycle. An example of interpolation is shown below:
example of interpolation is shown below:
```html ```html
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a> <a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
@ -153,8 +149,8 @@ For example, considering this template:
``` ```
We would expect Angular to be able to bind to this, but when we check the console we see We would expect Angular to be able to bind to this, but when we check the console we see
something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's restrictions,
restrictions, you cannot simply write `cx="{{cx}}"`. you cannot simply write `cx="{{cx}}"`.
With `ng-attr-cx` you can work around this problem. With `ng-attr-cx` you can work around this problem.
@ -174,19 +170,18 @@ For example, we could fix the example above by instead writing:
## Creating Directives ## Creating Directives
First let's talk about the {@link api/ng.$compileProvider#methods_directive API for registering directives}. Much like First let's talk about the API for registering directives. Much like controllers, directives are registered on
controllers, directives are registered on modules. To register a directive, you use the modules. To register a directive, you use the `module.directive` API. `module.directive` takes the
`module.directive` API. `module.directive` takes the {@link guide/directive#creating-custom-directives_matching-directives normalized} directive name followed
{@link guide/directive#creating-custom-directives_matching-directives normalized} directive name by a **factory function.** This factory function should return
followed by a **factory function.** This factory function should return an object with the different an object with the different options to tell `$compile` how the directive should behave when matched.
options to tell `$compile` how the directive should behave when matched.
The factory function is invoked only once when the The factory function is invoked only once when the
{@link api/ng.$compile compiler} matches the directive for the first time. You can perform any {@link api/ng.$compile compiler} matches the directive for the first time. You can
initialization work here. The function is invoked using perform any initialization work here. The function is invoked using {@link
{@link api/AUTO.$injector#methods_invoke $injector.invoke} which makes it injectable just like a api/AUTO.$injector#methods_invoke $injector.invoke} which
controller. makes it injectable just like a controller.
<div class="alert alert-success"> <div class="alert alert-success">
**Best Practice:** Prefer using the definition object over returning a function. **Best Practice:** Prefer using the definition object over returning a function.
@ -209,9 +204,9 @@ For the following examples, we'll use the prefix `my` (e.g. `myCustomer`).
### Template-expanding directive ### Template-expanding directive
Let's say you have a chunk of your template that represents a customer's information. This template Let's say you have a chunk of your template that represents a customer's information. This template is repeated
is repeated many times in your code. When you change it in one place, you have to change it in many times in your code. When you change it in one place, you have to change it in several others. This is a
several others. This is a good opportunity to use a directive to simplify your template. good opportunity to use a directive to simplify your template.
Let's create a directive that simply replaces its contents with a static template: Let's create a directive that simply replaces its contents with a static template:
@ -237,22 +232,21 @@ Let's create a directive that simply replaces its contents with a static templat
</file> </file>
</example> </example>
Notice that we have bindings in this directive. After `$compile` compiles and links Notice that we have bindings in this directive. After `$compile` compiles and links `<div my-customer></div>`,
`<div my-customer></div>`, it will try to match directives on the element's children. This means you it will try to match directives on the element's children. This means you can compose directives of other directives.
can compose directives of other directives. We'll see how to do that in We'll see how to do that in {@link
{@link guide/directive#creating-custom-directives_demo_creating-directives-that-communicate an example} guide/directive#creating-custom-directives_demo_creating-directives-that-communicate an example} below.
below.
In the example above we in-lined the value of the `template` option, but this will become annoying In the example above we in-lined the value of the `template` option, but this will become annoying as the size
as the size of your template grows. of your template grows.
<div class="alert alert-success"> <div class="alert alert-success">
**Best Practice:** Unless your template is very small, it's typically better to break it apart into **Best Practice:** Unless your template is very small, it's typically better to break it apart into its own
its own HTML file and load it with the `templateUrl` option. HTML file and load it with the `templateUrl` option.
</div> </div>
If you are familiar with `ngInclude`, `templateUrl` works just like it. Here's the same example If you are familiar with `ngInclude`, `templateUrl` works just like it. Here's the same example using `templateUrl`
using `templateUrl` instead: instead:
<example module="docsTemplateUrlDirective"> <example module="docsTemplateUrlDirective">
<file name="script.js"> <file name="script.js">
@ -280,11 +274,11 @@ using `templateUrl` instead:
</example> </example>
Great! But what if we wanted to have our directive match the tag name `<my-customer>` instead? Great! But what if we wanted to have our directive match the tag name `<my-customer>` instead?
If we simply put a `<my-customer>` element into the HTML, it doesn't work. If we simply put a `<my-customer>` element into the HMTL, it doesn't work.
<div class="alert alert-waring"> <div class="alert alert-waring">
**Note:** When you create a directive, it is restricted to attribute only by default. In order to **Note:** When you create a directive, it is restricted to attribute only by default. In order to create
create directives that are triggered by element name, you need to use the `restrict` option. directives that are triggered by element name, you need to use the `restrict` option.
</div> </div>
The `restrict` option is typically set to: The `restrict` option is typically set to:
@ -323,33 +317,28 @@ Let's change our directive to use `restrict: 'E'`:
</file> </file>
</example> </example>
For more on the For more on the {@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object
{@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object `restrict`} `restrict`, see the API docs}.
property, see the
{@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object API docs}.
<div class="alert alert-info"> <div class="alert alert-info">
**When should I use an attribute versus an element?** **When should I use an attribute versus an element?**
Use an element when you are creating a component that is in control of the template. The common case Use an element when you are creating a component that is in control of the template. The common case for this
for this is when you are creating a Domain-Specific Language for parts of your template. is when you are creating a Domain-Specific Language for parts of your template.
Use an attribute when you are decorating an existing element with new functionality. Use an attribute when you are decorating an existing element with new functionality.
</div> </div>
Using an element for the `myCustomer` directive is clearly the right choice because you're not Using an element for the `myCustomer` directive is clearly the right choice because you're not decorating an element
decorating an element with some "customer" behavior; you're defining the core behavior of the with some "customer" behavior; you're defining the core behavior of the element as a customer component.
element as a customer component.
### Isolating the Scope of a Directive ### Isolating the Scope of a Directive
Our `myCustomer` directive above is great, but it has a fatal flaw. We can only use it once within a Our `myCustomer` directive above is great, but it has a fatal flaw. We can only use it once within a given scope.
given scope.
In its current implementation, we'd need to create a different controller each time In order to In its current implementation, we'd need to create a different controller each time In order to re-use such a directive:
re-use such a directive:
<example module="docsScopeProblemExample"> <example module="docsScopeProblemExample">
<file name="script.js"> <file name="script.js">
@ -390,8 +379,8 @@ re-use such a directive:
This is clearly not a great solution. This is clearly not a great solution.
What we want to be able to do is separate the scope inside a directive from the scope What we want to be able to do is separate the scope inside a directive from the scope
outside, and then map the outer scope to a directive's inner scope. We can do this by creating what outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an
we call an **isolate scope**. To do this, we can use a directive's `scope` option: **isolate scope**. To do this, we can use a directive's `scope` option:
<example module="docsIsolateScopeDirective"> <example module="docsIsolateScopeDirective">
<file name="script.js"> <file name="script.js">
@ -404,67 +393,62 @@ we call an **isolate scope**. To do this, we can use a directive's `scope` optio
return { return {
restrict: 'E', restrict: 'E',
scope: { scope: {
customerInfo: '=info' customer: '=customer'
}, },
templateUrl: 'my-customer-iso.html' templateUrl: 'my-customer.html'
}; };
}); });
</file> </file>
<file name="index.html"> <file name="index.html">
<div ng-controller="Ctrl"> <div ng-controller="Ctrl">
<my-customer info="naomi"></my-customer> <my-customer customer="naomi"></my-customer>
<hr> <hr>
<my-customer info="igor"></my-customer> <my-customer customer="igor"></my-customer>
</div> </div>
</file> </file>
<file name="my-customer-iso.html"> <file name="my-customer.html">
Name: {{customerInfo.name}} Address: {{customerInfo.address}} Name: {{customer.name}} Address: {{customer.address}}
</file> </file>
</example> </example>
Looking at `index.html`, the first `<my-customer>` element binds the `info` attribute to `naomi`, Looking at `index.html`, the first `<my-customer>` element binds the inner scope's `customer` to `naomi`,
which we have exposed on our controller's scope. The second binds `info` to `igor`. which we have exposed on our controller's scope. The second binds `customer` to `igor`.
Let's take a closer look at the scope option: Let's take a closer look at the scope option:
```javascript ```javascript
//... //...
scope: { scope: {
customerInfo: '=info' customer: '=customer'
}, },
//... //...
``` ```
The **scope option** is an object that contains a property for each isolate scope binding. In this The property name (`customer`) corresponds to the variable name of the `myCustomer` directive's isolated scope.
case it has just one property: The value of the property (`=customer`) tells `$compile` to bind to the `customer` attribute.
- Its name (`customerInfo`) corresponds to the
directive's **isolate scope** property `customerInfo`.
- Its value (`=info`) tells `$compile` to bind to the `info` attribute.
<div class="alert alert-warning"> <div class="alert alert-warning">
**Note:** These `=attr` attributes in the `scope` option of directives are normalized just like **Note:** These `=attr` attributes in the `scope` option of directives are normalized just like directive names.
directive names. To bind to the attribute in `<div bind-to-this="thing">`, you'd specify a binding To bind to the attribute in `<div bind-to-this="thing">`, you'd specify a binding of `=bindToThis`.
of `=bindToThis`.
</div> </div>
For cases where the attribute name is the same as the value you want to bind to inside the For cases where the attribute name is the same as the value you want to bind to inside
directive's scope, you can use this shorthand syntax: the directive's scope, you can use this shorthand syntax:
```javascript ```javascript
... //...
scope: { scope: {
// same as '=customer' // same as '=customer'
customer: '=' customer: '='
}, },
... //...
``` ```
Besides making it possible to bind different data to the scope inside a directive, using an isolated Besides making it possible to bind different data to the scope inside a directive, using an isolated scope has another
scope has another effect. effect.
We can show this by adding another property, `vojta`, to our scope and trying to access it from We can show this by adding another property, `vojta`, to our scope and trying to access it
within our directive's template: from within our directive's template:
<example module="docsIsolationExample"> <example module="docsIsolationExample">
<file name="script.js"> <file name="script.js">
@ -478,7 +462,7 @@ within our directive's template:
return { return {
restrict: 'E', restrict: 'E',
scope: { scope: {
customerInfo: '=info' customer: '=customer'
}, },
templateUrl: 'my-customer-plus-vojta.html' templateUrl: 'my-customer-plus-vojta.html'
}; };
@ -486,11 +470,11 @@ within our directive's template:
</file> </file>
<file name="index.html"> <file name="index.html">
<div ng-controller="Ctrl"> <div ng-controller="Ctrl">
<my-customer info="naomi"></my-customer> <my-customer customer="naomi"></my-customer>
</div> </div>
</file> </file>
<file name="my-customer-plus-vojta.html"> <file name="my-customer-plus-vojta.html">
Name: {{customerInfo.name}} Address: {{customerInfo.address}} Name: {{customer.name}} Address: {{customer.address}}
<hr> <hr>
Name: {{vojta.name}} Address: {{vojta.address}} Name: {{vojta.name}} Address: {{vojta.address}}
</file> </file>
@ -506,8 +490,6 @@ that you explicitly pass in.
<div class="alert alert-warning"> <div class="alert alert-warning">
**Note:** Normally, a scope prototypically inherits from its parent. An isolated scope does not. **Note:** Normally, a scope prototypically inherits from its parent. An isolated scope does not.
See the {@link guide/directive#creating-custom-directives_demo_isolating-the-scope-of-a-directive
"Isolating the Scope of a Directive"} section for more information about isolate scopes.
</div> </div>
<div class="alert alert-success"> <div class="alert alert-success">
@ -522,19 +504,15 @@ In this example we will build a directive that displays the current time.
Once a second, it updates the DOM to reflect the current time. Once a second, it updates the DOM to reflect the current time.
Directives that want to modify the DOM typically use the `link` option. Directives that want to modify the DOM typically use the `link` option.
`link` takes a function with the following signature, `function link(scope, element, attrs) { ... }` `link` takes a function with the following signature, `function link(scope, element, attrs) { ... }` where:
where:
* `scope` is an Angular scope object. * `scope` is an Angular scope object.
* `element` is the jqLite-wrapped element that this directive matches. * `element` is the jqLite-wrapped element that this directive matches.
* `attrs` is a hash object with key-value pairs of normalized attribute names and their * `attrs` is an object with the normalized attribute names and their corresponding values.
corresponding attribute values.
In our `link` function, we want to update the displayed time once a second, or whenever a user In our `link` function, we want to update the displayed time once a second, or whenever a user
changes the time formatting string that our directive binds to. We will use the `$interval` service changes the time formatting string that our directive binds to. We also want to remove the timeout
to call a handler on a regular basis. This is easier than using `$timeout` but also works better with if the directive is deleted so we don't introduce a memory leak.
end 2 end testing, where we want to ensure that all $timeouts have completed before completing the test.
We also want to remove the `$interval` if the directive is deleted so we don't introduce a memory leak.
<example module="docsTimeDirective"> <example module="docsTimeDirective">
<file name="script.js"> <file name="script.js">
@ -542,7 +520,7 @@ We also want to remove the `$interval` if the directive is deleted so we don't i
.controller('Ctrl2', function($scope) { .controller('Ctrl2', function($scope) {
$scope.format = 'M/d/yy h:mm:ss a'; $scope.format = 'M/d/yy h:mm:ss a';
}) })
.directive('myCurrentTime', function($interval, dateFilter) { .directive('myCurrentTime', function($timeout, dateFilter) {
function link(scope, element, attrs) { function link(scope, element, attrs) {
var format, var format,
@ -557,14 +535,20 @@ We also want to remove the `$interval` if the directive is deleted so we don't i
updateTime(); updateTime();
}); });
function scheduleUpdate() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
scheduleUpdate(); // schedule the next update
}, 1000);
}
element.on('$destroy', function() { element.on('$destroy', function() {
$interval.cancel(timeoutId); $timeout.cancel(timeoutId);
}); });
// start the UI update process; save the timeoutId for canceling // start the UI update process.
timeoutId = $interval(function() { scheduleUpdate();
updateTime(); // update DOM
}, 1000);
} }
return { return {
@ -581,9 +565,8 @@ We also want to remove the `$interval` if the directive is deleted so we don't i
</example> </example>
There are a couple of things to note here. There are a couple of things to note here.
Just like the `module.controller` API, the function argument in `module.directive` is dependency Just like the `module.controller` API, the function argument in `module.directive` is dependency injected.
injected. Because of this, we can use `$interval` and `dateFilter` inside our directive's `link` Because of this, we can use `$timeout` and `dateFilter` inside our directive's `link` function.
function.
We register an event `element.on('$destroy', ...)`. What fires this `$destroy` event? We register an event `element.on('$destroy', ...)`. What fires this `$destroy` event?
@ -597,9 +580,8 @@ but if you registered a listener on a service, or registered a listener on a DOM
being deleted, you'll have to clean it up yourself or you risk introducing a memory leak. being deleted, you'll have to clean it up yourself or you risk introducing a memory leak.
<div class="alert alert-success"> <div class="alert alert-success">
**Best Practice:** Directives should clean up after themselves. You can use **Best Practice:** Directives should clean up after themselves. You can use `element.on('$destroy', ...)`
`element.on('$destroy', ...)` or `scope.$on('$destroy', ...)` to run a clean-up function when the or `scope.$on('$destroy', ...)` to run a clean-up function when the directive is removed.
directive is removed.
</div> </div>
@ -637,11 +619,11 @@ To do this, we need to use the `transclude` option.
</file> </file>
</example> </example>
What does this `transclude` option do, exactly? `transclude` makes the contents of a directive with What does this `transclude` option do, exactly? `transclude` makes the contents of a directive with this
this option have access to the scope **outside** of the directive rather than inside. option have access to the scope **outside** of the directive rather than inside.
To illustrate this, see the example below. Notice that we've added a `link` function in `script.js` To illustrate this, see the example below. Notice that we've added a `link` function in `script.js` that
that redefines `name` as `Jeff`. What do you think the `{{name}}` binding will resolve to now? redefines `name` as `Jeff`. What do you think the `{{name}}` binding will resolve to now?
<example module="docsTransclusionExample"> <example module="docsTransclusionExample">
<file name="script.js"> <file name="script.js">
@ -687,12 +669,11 @@ pass in each model you wanted to use separately. If you have to pass in each mod
use, then you can't really have arbitrary contents, can you? use, then you can't really have arbitrary contents, can you?
<div class="alert alert-success"> <div class="alert alert-success">
**Best Practice:** only use `transclude: true` when you want to create a directive that wraps **Best Practice:** only use `transclude: true` when you want to create a directive that wraps arbitrary content.
arbitrary content.
</div> </div>
Next, we want to add buttons to this dialog box, and allow someone using the directive to bind their Next, we want to add buttons to this dialog box, and allow someone using the directive to bind their own
own behavior to it. behavior to it.
<example module="docsIsoFnBindExample"> <example module="docsIsoFnBindExample">
<file name="script.js"> <file name="script.js">
@ -719,7 +700,7 @@ own behavior to it.
</file> </file>
<file name="index.html"> <file name="index.html">
<div ng-controller="Ctrl"> <div ng-controller="Ctrl">
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()"> <my-dialog ng-hide="dialogIsHidden" on-close="dialogIsHidden = true">
Check out the contents, {{name}}! Check out the contents, {{name}}!
</my-dialog> </my-dialog>
</div> </div>
@ -735,13 +716,13 @@ own behavior to it.
We want to run the function we pass by invoking it from the directive's scope, but have it run We want to run the function we pass by invoking it from the directive's scope, but have it run
in the context of the scope where its registered. in the context of the scope where its registered.
We saw earlier how to use `=attr` in the `scope` option, but in the above example, we're using We saw earlier how to use `=prop` in the `scope` option, but in the above example, we're using
`&attr` instead. `&` bindings expose a function to an isolated scope allowing the isolated scope `&prop` instead. `&` bindings expose a function to an isolated scope allowing the isolated scope
to invoke it, but maintaining the original scope of the function. So when a user clicks the to invoke it, but maintaining the original scope of the function. So when a user clicks the
`x` in the dialog, it runs `Ctrl`'s `hideDialog` function. `x` in the dialog, it runs `Ctrl`'s `close` function.
<div class="alert alert-success"> <div class="alert alert-success">
**Best Practice:** use `&attr` in the `scope` option when you want your directive **Best Practice:** use `&prop` in the `scope` option when you want your directive
to expose an API for binding to behaviors. to expose an API for binding to behaviors.
</div> </div>
@ -912,6 +893,5 @@ point for creating your own directives.
You might also be interested in an in-depth explanation of the compilation process that's You might also be interested in an in-depth explanation of the compilation process that's
available in the {@link guide/compiler compiler guide}. available in the {@link guide/compiler compiler guide}.
The {@link api/ng.$compile `$compile` API} page has a comprehensive list of directive options for The {@link api/ng.$compile `$compile` API} page has a comprehensive list of directive options for reference.
reference.

View file

@ -18,7 +18,7 @@ It might be tempting to think of Angular view expressions as JavaScript expressi
not entirely correct, since Angular does not use a JavaScript `eval()` to evaluate expressions. not entirely correct, since Angular does not use a JavaScript `eval()` to evaluate expressions.
You can think of Angular expressions as JavaScript expressions with following differences: You can think of Angular expressions as JavaScript expressions with following differences:
* **Attribute Evaluation:** evaluation of all properties are against the scope doing the * **Attribute Evaluation:** evaluation of all properties are against the scope, doing the
evaluation, unlike in JavaScript where the expressions are evaluated against the global evaluation, unlike in JavaScript where the expressions are evaluated against the global
`window`. `window`.
@ -37,11 +37,11 @@ JavaScript, use the {@link api/ng.$rootScope.Scope#methods_$eval `$eval()`} meth
<doc:source> <doc:source>
1+2={{1+2}} 1+2={{1+2}}
</doc:source> </doc:source>
<doc:protractor> <doc:scenario>
it('should calculate expression in binding', function() { it('should calculate expression in binding', function() {
expect(element(by.binding('1+2')).getText()).toEqual('1+2=3'); expect(binding('1+2')).toEqual('3');
}); });
</doc:protractor> </doc:scenario>
</doc:example> </doc:example>
You can try evaluating different expressions here: You can try evaluating different expressions here:
@ -73,14 +73,14 @@ You can try evaluating different expressions here:
</ul> </ul>
</div> </div>
</doc:source> </doc:source>
<doc:protractor> <doc:scenario>
it('should allow user expression testing', function() { it('should allow user expression testing', function() {
element(by.css('.expressions button')).click(); element('.expressions :button').click();
var lis = element(by.css('.expressions ul')).element.all(by.repeater('expr in exprs')); var li = using('.expressions ul').repeater('li');
expect(lis.count()).toBe(1); expect(li.count()).toBe(1);
expect(lis.get(0).getText()).toEqual('[ X ] 3*10|currency => $30.00'); expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]);
}); });
</doc:protractor> </doc:scenario>
</doc:example> </doc:example>
@ -99,7 +99,7 @@ prevent accidental access to the global state (a common source of subtle bugs).
$scope.name = 'World'; $scope.name = 'World';
$scope.greet = function() { $scope.greet = function() {
$window.alert('Hello ' + $scope.name); ($window.mockWindow || $window).alert('Hello ' + $scope.name);
} }
} }
</script> </script>
@ -108,17 +108,21 @@ prevent accidental access to the global state (a common source of subtle bugs).
<button ng-click="greet()">Greet</button> <button ng-click="greet()">Greet</button>
</div> </div>
</doc:source> </doc:source>
<doc:protractor> <doc:scenario>
it('should calculate expression in binding', function() { it('should calculate expression in binding', function() {
element(by.css('[ng-click="greet()"]')).click(); var alertText;
this.addFutureAction('set mock', function($window, $document, done) {
var alertDialog = browser.switchTo().alert(); $window.mockWindow = {
alert: function(text){ alertText = text; }
expect(alertDialog.getText()).toEqual('Hello World'); };
done();
alertDialog.accept(); });
}); element(':button:contains(Greet)').click();
</doc:protractor> expect(this.addFuture('alert text', function(done) {
done(null, alertText);
})).toBe('Hello World');
});
</doc:scenario>
</doc:example> </doc:example>
## Forgiving ## Forgiving

View file

@ -121,6 +121,3 @@ text upper-case.
</doc:source> </doc:source>
</doc:example> </doc:example>
## Testing custom filters
See the {@link http://docs.angularjs.org/tutorial/step_09#test phonecat tutorial} for an example.

View file

@ -33,10 +33,10 @@ In addition it provides an {@link api/ng.directive:ngModel.NgModelController API
<script> <script>
function Controller($scope) { function Controller($scope) {
$scope.master = {}; $scope.master= {};
$scope.update = function(user) { $scope.update = function(user) {
$scope.master = angular.copy(user); $scope.master= angular.copy(user);
}; };
$scope.reset = function() { $scope.reset = function() {
@ -115,14 +115,9 @@ This ensures that the user is not distracted with an error until after interacti
A form is an instance of {@link api/ng.directive:form.FormController FormController}. A form is an instance of {@link api/ng.directive:form.FormController FormController}.
The form instance can optionally be published into the scope using the `name` attribute. The form instance can optionally be published into the scope using the `name` attribute.
Similarly, control is an instance of {@link api/ng.directive:ngModel.NgModelController NgModelController}.
Similarly, an input control that has the {@link api/ng.directive:ngModel ngModel} directive holds an The control instance can similarly be published into the form instance using the `name` attribute.
instance of {@link api/ng.directive:ngModel.NgModelController NgModelController}. This implies that the internal state of both the form and the control is available for binding in the view using the standard binding primitives.
Such a control instance can be published as a property of the form instance using the `name` attribute
on the input control. The name attribute specifies the name of the property on the form instance.
This implies that the internal state of both the form and the control is available for binding in
the view using the standard binding primitives.
This allows us to extend the above example with these features: This allows us to extend the above example with these features:
@ -235,7 +230,7 @@ In the following example we create two directives.
<script> <script>
var app = angular.module('form-example1', []); var app = angular.module('form-example1', []);
var INTEGER_REGEXP = /^\-?\d+$/; var INTEGER_REGEXP = /^\-?\d*$/;
app.directive('integer', function() { app.directive('integer', function() {
return { return {
require: 'ngModel', require: 'ngModel',

View file

@ -43,7 +43,7 @@ http://userguide.icu-project.org/locale ICU } website for more information about
**Supported locales in Angular** **Supported locales in Angular**
Angular separates number and datetime format rule sets into different files, each file for a Angular separates number and datetime format rule sets into different files, each file for a
particular locale. You can find a list of currently supported locales {@link particular locale. You can find a list of currently supported locales {@link
https://github.com/angular/angular.js/tree/master/src/ngLocale here} https://github.com/angular/angular.js/tree/master/i18n/locale here}
# Providing locale rules to Angular # Providing locale rules to Angular
There are two approaches to providing locale rules to Angular: There are two approaches to providing locale rules to Angular:
@ -97,9 +97,9 @@ locale, it is fine to rely on the default currency symbol. However, if you antic
in other locales might use your app, you should provide your own currency symbol to make sure the in other locales might use your app, you should provide your own currency symbol to make sure the
actual value is understood. actual value is understood.
For example, if you want to display an account balance of 1000 dollars with the following binding For example, if you want to display account balance of 1000 dollars with the following binding
containing currency filter: `{{ 1000 | currency }}`, and your app is currently in en-US locale. containing currency filter: `{{ 1000 | currency }}`, and your app is currently in en-US locale.
'$1000.00' will be shown. However, if someone in a different local (say, Japan) views your app, their '$1000.00' will be shown. However, if someone in a different local (say, Japan) views your app, her
browser will specify the locale as ja, and the balance of '¥1000.00' will be shown instead. This browser will specify the locale as ja, and the balance of '¥1000.00' will be shown instead. This
will really upset your client. will really upset your client.

View file

@ -8,22 +8,11 @@ This document describes the Internet Explorer (IE) idiosyncrasies when dealing w
attributes and tags. Read this document if you are planning on deploying your Angular application attributes and tags. Read this document if you are planning on deploying your Angular application
on IE v8.0 or earlier. on IE v8.0 or earlier.
The project currently supports and will attempt to fix bugs for IE8 and above. The continuous
integration server runs all the tests against IE8. See http://ci.angularjs.org.
IE7 and below are not tested and the project makes no guarantee that Angular will work on it.
A subset of the AngularJS functionality may work. It is up to you to test and decide whether
it works for your particular app.
It is very unlikely that issues specific to IE7 or earlier will be given any time by the core team.
[GitHub](https://github.com/angular/angular.js/issues/4974)
# Short Version # Short Version
To make your Angular application work on IE please make sure that: To make your Angular application work on IE please make sure that:
1. You polyfill JSON.stringify for IE7 and below. You can use 1. You polyfill JSON.stringify if necessary (IE7 will need this). You can use
[JSON2](https://github.com/douglascrockford/JSON-js) or [JSON2](https://github.com/douglascrockford/JSON-js) or
[JSON3](http://bestiejs.github.com/json3/) polyfills for this. [JSON3](http://bestiejs.github.com/json3/) polyfills for this.
<pre> <pre>
@ -51,7 +40,7 @@ To make your Angular application work on IE please make sure that:
3. you **do not** use custom element tags such as `<ng:view>` (use the attribute version 3. you **do not** use custom element tags such as `<ng:view>` (use the attribute version
`<div ng-view>` instead), or `<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: 4. if you **do use** custom element tags, then you must take these steps to make IE happy:
<pre> <pre>
<!doctype html> <!doctype html>
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName"> <html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
@ -91,7 +80,7 @@ The **important** parts are:
IE has issues with element tag names which are not standard HTML tag names. These fall into two IE has issues with element tag names which are not standard HTML tag names. These fall into two
categories, and each category has its own fix. categories, and each category has its own fix.
* If the tag name starts with `my:` prefix then it is considered an XML namespace and must * If the tag name starts with `my:` prefix than it is considered an XML namespace and must
have corresponding namespace declaration on `<html xmlns:my="ignored">` have corresponding namespace declaration on `<html xmlns:my="ignored">`
* If the tag has no `:` but it is not a standard HTML tag, then it must be pre-created using * If the tag has no `:` but it is not a standard HTML tag, then it must be pre-created using

View file

@ -7,14 +7,19 @@
Everything you need to know about AngularJS Everything you need to know about AngularJS
* {@link guide/introduction What is AngularJS?} * {@link guide/introduction What is AngularJS?}
* {@link guide/concepts Conceptual Overview} * {@link guide/concepts Conceptual Overview}
## Tutorials ## Tutorials
* {@link tutorial/index Official AngularJS Tutorial} * {@link tutorial/index Official AngularJS Tutorial}
* [10 Reasons Why You Should Use AngularJS](http://www.sitepoint.com/10-reasons-use-angularjs/) * [10 Reasons Why You Should Use AngularJS](http://www.sitepoint.com/10-reasons-use-angularjs/)
* [Design Principles of AngularJS (video)](https://www.youtube.com/watch?v=HCR7i5F5L8c) * [Design Principles of AngularJS (video)](https://www.youtube.com/watch?v=HCR7i5F5L8c)
* [Fundamentals in 60 Minutes (video)](http://www.youtube.com/watch?v=i9MHigUZKEM) * [Fundamentals in 60 Minutes (video)](http://www.youtube.com/watch?v=i9MHigUZKEM)
* [For folks with jQuery background](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background) * [For folks with jQuery background](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background)
## Core Concepts ## Core Concepts
@ -24,53 +29,75 @@ Everything you need to know about AngularJS
In Angular applications, you move the job of filling page templates with data from the server to the client. The result is a system better structured for dynamic page updates. Below are the core features you'll use. In Angular applications, you move the job of filling page templates with data from the server to the client. The result is a system better structured for dynamic page updates. Below are the core features you'll use.
* {@link guide/databinding Data binding} * {@link guide/databinding Data binding}
* {@link guide/expression Expressions} * {@link guide/expression Expressions}
* {@link guide/directive Directives} * {@link guide/directive Directives}
* {@link api/ngRoute.$route Views and routes (see the example)} * {@link api/ngRoute.$route Views and routes (see the example)}
* {@link guide/filter Filters} * {@link guide/filter Filters}
* {@link guide/forms Forms} and [Concepts of AngularJS Forms](http://mrbool.com/the-concepts-of-angularjs-forms/29117) * {@link guide/forms Forms} and [Concepts of AngularJS Forms](http://mrbool.com/the-concepts-of-angularjs-forms/29117)
### Application Structure ### Application Structure
* **Blog post: **[When to use directives, controllers or services](http://kirkbushell.me/when-to-use-directives-controllers-or-services-in-angular/) * **Blog post: **[When to use directives, controllers or services](http://kirkbushell.me/when-to-use-directives-controllers-or-services-in-angular/)
* **App wiring:** {@link guide/di Dependency injection} * **App wiring:** {@link guide/di Dependency injection}
* **Exposing model to templates:** {@link guide/scope Scopes} * **Exposing model to templates:** {@link guide/scope Scopes}
* **Communicating with servers:** {@link api/ng.$http $http}, {@link api/ngResource.$resource $resource} * **Communicating with servers:** {@link api/ng.$http $http}, {@link api/ngResource.$resource $resource}
### Other AngularJS Features ### Other AngularJS Features
* **Animation:** {@link guide/animations Core concepts}, {@link api/ngAnimate ngAnimate API}, and [Animation in AngularJS 1.2](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html) * **Animation:** {@link guide/animations Core concepts}, {@link api/ngAnimate ngAnimate API}, and [Animation in AngularJS 1.2](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
* **Security:** {@link api/ng.$sce Strict Contextual Escaping}, {@link api/ng.directive:ngCsp Content Security Policy}, {@link api/ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54) * **Security:** {@link api/ng.$sce Strict Contextual Escaping}, {@link api/ng.directive:ngCsp Content Security Policy}, {@link api/ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
* **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link api/ng.filter:date date filter}, {@link api/ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/) * **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link api/ng.filter:date date filter}, {@link api/ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
* **Mobile:** {@link api/ngTouch Touch events} * **Mobile:** {@link api/ngTouch Touch events}
### Testing ### Testing
* **Unit testing:** [Using Karma (video)](http://www.youtube.com/watch?v=YG5DEzaQBIc), {@link guide/dev_guide.unit-testing Unit testing}, {@link guide/dev_guide.services.testing_services Testing services}, [Karma in Webstorm](http://blog.jetbrains.com/webstorm/2013/10/running-javascript-tests-with-karma-in-webstorm-7/) * **Unit testing:** [Using Karma (video)](http://www.youtube.com/watch?v=YG5DEzaQBIc), {@link guide/dev_guide.unit-testing Unit testing}, {@link guide/dev_guide.services.testing_services Testing services}, [Karma in Webstorm](http://blog.jetbrains.com/webstorm/2013/10/running-javascript-tests-with-karma-in-webstorm-7/)
* **Scenario testing:** [Protractor](https://github.com/angular/protractor) * **Scenario testing:** [Protractor](https://github.com/angular/protractor)
## Specific Topics ## Specific Topics
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/) * **Login: **[Google example](https://developers.google.com/+/photohunt/python), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
* **Mobile:** [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html), [PhoneGap](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/) * **Mobile:** [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html), [PhoneGap](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/)
* **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki) * **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
* **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder) * **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
* **Visualization:** [SVG](http://gaslight.co/blog/angular-backed-svgs), [D3.js](http://www.ng-newsletter.com/posts/d3-on-angular.html) * **Visualization:** [SVG](http://gaslight.co/blog/angular-backed-svgs), [D3.js](http://www.ng-newsletter.com/posts/d3-on-angular.html)
## Tools ## Tools
* **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en) * **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en)
* **Testing:** [Karma](http://karma-runner.github.io), [Protractor](https://github.com/angular/protractor) * **Testing:** [Karma](http://karma-runner.github.io), [Protractor](https://github.com/angular/protractor)
* **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012) * **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012)
* **Workflow:** [Yeoman.io](https://github.com/yeoman/generator-angular) and [Angular Yeoman Tutorial](http://www.sitepoint.com/kickstart-your-angularjs-development-with-yeoman-grunt-and-bower/) * **Workflow:** [Yeoman.io](https://github.com/yeoman/generator-angular) and [Angular Yeoman Tutorial](http://www.sitepoint.com/kickstart-your-angularjs-development-with-yeoman-grunt-and-bower/)
## Complementary Libraries ## Complimentary Libraries
This is a short list of libraries with specific support and documentation for working with Angular. You can find a full list of all known Angular external libraries at [ngmodules.org](http://ngmodules.org/). This is a short list of libraries with specific support and documentation for working with Angular. You can find a full list of all known Angular external libraries at [ngmodules.org](http://ngmodules.org/).
* **Internationalization:** [angular-translate](http://pascalprecht.github.io/angular-translate/), [angular-gettext](http://angular-gettext.rocketeer.be/) * **Internationalization:** [angular-translate](http://pascalprecht.github.io/angular-translate/), [angular-gettext](http://angular-gettext.rocketeer.be/)
* **RESTful services:** [Restangular](https://github.com/mgonto/restangular) * **RESTful services:** [Restangular](https://github.com/mgonto/restangular)
* **SQL and NoSQL backends:** [BreezeJS](http://www.breezejs.com/), [AngularFire](http://angularfire.com/) * **SQL and NoSQL backends:** [BreezeJS](http://www.breezejs.com/), [AngularFire](http://angularfire.com/)
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/) * **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/)
## Deployment ## Deployment
@ -78,17 +105,25 @@ This is a short list of libraries with specific support and documentation for wo
### General ### General
* **Javascript minification: **[Background](http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/), [ngmin automation tool](http://www.thinkster.io/pick/XlWneEZCqY/angularjs-ngmin) * **Javascript minification: **[Background](http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/), [ngmin automation tool](http://www.thinkster.io/pick/XlWneEZCqY/angularjs-ngmin)
* **Tracking:** [Angularyitcs (Google Analytics)](http://ngmodules.org/modules/angularytics), [Logging Client-Side Errors](http://www.bennadel.com/blog/2542-Logging-Client-Side-Errors-With-AngularJS-And-Stacktrace-js.htm) * **Tracking:** [Angularyitcs (Google Analytics)](http://ngmodules.org/modules/angularytics), [Logging Client-Side Errors](http://www.bennadel.com/blog/2542-Logging-Client-Side-Errors-With-AngularJS-And-Stacktrace-js.htm)
* **SEO:** [By hand](http://www.yearofmoo.com/2012/11/angularjs-and-seo.html), [prerender.io](http://prerender.io/), [Brombone](http://www.brombone.com/), [SEO.js](http://getseojs.com/), [SEO4Ajax](http://www.seo4ajax.com/) * **SEO:** [By hand](http://www.yearofmoo.com/2012/11/angularjs-and-seo.html), [prerender.io](http://prerender.io/), [Brombone](http://www.brombone.com/), [SEO.js](http://getseojs.com/), [SEO4Ajax](http://www.seo4ajax.com/)
### Server-Specific ### Server-Specific
* **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html) * **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html)
* **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU) * **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/resources/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos) * **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/resources/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/) * **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610) * **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
* **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails) * **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails)
* **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c) * **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c)
## Learning Resources ## Learning Resources
@ -99,21 +134,20 @@ This is a short list of libraries with specific support and documentation for wo
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston * [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz * [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller * [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
###Videos: ###Videos:
* [egghead.io](http://egghead.io/) * [egghead.io](http://egghead.io/),
* [Angular on YouTube](http://youtube.com/angularjs) * [Angular on YouTube](http://youtube.com/angularjs)
### Courses ###Courses
* **Free online:** * **Free on-line:**
[thinkster.io](http://thinkster.io), [thinkster.io](http://thinkster.io),
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1) [CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
* **Paid online:** * **Paid on-line:**
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs), [Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/), [Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html) [lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html)
* **Paid onsite:** * **Paid on-site:**
[angularbootcamp.com](http://angularbootcamp.com/) [angularbootcamp.com](http://angularbootcamp.com/)
## Getting Help ## Getting Help
@ -121,14 +155,17 @@ This is a short list of libraries with specific support and documentation for wo
The recipe for getting help on your unique issue is to create an example that could work (even if it doesn't) in a shareable example on [Plunker](http://plnkr.co/), [JSFiddle](http://jsfiddle.net/), or similar site and then post to one of the following: The recipe for getting help on your unique issue is to create an example that could work (even if it doesn't) in a shareable example on [Plunker](http://plnkr.co/), [JSFiddle](http://jsfiddle.net/), or similar site and then post to one of the following:
* [Stackoverflow.com](http://stackoverflow.com/search?q=angularjs) * [Stackoverflow.com](http://stackoverflow.com/search?q=angularjs)
* [AngularJS mailing list](https://groups.google.com/forum/#!forum/angular) * [AngularJS mailing list](https://groups.google.com/forum/#!forum/angular)
* [AngularJS IRC channel](http://webchat.freenode.net/?channels=angularjs&uio=d4) * [AngularJS IRC channel](http://webchat.freenode.net/?channels=angularjs&uio=d4)
## Social Channels ## Social Channels
* **Daily updates:** [Google+](https://plus.google.com/u/0/+AngularJS) or [Twitter](https://twitter.com/angularjs) * **Daily updates:** [Google+](https://plus.google.com/u/0/+AngularJS) or [Twitter](https://twitter.com/angularjs)
* **Weekly newsletter:** [ng-newsletter](http://www.ng-newsletter.com/)
* **Meetups: **[meetup.com](http://www.meetup.com/find/?keywords=angularJS&radius=Infinity&userFreeform=San+Francisco%2C+CA&mcId=z94108&mcName=San+Francisco%2C+CA&sort=member_count&eventFilter=mysugg) * **Meetups: **[meetup.com](http://www.meetup.com/find/?keywords=angularJS&radius=Infinity&userFreeform=San+Francisco%2C+CA&mcId=z94108&mcName=San+Francisco%2C+CA&sort=member_count&eventFilter=mysugg)
* **Official news and releases: **[AngularJS Blog](http://blog.angularjs.org/) * **Official news and releases: **[AngularJS Blog](http://blog.angularjs.org/)
## Contributing to AngularJS ## Contributing to AngularJS

View file

@ -1,655 +0,0 @@
@ngdoc overview
@name Migrating from 1.0 to 1.2
@description
AngularJS version 1.2 introduces several breaking changes that may require changes to your
application's source code.
Although we try to avoid breaking changes, there are some cases where it is unavoidable.
AngularJS 1.2 has undergone a thourough security review to make applications safer by default,
which has driven many of these changes. Several new features, especially animations, would not
be possible without a few changes. Finally, some outstanding bugs were best fixed by changing
an existing API.
<div class="alert alert-warning">
<p>**Note:** AngularJS versions 1.1.x are considered "experimental" with breaking changes between minor releases.
Version 1.2 is the result of several versions on the 1.1 branch, and has a stable API.</p>
<p>If you have an application on 1.1 and want to migrate it to 1.2, everything in the guide
below should still apply, but you may want to consult the
[changelog](https://github.com/angular/angular.js/blob/master/CHANGELOG.md) as well.</p>
</div>
<ul class="nav nav-list">
<li class="nav-header">Summary of Breaking Changes</li>
<li>{@link guide/migration#ngroute-has-been-moved-into-its-own-module ngRoute has been moved into its own module}</li>
<li>{@link guide/migration#templates-no-longer-automatically-unwrap-promises Templates no longer automatically unwrap promises}</li>
<li>{@link guide/migration#syntax-for-named-wildcard-parameters-changed-in Syntax for named wildcard parameters changed in <code>$route</code>}</li>
<li>{@link guide/migration#you-can-only-bind-one-expression-to You can only bind one expression to <code>*[src]</code> or <code>*[ng-src]</code>}</li>
<li>{@link guide/migration#interpolations-inside-dom-event-handlers-are-now-disallowed Interpolations inside DOM event handlers are now disallowed}</li>
<li>{@link guide/migration#directives-cannot-end-with--start-or--end Directives cannot end with -start or -end}</li>
<li>{@link guide/migration#in-$q,-promisealways-has-been-renamed-promisefinally In $q, promise.always has been renamed promise.finally}</li>
<li>{@link guide/migration#ngmobile-is-now-ngtouch ngMobile is now ngTouch}</li>
<li>{@link guide/migration#resource$then-has-been-removed resource.$then has been removed}</li>
<li>{@link guide/migration#resource-methods-return-the-promise Resource methods return the promise}</li>
<li>{@link guide/migration#resource-promises-are-resolved-with-the-resource-instance Resource promises are resolved with the resource instance}</li>
<li>{@link guide/migration#$locationsearch-supports-multiple-keys $location.search supports multiple keys}</li>
<li>{@link guide/migration#ngbindhtmlunsafe-has-been-removed-and-replaced-by-ngbindhtml ngBindHtmlUnsafe has been removed and replaced by ngBindHtml}</li>
<li>{@link guide/migration#form-names-that-are-expressions-are-evaluated Form names that are expressions are evaluated}</li>
<li>{@link guide/migration#hasownproperty-disallowed-as-an-input-name hasOwnProperty disallowed as an input name}</li>
<li>{@link guide/migration#directives-order-of-postlink-functions-reversed Directives: Order of postLink functions reversed}</li>
<li>{@link guide/migration#directive-priority Directive priority}</li>
<li>{@link guide/migration#ngscenario ngScenario}</li>
<li>{@link guide/migration#nginclude-and-ngview-replace-its-entire-element-on-update ngInclude and ngView replace its entire element on update}</li>
<li>{@link guide/migration#urls-are-now-sanitized-against-a-whitelist URLs are now sanitized against a whitelist}</li>
<li>{@link guide/migration#isolate-scope-only-exposed-to-directives-with-property Isolate scope only exposed to directives with <code>scope</code> property}</li>
<li>{@link guide/migration#change-to-interpolation-priority Change to interpolation priority}</li>
<li>{@link guide/migration#underscore-prefixed/suffixed-properties-are-non-bindable Underscore-prefixed/suffixed properties are non-bindable}</li>
<li>{@link guide/migration#you-cannot-bind-to-select[multiple] You cannot bind to select[multiple]}</li>
<li>{@link guide/migration#uncommon-region-specific-local-files-were-removed-from-i18n Uncommon region-specific local files were removed from i18n}</li>
</ul>
## ngRoute has been moved into its own module
Just like `ngResource`, `ngRoute` is now its own module.
Applications that use `$route`, `ngView`, and/or `$routeParams` will now need to load an
`angular-route.js` file and have their application's module dependency on the `ngRoute` module.
Before:
```html
<script src="angular.js"></script>
```
```javascript
var myApp = angular.module('myApp', ['someOtherModule']);
```
After:
```html
<script src="angular.js"></script>
<script src="angular-route.js"></script>
```
```javascript
var myApp = angular.module('myApp', ['ngRoute', 'someOtherModule']);
```
See [5599b55b](https://github.com/angular/angular.js/commit/5599b55b04788c2e327d7551a4a699d75516dd21).
## Templates no longer automatically unwrap promises
`$parse` and templates in general will no longer automatically unwrap promises.
Before:
```javascript
$scope.foo = $http({method: 'GET', url: '/someUrl'});
```
```html
<p>{{foo}}</p>
```
After:
```javascript
$http({method: 'GET', url: '/someUrl'})
.success(function(data) {
$scope.foo = data;
});
```
```html
<p>{{foo}}</p>
```
This feature has been deprecated. If absolutely needed, it can be reenabled for now via the
`$parseProvider.unwrapPromises(true)` API.
See [5dc35b52](https://github.com/angular/angular.js/commit/5dc35b527b3c99f6544b8cb52e93c6510d3ac577),
[b6a37d11](https://github.com/angular/angular.js/commit/b6a37d112b3e1478f4d14a5f82faabf700443748).
## Syntax for named wildcard parameters changed in `$route`
To migrate the code, follow the example below. Here, `*highlight` becomes `:highlight*`
Before:
```javascript
$routeProvider.when('/Book1/:book/Chapter/:chapter/*highlight/edit',
{controller: noop, templateUrl: 'Chapter.html'});
```
After:
```javascript
$routeProvider.when('/Book1/:book/Chapter/:chapter/:highlight*/edit',
{controller: noop, templateUrl: 'Chapter.html'});
```
See [04cebcc1](https://github.com/angular/angular.js/commit/04cebcc133c8b433a3ac5f72ed19f3631778142b).
## You can only bind one expression to `*[src]` or `*[ng-src]`
With the exception of `<a>` and `<img>` elements, you cannot bind more than one expression to the
`src` attribute of elements.
This is one of several improvements to security introduces by Angular 1.2.
Concatenating expressions makes it hard to understand whether some combination of concatenated
values are unsafe to use and potentially subject to XSS vulnerabilities. To simplify the task of
auditing for XSS issues, we now require that a single expression be used for `*[src/ng-src]`
bindings such as bindings for `iframe[src]`, `object[src]`, etc.
<table class="table table-bordered code-table">
<thead>
<tr>
<th>Examples</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&lt;img src="{{a}}/{{b}}"&gt;</code></td>
<td class="success">ok</td>
</tr>
<tr>
<td><code>&lt;iframe src="{{a}}/{{b}}"&gt;&lt;/iframe&gt;</code></td>
<td class="error">bad</td>
</tr>
<tr>
<td><code>&lt;iframe src="{{a}}"&gt;&lt;/iframe&gt;</code></td>
<td class="success">ok</td>
</tr>
</tbody>
</table>
To migrate your code, you can combine multiple expressions using a method attached to your scope.
Before:
```javascript
scope.baseUrl = 'page';
scope.a = 1;
scope.b = 2;
```
```html
<!-- Are a and b properly escaped here? Is baseUrl controlled by user? -->
<iframe src="{{baseUrl}}?a={{a}&b={{b}}">
```
After:
```javascript
var baseUrl = "page";
scope.getIframeSrc = function() {
// One should think about their particular case and sanitize accordingly
var qs = ["a", "b"].map(function(value, name) {
return encodeURIComponent(name) + "=" +
encodeURIComponent(value);
}).join("&");
// `baseUrl` isn't exposed to a user's control, so we don't have to worry about escaping it.
return baseUrl + "?" + qs;
};
```
```html
<iframe src="{{getIframeSrc()}}">
```
See [38deedd6](https://github.com/angular/angular.js/commit/38deedd6e3d806eb8262bb43f26d47245f6c2739).
## Interpolations inside DOM event handlers are now disallowed
DOM event handlers execute arbitrary Javascript code. Using an interpolation for such handlers
means that the interpolated value is a JS string that is evaluated. Storing or generating such
strings is error prone and leads to XSS vulnerabilities. On the other hand, `ngClick` and other
Angular specific event handlers evaluate Angular expressions in non-window (Scope) context which
makes them much safer.
To migrate the code follow the example below:
Before:
```
JS: scope.foo = 'alert(1)';
HTML: <div onclick="{{foo}}">
```
After:
```
JS: scope.foo = function() { alert(1); }
HTML: <div ng-click="foo()">
```
See [39841f2e](https://github.com/angular/angular.js/commit/39841f2ec9b17b3b2920fd1eb548d444251f4f56).
## Directives cannot end with -start or -end
This change was necessary to enable multi-element directives. The best fix is to rename existing
directives so that they don't end with these suffixes.
See [e46100f7](https://github.com/angular/angular.js/commit/e46100f7097d9a8f174bdb9e15d4c6098395c3f2).
## In $q, promise.always has been renamed promise.finally
The reason for this change is to align `$q` with the [Q promise
library](https://github.com/kriskowal/q), despite the fact that this makes it a bit more difficult
to use with non-ES5 browsers, like IE8.
`finally` also goes well together with the `catch` API that was added to `$q` recently and is part
of the [DOM promises standard](http://dom.spec.whatwg.org/).
To migrate the code follow the example below.
Before:
```javascript
$http.get('/foo').always(doSomething);
```
After:
```javascript
$http.get('/foo').finally(doSomething);
```
Or for IE8-compatible code:
```javascript
$http.get('/foo')['finally'](doSomething);
```
See [f078762d](https://github.com/angular/angular.js/commit/f078762d48d0d5d9796dcdf2cb0241198677582c).
## ngMobile is now ngTouch
Many touch-enabled devices are not mobile devices, so we decided to rename this module to better
reflect its concerns.
To migrate, replace all references to `ngMobile` with `ngTouch` and `angular-mobile.js` with
`angular-touch.js`.
See [94ec84e7](https://github.com/angular/angular.js/commit/94ec84e7b9c89358dc00e4039009af9e287bbd05).
## resource.$then has been removed
Resource instances do not have a `$then` function anymore. Use the `$promise.then` instead.
Before:
```javascript
Resource.query().$then(callback);
```
After:
```javascript
Resource.query().$promise.then(callback);
```
See [05772e15](https://github.com/angular/angular.js/commit/05772e15fbecfdc63d4977e2e8839d8b95d6a92d).
## Resource methods return the promise
Methods of a resource instance return the promise rather than the instance itself.
Before:
```javascript
resource.$save().chaining = true;
```
After:
```javascript
resource.$save();
resource.chaining = true;
```
See [05772e15](https://github.com/angular/angular.js/commit/05772e15fbecfdc63d4977e2e8839d8b95d6a92d).
## Resource promises are resolved with the resource instance
On success, the resource promise is resolved with the resource instance rather than HTTP response object.
Use interceptor API to access the HTTP response object.
Before:
```javascript
Resource.query().$then(function(response) {...});
```
After:
```javascript
var Resource = $resource('/url', {}, {
get: {
method: 'get',
interceptor: {
response: function(response) {
// expose response
return response;
}
}
}
});
```
See [05772e15](https://github.com/angular/angular.js/commit/05772e15fbecfdc63d4977e2e8839d8b95d6a92d).
## $location.search supports multiple keys
{@link api/ng.$location#methods_search `$location.search`} now supports multiple keys with the
same value provided that the values are stored in an array.
Before this change:
* `parseKeyValue` only took the last key overwriting all the previous keys.
* `toKeyValue` joined the keys together in a comma delimited string.
This was deemed buggy behavior. If your server relied on this behavior then either the server
should be fixed, or a simple serialization of the array should be done on the client before
passing it to `$location`.
See [80739409](https://github.com/angular/angular.js/commit/807394095b991357225a03d5fed81fea5c9a1abe).
## ngBindHtmlUnsafe has been removed and replaced by ngBindHtml
`ngBindHtml` which has been moved from `ngSanitize` module to the core `ng` module.
`ngBindHtml` provides `ngBindHtmlUnsafe` like
behavior (evaluate an expression and innerHTML the result into the DOM) when bound to the result
of `$sce.trustAsHtml(string)`. When bound to a plain string, the string is sanitized via
`$sanitize` before being innerHTML'd. If the `$sanitize` service isn't available (`ngSanitize`
module is not loaded) and the bound expression evaluates to a value that is not trusted an
exception is thrown.
See [dae69473](https://github.com/angular/angular.js/commit/dae694739b9581bea5dbc53522ec00d87b26ae55).
## Form names that are expressions are evaluated
If you have form names that will evaluate as an expression:
```
<form name="ctrl.form">
```
And if you are accessing the form from your controller:
Before:
```javascript
function($scope) {
$scope['ctrl.form'] // form controller instance
}
```
After:
```javascript
function($scope) {
$scope.ctrl.form // form controller instance
}
```
This makes it possible to access a form from a controller using the new "controller as" syntax.
Supporting the previous behavior offers no benefit.
See [8ea802a1](https://github.com/angular/angular.js/commit/8ea802a1d23ad8ecacab892a3a451a308d9c39d7).
## hasOwnProperty disallowed as an input name
Inputs with name equal to `hasOwnProperty` are not allowed inside form or ngForm directives.
Before, inputs whose name was "hasOwnProperty" were quietly ignored and not added to the scope.
Now a badname exception is thrown. Using "hasOwnProperty" for an input name would be very unusual
and bad practice. To migrate, change your input name.
See [7a586e5c](https://github.com/angular/angular.js/commit/7a586e5c19f3d1ecc3fefef084ce992072ee7f60).
## Directives: Order of postLink functions reversed
The order of postLink fn is now mirror opposite of the order in which corresponding preLinking and compile functions execute.
Previously the compile/link fns executed in order, sorted by priority:
<table class="table table-bordered table-striped code-table">
<thead>
<tr>
<th>#</th>
<th>Step</th>
<th align="center">Old Sort Order</th>
<th align="center">New Sort Order</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Compile Fns</td>
<td align="center" colspan="2">High → Low</td>
</tr>
<tr>
<td>2</td>
<td colspan="3">Compile child nodes</td>
</tr>
<tr>
<td>3</td>
<td>PreLink Fns</td>
<td align="center" colspan="2">High → Low</td>
</tr>
<tr>
<td>4</td>
<td colspan="3">Link child nodes</td>
</tr>
<tr>
<td>5</td>
<td>PostLink Fns</td>
<td align="center">High → Low</td>
<td align="center">**Low → High**</td>
</tr>
</tbody>
</table>
<small>"High → Low" here refers to the `priority` option of a directive.</small>
Very few directives in practice rely on the order of postLinking functions (unlike on the order
of compile functions), so in the rare case of this change affecting an existing directive, it might
be necessary to convert it to a preLinking function or give it negative priority.
You can look at [the diff of this
commit](https://github.com/angular/angular.js/commit/31f190d4d53921d32253ba80d9ebe57d6c1de82b) to see how an internal
attribute interpolation directive was adjusted.
See [31f190d4](https://github.com/angular/angular.js/commit/31f190d4d53921d32253ba80d9ebe57d6c1de82b).
## Directive priority
the priority of ngRepeat, ngSwitchWhen, ngIf, ngInclude and ngView has changed. This could affect directives that explicitly specify their priority.
In order to make ngRepeat, ngSwitchWhen, ngIf, ngInclude and ngView work together in all common scenarios their directives are being adjusted to achieve the following precendence:
Directive | Old Priority | New Priority
-----------------|--------------|-------------
ngRepeat | 1000 | 1000
ngSwitchWhen | 500 | 800
ngIf | 1000 | 600
ngInclude | 1000 | 400
ngView | 1000 | 400
See [b7af76b4](https://github.com/angular/angular.js/commit/b7af76b4c5aa77648cc1bfd49935b48583419023).
## ngScenario
browserTrigger now uses an eventData object instead of direct parameters for mouse events.
To migrate, place the `keys`,`x` and `y` parameters inside of an object and place that as the
third parameter for the browserTrigger function.
See [28f56a38](https://github.com/angular/angular.js/commit/28f56a383e9d1ff378e3568a3039e941c7ffb1d8).
## ngInclude and ngView replace its entire element on update
Previously `ngInclude` and `ngView` only updated its element's content. Now these directives will
recreate the element every time a new content is included.
This ensures that a single rootElement for all the included contents always exists, which makes
definition of css styles for animations much easier.
See [7d69d52a](https://github.com/angular/angular.js/commit/7d69d52acff8578e0f7d6fe57a6c45561a05b182),
[aa2133ad](https://github.com/angular/angular.js/commit/aa2133ad818d2e5c27cbd3933061797096356c8a).
## URLs are now sanitized against a whitelist
A whitelist configured via `$compileProvider` can be used to configure what URLs are considered safe.
By default all common protocol prefixes are whitelisted including `data:` URIs with mime types `image/*`.
This change sholdn't impact apps that don't contain malicious image links.
See [1adf29af](https://github.com/angular/angular.js/commit/1adf29af13890d61286840177607edd552a9df97),
[3e39ac7e](https://github.com/angular/angular.js/commit/3e39ac7e1b10d4812a44dad2f959a93361cd823b).
## Isolate scope only exposed to directives with `scope` property
Directives without isolate scope do not get the isolate scope from an isolate directive on the
same element. If your code depends on this behavior (non-isolate directive needs to access state
from within the isolate scope), change the isolate directive to use scope locals to pass these explicitly.
**Before**
```
<input ng-model="$parent.value" ng-isolate>
.directive('ngIsolate', function() {
return {
scope: {},
template: '{{value}}'
};
});
```
**After**
```
<input ng-model="value" ng-isolate>
.directive('ngIsolate', function() {
return {
scope: {value: '=ngModel'},
template: '{{value}}
};
});
```
See [909cabd3](https://github.com/angular/angular.js/commit/909cabd36d779598763cc358979ecd85bb40d4d7),
[#1924](https://github.com/angular/angular.js/issues/1924) and
[#2500](https://github.com/angular/angular.js/issues/2500).
## Change to interpolation priority
Previously, the interpolation priority was `-100` in 1.2.0-rc.2, and `100` before 1.2.0-rc.2.
Before this change the binding was setup in the post-linking phase.
Now the attribute interpolation (binding) executes as a directive with priority 100 and the
binding is set up in the pre-linking phase.
See [79223eae](https://github.com/angular/angular.js/commit/79223eae5022838893342c42dacad5eca83fabe8),
[#4525](https://github.com/angular/angular.js/issues/4525),
[#4528](https://github.com/angular/angular.js/issues/4528), and
[#4649](https://github.com/angular/angular.js/issues/4649)
## Underscore-prefixed/suffixed properties are non-bindable
<div class="alert alert-info">
<p>**Reverted**: This breaking change has been reverted in 1.2.1, and so can be ignored if you're using **version 1.2.1 or higher**</p>
</div>
This change introduces the notion of "private" properties (properties
whose names begin and/or end with an underscore) on the scope chain.
These properties will not be available to Angular expressions (i.e. {{
}} interpolation in templates and strings passed to `$parse`) They are
freely available to JavaScript code (as before).
**Motivation**
Angular expressions execute in a limited context. They do not have
direct access to the global scope, `window`, `document` or the Function
constructor. However, they have direct access to names/properties on
the scope chain. It has been a long standing best practice to keep
sensitive APIs outside of the scope chain (in a closure or your
controller.) That's easier said that done for two reasons:
1. JavaScript does not have a notion of private properties so if you need
someone on the scope chain for JavaScript use, you also expose it to
Angular expressions
2. The new `controller as` syntax that's now in increased usage exposes the
entire controller on the scope chain greatly increaing the exposed surface.
Though Angular expressions are written and controlled by the developer, they:
1. Typically deal with user input
2. Don't get the kind of test coverage that JavaScript code would
This commit provides a way, via a naming convention, to allow restricting properties from
controllers/scopes. This means Angular expressions can access only those properties that
are actually needed by the expressions.
See [3d6a89e8](https://github.com/angular/angular.js/commit/3d6a89e8888b14ae5cb5640464e12b7811853c7e).
## You cannot bind to select[multiple]
Switching between `select[single]` and `select[multiple]` has always been odd due to browser quirks.
This feature never worked with two-way data-binding so it's not expected that anyone is using it.
If you are interested in properly adding this feature, please submit a pull request on Github.
See [d87fa004](https://github.com/angular/angular.js/commit/d87fa0042375b025b98c40bff05e5f42c00af114).
## Uncommon region-specific local files were removed from i18n
AngularJS uses the Google Closure library's locale files. The following locales were removed from
Closure, so Angular is not able to continue to support them:
`chr`, `cy`, `el-polyton`, `en-zz`, `fr-rw`, `fr-sn`, `fr-td`, `fr-tg`, `haw`, `it-ch`, `ln-cg`,
`mo`, `ms-bn`, `nl-aw`, `nl-be`, `pt-ao`, `pt-gw`, `pt-mz`, `pt-st`, `ro-md`, `ru-md`, `ru-ua`,
`sr-cyrl-ba`, `sr-cyrl-me`, `sr-cyrl`, `sr-latn-ba`, `sr-latn-me`, `sr-latn`, `sr-rs`, `sv-fi`,
`sw-ke`, `ta-lk`, `tl-ph`, `ur-in`, `zh-hans-hk`, `zh-hans-mo`, `zh-hans-sg`, `zh-hans`,
`zh-hant-hk`, `zh-hant-mo`, `zh-hant-tw`, `zh-hant`
Although these locales were removed from the official AngularJS repository, you can continue to
load and use your copy of the locale file provided that you maintain it yourself.
See [6382e21f](https://github.com/angular/angular.js/commit/6382e21fb28541a2484ac1a241d41cf9fbbe9d2c).

View file

@ -110,7 +110,7 @@ myApp.factory('clientId', function clientIdFactory() {
But given that the token is just a string literal, sticking with the Value recipe is still more But given that the token is just a string literal, sticking with the Value recipe is still more
appropriate as it makes the code easier to follow. appropriate as it makes the code easier to follow.
Let's say, however, that we would also like to create a service that computes a token used for Let's say, however, that we would also like create a service that computes a token used for
authentication against a remote API. This token will be called 'apiToken' and will be computed authentication against a remote API. This token will be called 'apiToken' and will be computed
based on the `clientId` value and a secret stored in browser's local storage: based on the `clientId` value and a secret stored in browser's local storage:
@ -228,7 +228,7 @@ myApp.provider('unicornLauncher', function UnicornLauncherProvider() {
// let's assume that the UnicornLauncher constructor was also changed to // let's assume that the UnicornLauncher constructor was also changed to
// accept and use the useTinfoilShielding argument // accept and use the useTinfoilShielding argument
return new UnicornLauncher(apiToken, useTinfoilShielding); return new UnicornLauncher(apiToken, useTinfoilShielding);
}]; }]);
}); });
``` ```

View file

@ -18,12 +18,8 @@ watch {@link guide/expression expressions} and propagate events.
propagate any model changes through the system into the view from outside of the "Angular propagate any model changes through the system into the view from outside of the "Angular
realm" (controllers, services, Angular event handlers). realm" (controllers, services, Angular event handlers).
- Scopes can be nested to limit access to the properties of application components while providing - Scopes can be nested to isolate application components while providing access to shared model
access to shared model properties. Nested scopes are either "child scopes" or "isolate scopes". properties. A scope (prototypically) inherits properties from its parent scope.
A "child scope" (prototypically) inherits properties from its parent scope. An "isolate scope"
does not. See {@link
guide/directive#creating-custom-directives_demo_isolating-the-scope-of-a-directive isolated
scopes} for more information.
- Scopes provide context against which {@link guide/expression expressions} are evaluated. For - Scopes provide context against which {@link guide/expression expressions} are evaluated. For
example `{{username}}` expression is meaningless, unless it is evaluated against a specific example `{{username}}` expression is meaningless, unless it is evaluated against a specific
@ -122,23 +118,21 @@ inheritance, and child scopes prototypically inherit from their parents.
This example illustrates scopes in application, and prototypical inheritance of properties. The example is followed by This example illustrates scopes in application, and prototypical inheritance of properties. The example is followed by
a diagram depicting the scope boundaries. a diagram depicting the scope boundaries.
<div class="show-scope">
<example> <example>
<file name="index.html"> <file name="index.html">
<div class="show-scope-demo">
<div ng-controller="GreetCtrl"> <div ng-controller="GreetCtrl">
Hello {{name}}! Hello {{name}}!
</div> </div>
<div ng-controller="ListCtrl"> <div ng-controller="ListCtrl">
<ol> <ol>
<li ng-repeat="name in names">{{name}} from {{department}}</li> <li ng-repeat="name in names">{{name}}</li>
</ol> </ol>
</div> </div>
</div>
</file> </file>
<file name="script.js"> <file name="script.js">
function GreetCtrl($scope, $rootScope) { function GreetCtrl($scope) {
$scope.name = 'World'; $scope.name = 'World';
$rootScope.department = 'Angular';
} }
function ListCtrl($scope) { function ListCtrl($scope) {
@ -146,19 +140,20 @@ a diagram depicting the scope boundaries.
} }
</file> </file>
<file name="style.css"> <file name="style.css">
.show-scope-demo.ng-scope, .show-scope .doc-example-live.ng-scope,
.show-scope-demo .ng-scope { .show-scope .doc-example-live .ng-scope {
border: 1px solid red; border: 1px solid red;
margin: 3px; margin: 3px;
} }
</file> </file>
</example> </example>
</div>
<img class="center" src="img/guide/concepts-scope.png"> <img class="center" src="img/guide/concepts-scope.png">
Notice that Angular automatically places `ng-scope` class on elements where scopes are Notice that Angular automatically places `ng-scope` class on elements where scopes are
attached. The `<style>` definition in this example highlights in red the new scope locations. The attached. The `<style>` definition in this example highlights in red the new scope locations. The
child scopes are necessary because the repeater evaluates `{{name}}` expression, but child scopes are necessary because the repeater evaluates `{{employee.name}}` expression, but
depending on which scope the expression is evaluated it produces different result. Similarly the depending on which scope the expression is evaluated it produces different result. Similarly the
evaluation of `{{department}}` prototypically inherits from root scope, as it is the only place evaluation of `{{department}}` prototypically inherits from root scope, as it is the only place
where the `department` property is defined. where the `department` property is defined.
@ -181,7 +176,7 @@ To examine the scope in the debugger:
2. The debugger allows you to access the currently selected element in the console as `$0` 2. The debugger allows you to access the currently selected element in the console as `$0`
variable. variable.
3. To retrieve the associated scope in console execute: `angular.element($0).scope()` or just type $scope 3. To retrieve the associated scope in console execute: `angular.element($0).scope()`
## Scope Events Propagation ## Scope Events Propagation
@ -263,8 +258,8 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
For mutations to be properly observed, you should make them only within the {@link For mutations to be properly observed, you should make them only within the {@link
api/ng.$rootScope.Scope#methods_$apply scope.$apply()}. (Angular APIs do this api/ng.$rootScope.Scope#methods_$apply scope.$apply()}. (Angular APIs do this
implicitly, so no extra `$apply` call is needed when doing synchronous work in controllers, implicitly, so no extra `$apply` call is needed when doing synchronous work in controllers,
or asynchronous work with {@link api/ng.$http $http}, {@link api/ng.$timeout $timeout} or asynchronous work with {@link api/ng.$http $http} or {@link
or {@link api/ng.$interval $interval} services. api/ng.$timeout $timeout} services.
4. **Mutation observation** 4. **Mutation observation**
@ -310,8 +305,6 @@ api/ng.directive:ngController ng-controller} and {@link
api/ng.directive:ngRepeat ng-repeat}, create new child scopes api/ng.directive:ngRepeat ng-repeat}, create new child scopes
and attach the child scope to the corresponding DOM element. You can retrieve a scope for any DOM and attach the child scope to the corresponding DOM element. You can retrieve a scope for any DOM
element by using an `angular.element(aDomElement).scope()` method call. element by using an `angular.element(aDomElement).scope()` method call.
See the {@link guide/directive#creating-custom-directives_demo_isolating-the-scope-of-a-directive
directives guide} for more information about isolate scopes.
### Controllers and Scopes ### Controllers and Scopes

View file

@ -11,19 +11,19 @@ See the [contributing guidelines](https://github.com/angular/angular.js/blob/mas
for how to contribute your own code to AngularJS. for how to contribute your own code to AngularJS.
1. {@link #building-and-testing-angularjs_installing-dependencies Installing Dependencies} 1. <a href="#developing-angularjs_installing-dependencies">Installing Dependencies</a>
2. {@link #building-and-testing-angularjs_forking-angular-on-github Forking Angular on Github} 2. <a href="#developing-angularjs_forking-angular-on-github">Forking Angular on Github</a>
3. {@link #building-and-testing-angularjs_building-angularjs Building AngularJS} 3. <a href="#developing-angularjs_building-angularjs">Building AngularJS</a>
4. {@link #building-and-testing-angularjs_running-a-local-development-web-server Running a Local Development Web Server} 4. <a href="#developing-angularjs_running-a-local-development-web-server">Running a Local Development Web Server</a>
5. {@link #building-and-testing-angularjs_running-the-unit-test-suite Running the Unit Test Suite} 5. <a href="#developing-angularjs_running-the-unit-test-suite">Running the Unit Test Suite</a>
6. {@link #building-and-testing-angularjs_running-the-end-to-end-test-suite Running the End-to-end Test Suite} 6. <a href="#developing-angularjs_running-the-end-to-end-test-suite">Running the End-to-end Test Suite</a>
## Installing Dependencies ## Installing Dependencies
Before you can build AngularJS, you must install and configure the following dependencies on your Before you can build AngularJS, you must install and configure the following dependencies on your
machine: machine:
* {@link http://git-scm.com/ Git}: The {@link https://help.github.com/articles/set-up-git Github Guide to * {@link http://git-scm.com/ Git}: The {@link http://help.github.com/mac-git-installation Github Guide to
Installing Git} is a good source of information. Installing Git} is a good source of information.
* {@link http://nodejs.org Node.js}: We use Node to generate the documentation, run a * {@link http://nodejs.org Node.js}: We use Node to generate the documentation, run a
@ -46,8 +46,6 @@ and included in your {@link http://docs.oracle.com/javase/tutorial/essential/env
npm install -g bower npm install -g bower
``` ```
**Note:** You may need to use sudo (for OSX, *nix, BSD etc) or run your command shell as Administrator (for Windows) to install Grunt &amp;
Bower globally.
## Forking Angular on Github ## Forking Angular on Github
@ -83,18 +81,8 @@ grunt package
<div class="alert alert-warning"> <div class="alert alert-warning">
**Note:** If you're using Windows, you must use an elevated command prompt (right click, run as **Note:** If you're using Windows you must run your command line with administrative privileges (right click, run as
Administrator). This is because `grunt package` creates some symbolic links. Administrator).
</div>
<div class="alert alert-warning">
**Note:** If you're using Linux, and npm install fails with the message
'Please try running this command again as root/Administrator.', you may need to globally install grunt and bower:
<ul>
<li>sudo npm install -g grunt-cli</li>
<li>sudo npm install -g bower</li>
</ul>
</div> </div>
The build output can be located under the `build` directory. It consists of the following files and The build output can be located under the `build` directory. It consists of the following files and

View file

@ -9,20 +9,20 @@ This way, you don't have to download anything or maintain a local copy.
There are two types of angular script URLs you can point to, one for development and one for There are two types of angular script URLs you can point to, one for development and one for
production: production:
* __angular.js__ — This is the human-readable, non-minified version, suitable for web * __angular-<version>.js__ — This is the human-readable, non-minified version, suitable for web
development. development.
* __angular.min.js__ — This is the minified version, which we strongly suggest you use in * __angular-<version>.min.js__ — This is the minified version, which we strongly suggest you use in
production. production.
To point your code to an angular script on the Google CDN server, use the following template. This To point your code to an angular script on the Google CDN server, use the following template. This
example points to the minified version 1.2.0: example points to the minified version 1.0.2:
<pre> <pre>
<!doctype html> <!doctype html>
<html ng-app> <html ng-app>
<head> <head>
<title>My Angular App</title> <title>My Angular App</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
</head> </head>
<body> <body>
</body> </body>
@ -58,7 +58,7 @@ for this angular version. Use this file to get everything in a single download.
* __`angular-mocks.js`__ — This file contains an implementation of mocks that makes * __`angular-mocks.js`__ — This file contains an implementation of mocks that makes
testing angular apps even easier. Your unit/integration test harness should load this file after testing angular apps even easier. Your unit/integration test harness should load this file after
`angular.js` is loaded. `angular-<version>.js` is loaded.
* __`angular-scenario.js`__ — This file is a very nifty JavaScript file that allows you * __`angular-scenario.js`__ — This file is a very nifty JavaScript file that allows you
to write and execute end-to-end tests for angular applications. to write and execute end-to-end tests for angular applications.
@ -68,19 +68,8 @@ to write and execute end-to-end tests for angular applications.
contents of this file are copy&pasted into the `index.html` to avoid even the initial request to `angular-loader.min.js`. contents of this file are copy&pasted into the `index.html` to avoid even the initial request to `angular-loader.min.js`.
See [angular-seed](https://github.com/angular/angular-seed/blob/master/app/index-async.html) for an example of usage. See [angular-seed](https://github.com/angular/angular-seed/blob/master/app/index-async.html) for an example of usage.
* __Additional Angular modules:__ optional modules with additional functionality. These files should be loaded * __`angular-resource.js`__, __`angular-cookies.js`__, etc - extra Angular modules with additional functionality.
after the core `angular.js` file:
* __`angular-animate.js`__ - Enable animation support
* __`angular-cookies.js`__ - A convenient wrapper for reading and writing browser cookies
* __`angular-resource.js`__ - Interaction support with RESTful services via the $resource service
* __`angular-route.js`__ - Routing and deeplinking services and directives for angular apps
* __`angular-sanitize.js`__ - Functionality to sanitize HTML
* __`angular-touch.js`__ - Touch events and other helpers for touch-enabled devices
* __`docs`__ — this directory contains all the files that compose the * __`docs`__ — this directory contains all the files that compose the
<http://docs.angularjs.org/> documentation app. These files are handy to see the older version of <http://docs.angularjs.org/> documentation app. These files are handy to see the older version of
our docs, or even more importantly, view the docs offline. our docs, or even more importantly, view the docs offline.
* __`i18n`__ - this directory contains locale specific `ngLocale` angular modules to override the defaults
defined in the `ng` module.

View file

@ -67,7 +67,7 @@ illustration we typically build snappy apps with hundreds or thousands of active
### How big is the angular.js file that I need to include? ### How big is the angular.js file that I need to include?
The size of the file is < 36KB compressed and minified. The size of the file is < 29KB compressed and minified.
### Can I use the open-source Closure Library with Angular? ### Can I use the open-source Closure Library with Angular?
@ -81,7 +81,7 @@ Yes, Angular can use {@link http://jquery.com/ jQuery} if it's present in your a
application is being bootstrapped. If jQuery is not present in your script path, Angular falls back application is being bootstrapped. If jQuery is not present in your script path, Angular falls back
to its own implementation of the subset of jQuery that we call {@link api/angular.element jQLite}. to its own implementation of the subset of jQuery that we call {@link api/angular.element jQLite}.
Due to a change to use `on()`/`off()` rather than `bind()`/`unbind()`, Angular 1.2 only operates with Due to a change to use `on()`/`off()` rather than `bind()`\`unbind()`, Angular 1.2 only operates with
jQuery 1.7.1 or above. jQuery 1.7.1 or above.

View file

@ -66,25 +66,26 @@ directory.</p></li>
<p>The tutorial instructions, from now on, assume you are running all commands from the <code>angular-phonecat</code> <p>The tutorial instructions, from now on, assume you are running all commands from the <code>angular-phonecat</code>
directory.</p></li> directory.</p></li>
<li><p>You will also need Node.js and Karma to run unit tests, so please verify that you have <li><p>You will also need Node.js and Karma to run unit tests, so please verify that you have
<a href="http://nodejs.org/">Node.js</a> v0.10 or better installed <a href="http://nodejs.org/">Node.js</a> v0.8 or better installed
and that the <code>node</code> executable is on your <code>PATH</code> by running the following and that the <code>node</code> executable is on your <code>PATH</code> by running the following
command in a terminal window:</p></li> command in a terminal window:</p></li>
<pre>node --version</pre> <pre>node --version</pre>
<p>Additionally install <a href="http://karma-runner.github.io/">Karma</a> and its plugins if you <p>Additionally install <a href="http://karma-runner.github.io/">Karma</a> and its plugins if you
don't have it already:</p> don't have it already:</p>
<pre> <pre>
npm install -g karma
npm install npm install
</pre></li> </pre></li>
<li><p>You will need an http server running on your system. Mac and Linux machines typically <li><p>You will need an http server running on your system. Mac and Linux machines typically
have Apache pre-installed, but If you don't already have one installed, you can use <code>node</code> have Apache pre-installed, but If you don't already have one installed, you can use <code>node</code>
to run a simple bundled http server: <code>node scripts/web-server.js</code>.</p></li> to run <code>scripts/web-server.js</code>, a simple bundled http server.</p></li>
</ol> </ol>
</div> </div>
<div class="tab-pane well" id="git-win" title="Git on Windows"> <div class="tab-pane well" id="git-win" title="Git on Windows">
<ol> <ol>
<li><p>You will need Node.js and Karma to run unit tests, so please verify that you have <li><p>You will need Node.js and Karma to run unit tests, so please verify that you have
<a href="http://nodejs.org/">Node.js</a> v0.10 or better installed <a href="http://nodejs.org/">Node.js</a> v0.8 or better installed
and that the <code>node</code> executable is on your <code>PATH</code> by running the following and that the <code>node</code> executable is on your <code>PATH</code> by running the following
command in a terminal window:</p> command in a terminal window:</p>
<pre>node --version</pre> <pre>node --version</pre>
@ -106,8 +107,8 @@ directory.</p>
<p>Other commands like <code>test.bat</code> or <code>e2e-test.bat</code> should be <p>Other commands like <code>test.bat</code> or <code>e2e-test.bat</code> should be
executed from the Windows command line.</li> executed from the Windows command line.</li>
<li><p>You need an http server running on your system, but if you don't already have one <li><p>You need an http server running on your system, but if you don't already have one
already installed, you can use <code>node</code> to run a simple already installed, you can use <code>node</code> to run <code>scripts\web-server.js</code>, a simple
bundled http server: <code>node scripts\web-server.js</code>.</p></li> bundled http server.</p></li>
</ol> </ol>
</div> </div>

View file

@ -26,7 +26,7 @@ angular-seed, and run the application in the browser.
<ol> <ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node ./scripts/web-server.js</code> to start the web server.</li> <li>In a <i>separate</i> terminal tab or window, run <code>node ./scripts/web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a <li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li> href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol> </ol>
</li> </li>
<li><b>For other http servers:</b> <li><b>For other http servers:</b>
@ -54,7 +54,7 @@ angular-seed, and run the application in the browser.
<li><b>For node.js users:</b> <li><b>For node.js users:</b>
<ol> <ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node scripts\web-server.js</code> to start the web server.</li> <li>In a <i>separate</i> terminal tab or window, run <code>node scripts\web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li> <li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol> </ol>
</li> </li>
<li><b>For other http servers:</b> <li><b>For other http servers:</b>
@ -73,7 +73,7 @@ angular-seed, and run the application in the browser.
You can now see the page in your browser. It's not very exciting, but that's OK. You can now see the page in your browser. It's not very exciting, but that's OK.
The HTML page that displays "Nothing here yet!" was constructed with the HTML code shown below. The HTML page that displays "Nothing here yet!" was constructed with the HTML code shown below.
The code contains some key Angular elements that we will need as we progress. The code contains some key Angular elements that we will need going forward.
__`app/index.html`:__ __`app/index.html`:__
<pre> <pre>
@ -104,7 +104,7 @@ __`app/index.html`:__
The `ng-app` attribute represents an Angular directive named `ngApp` (Angular uses The `ng-app` attribute represents an Angular directive named `ngApp` (Angular uses
`name-with-dashes` for its custom attributes and `camelCase` for the corresponding directives `name-with-dashes` for its custom attributes and `camelCase` for the corresponding directives
which implement them). that implements them).
This directive is used to flag the html element that Angular should consider to be the root element This directive is used to flag the html element that Angular should consider to be the root element
of our application. of our application.
This gives application developers the freedom to tell Angular if the entire html page or only a This gives application developers the freedom to tell Angular if the entire html page or only a
@ -179,7 +179,7 @@ For the purposes of this tutorial, we modified the angular-seed with the followi
* Removed the example app * Removed the example app
* Added phone images to `app/img/phones/` * Added phone images to `app/img/phones/`
* Added phone data files (JSON) to `app/phones/` * Added phone data files (JSON) to `app/phones/`
* Added [Bootstrap](http://getbootstrap.com) files to `app/css/` and `app/img/` * Added [Bootstrap](http://twitter.github.com/bootstrap/) files to `app/css/` and `app/img/`

View file

@ -81,7 +81,7 @@ __`app/js/controllers.js`:__
var phonecatApp = angular.module('phonecatApp', []); var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope) { phonecatApp.controller('PhoneListCtrl', function PhoneListCtrl($scope) {
$scope.phones = [ $scope.phones = [
{'name': 'Nexus S', {'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.'}, 'snippet': 'Fast just got faster with Nexus S.'},
@ -104,7 +104,7 @@ the model and the view. We connected the dots between the presentation, data, an
as follows: as follows:
* The {@link api/ng.directive:ngController ngController} directive, located on the `<body>` tag, * The {@link api/ng.directive:ngController ngController} directive, located on the `<body>` tag,
references the name of our controller, `PhoneListCtrl` (located in the JavaScript file references the the name of our controller, `PhoneListCtrl` (located in the JavaScript file
`controllers.js`). `controllers.js`).
* The `PhoneListCtrl` controller attaches the phone data to the `$scope` that was injected into our * The `PhoneListCtrl` controller attaches the phone data to the `$scope` that was injected into our
@ -180,19 +180,15 @@ is available to be injected.
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
this tutorial in Jasmine. You can learn about Jasmine on the {@link this tutorial in Jasmine. You can learn about Jasmine on the {@link
http://pivotal.github.com/jasmine/ Jasmine home page} and at the {@link http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link
http://pivotal.github.io/jasmine/ Jasmine docs}. https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
The angular-seed project is pre-configured to run all unit tests using {@link The angular-seed project is pre-configured to run all unit tests using {@link
http://karma-runner.github.io/ Karma}. Ensure that the necessary karma plugins are installed. http://karma-runner.github.io/ Karma}. To run the test, do the following:
You can do this by issuing `npm install` into your terminal.
To run the test, do the following:
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run 1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
`./scripts/test.sh` (if you are on Windows, run scripts\test.bat) to start the Karma server (the `./scripts/test.sh` to start the Karma server (the config file necessary to start the server
config file necessary to start the server is located at `./config/karma.conf.js`). is located at `./config/karma.conf.js`).
2. Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in 2. Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
the background. Karma will use this browser for test execution. the background. Karma will use this browser for test execution.
@ -206,7 +202,7 @@ To run the test, do the following:
Yay! The test passed! Or not... Yay! The test passed! Or not...
4. To rerun the tests, just change any of the source or test .js files. Karma will notice the change 4. To rerun the tests, just change any of the source or test files. Karma will notice the change
and will rerun the tests for you. Now isn't that sweet? and will rerun the tests for you. Now isn't that sweet?
# Experiments # Experiments
@ -217,13 +213,9 @@ To run the test, do the following:
* Create a new model property in the controller and bind to it from the template. For example: * Create a new model property in the controller and bind to it from the template. For example:
$scope.name = "World"; $scope.hello = "Hello, World!"
Then add a new binding to `index.html`: Refresh your browser to make sure it says, "Hello, World!"
<p>Hello, {{name}}!</p>
Refresh your browser and verify that it says "Hello, World!".
* Create a repeater that constructs a simple table: * Create a repeater that constructs a simple table:

View file

@ -127,8 +127,8 @@ end-to-end tests! Use `./scripts/e2e-test.sh` script for that. End-to-end tests
with unit tests, Karma will exit after the test run and will not automatically rerun the test with unit tests, Karma will exit after the test run and will not automatically rerun the test
suite on every file change. To rerun the test suite, execute the `e2e-test.sh` script again. suite on every file change. To rerun the test suite, execute the `e2e-test.sh` script again.
Note: You must ensure you've installed the karma-ng-scenario framework plugin prior to running the Note: You must ensure you've installed karma-ng-scenario prior to running the `e2e-test.sh` script.
`e2e-test.sh` script. You can do this by issuing `npm install` into your terminal. You can do this by issuing `npm install karma-ng-scenario` into your terminal.
This test verifies that the search box and the repeater are correctly wired together. Notice how This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
@ -154,7 +154,7 @@ really is that easy to set up any functional, readable, end-to-end test.
`ngController` declaration to the HTML element because it is the common parent of both the body `ngController` declaration to the HTML element because it is the common parent of both the body
and title elements: and title elements:
<html ng-app="phonecatApp" ng-controller="PhoneListCtrl"> <html ng-app ng-controller="PhoneListCtrl">
Be sure to __remove__ the `ng-controller` declaration from the body element. Be sure to __remove__ the `ng-controller` declaration from the body element.

View file

@ -67,7 +67,7 @@ __`app/js/controllers.js`:__
<pre> <pre>
var phonecatApp = angular.module('phonecatApp', []); var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope) { phonecatApp.controller('PhoneListCtrl', function PhoneListCtrl($scope) {
$scope.phones = [ $scope.phones = [
{'name': 'Nexus S', {'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.', 'snippet': 'Fast just got faster with Nexus S.',
@ -88,8 +88,8 @@ phonecatApp.controller('PhoneListCtrl', function ($scope) {
record. This property is used to order phones by age. record. This property is used to order phones by age.
* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had * We added a line to the controller that sets the default value of `orderProp` to `age`. If we had
not set a default value here, the `orderBy` filter would remain uninitialized until our not set the default value here, the model would stay uninitialized until our user would pick an
user picked an option from the drop down menu. option from the drop down menu.
This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the
browser, "Newest" is selected in the drop down menu. This is because we set `orderProp` to `'age'` browser, "Newest" is selected in the drop down menu. This is because we set `orderProp` to `'age'`
@ -112,12 +112,11 @@ describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){ describe('PhoneListCtrl', function(){
var scope, ctrl; var scope, ctrl;
beforeEach(module('phonecatApp')); beforeEach(function() {
scope = {},
ctrl = new PhoneListCtrl(scope);
});
beforeEach(inject(function($controller) {
scope = {};
ctrl = $controller('PhoneListCtrl', {$scope:scope});
}));
it('should create "phones" model with 3 phones', function() { it('should create "phones" model with 3 phones', function() {
expect(scope.phones.length).toBe(3); expect(scope.phones.length).toBe(3);

View file

@ -56,7 +56,7 @@ __`app/js/controllers.js:`__
<pre> <pre>
var phonecatApp = angular.module('phonecatApp', []); var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) { phonecatApp.controller('PhoneListCtrl', function PhoneListCtrl($scope, $http) {
$http.get('phones/phones.json').success(function(data) { $http.get('phones/phones.json').success(function(data) {
$scope.phones = data; $scope.phones = data;
}); });
@ -79,7 +79,7 @@ json response and parsed it for us!
To use a service in angular, you simply declare the names of the dependencies you need as arguments To use a service in angular, you simply declare the names of the dependencies you need as arguments
to the controller's constructor function, as follows: to the controller's constructor function, as follows:
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...} function PhoneListCtrl($scope, $http) {...}
Angular's dependency injector provides services to your controller when the controller is being Angular's dependency injector provides services to your controller when the controller is being
constructed. The dependency injector also takes care of creating any transitive dependencies the constructed. The dependency injector also takes care of creating any transitive dependencies the
@ -145,7 +145,7 @@ __`app/js/controllers.js:`__
var phonecatApp = angular.module('phonecatApp', []); var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) { function PhoneListCtrl($scope, $http) {
$http.get('phones/phones.json').success(function(data) { $http.get('phones/phones.json').success(function(data) {
$scope.phones = data; $scope.phones = data;
}); });
@ -170,9 +170,6 @@ describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){ describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend; var scope, ctrl, $httpBackend;
// Load our app module definition before each test.
beforeEach(module('phonecatApp'));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable // This allows us to inject a service but then attach it to a variable
// with the same name as the service. // with the same name as the service.
@ -202,7 +199,7 @@ isolated from the work done in other tests.
* We created a new scope for our controller by calling `$rootScope.$new()` * We created a new scope for our controller by calling `$rootScope.$new()`
* We called the injected `$controller` function passing the name of the `PhoneListCtrl` controller * We called the injected `$controller` function passing the name of the`PhoneListCtrl` controller
and the created scope as parameters. and the created scope as parameters.
Because our code now uses the `$http` service to fetch the phone list data in our controller, before Because our code now uses the `$http` service to fetch the phone list data in our controller, before

View file

@ -64,7 +64,7 @@ We also added phone images next to each record using an image tag with the {@lin
api/ng.directive:ngSrc ngSrc} directive. That directive prevents the api/ng.directive:ngSrc ngSrc} directive. That directive prevents the
browser from treating the angular `{{ expression }}` markup literally, and initiating a request to browser from treating the angular `{{ expression }}` markup literally, and initiating a request to
invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`). specified an attribute binding in a regular `src` attribute (`<img class="diagram" src="{{phone.imageUrl}}">`).
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location. Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.

View file

@ -121,7 +121,7 @@ view, Angular will use the `phone-list.html` template and the `PhoneListCtrl` co
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new, We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view. empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
`$routeProvider.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when the browser `$route.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when the browser
address doesn't match either of our routes. address doesn't match either of our routes.
Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses
@ -149,7 +149,7 @@ __`app/js/controllers.js`:__
var phonecatControllers = angular.module('phonecatControllers', []); var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http', phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) { function PhoneListCtrl($scope, $http) {
$http.get('phones/phones.json').success(function(data) { $http.get('phones/phones.json').success(function(data) {
$scope.phones = data; $scope.phones = data;
}); });
@ -177,7 +177,7 @@ route into the layout template. This makes it a perfect fit for our `index.html`
<div class="alert alert-info"> <div class="alert alert-info">
**Note:** Starting with AngularJS version 1.2, `ngRoute` is in its own module and must be loaded by loading **Note:** Starting with AngularJS version 1.2, `ngRoute` is in its own module and must be loaded by loading
the `angular-route.js` file distributed with Angular. The easiest way to load the file is to add a `<script>` the `angular-route.js` file distributed with Angular. The easist way to load the file is to add a `<script>`
tag to your `index.html` file as shown below. tag to your `index.html` file as shown below.
</div> </div>
@ -258,7 +258,7 @@ to various URLs and verify that the correct view was rendered.
<pre> <pre>
... ...
it('should redirect index.html to index.html#/phones', function() { it('should redirect index.html to index.html#/phones', function() {
browser().navigateTo('app/index.html'); browser().navigateTo('../../app/index.html');
expect(browser().location().url()).toBe('/phones'); expect(browser().location().url()).toBe('/phones');
}); });
... ...
@ -266,7 +266,7 @@ to various URLs and verify that the correct view was rendered.
describe('Phone detail view', function() { describe('Phone detail view', function() {
beforeEach(function() { beforeEach(function() {
browser().navigateTo('app/index.html#/phones/nexus-s'); browser().navigateTo('../../app/index.html#/phones/nexus-s');
}); });

View file

@ -120,7 +120,7 @@ Angular's server}.
<button ng-click="hello('Elmo')">Hello</button> <button ng-click="hello('Elmo')">Hello</button>
to the `phone-detail.html` template. to the `phone-details.html` template.
<div style="display: none"> <div style="display: none">
TODO! TODO!

View file

@ -11,7 +11,7 @@ In this step, you will improve the way our app fetches data.
<div doc-tutorial-reset="11"></div> <div doc-tutorial-reset="11"></div>
The next improvement we will make to our app is to define a custom service that represents a {@link The last improvement we will make to our app is to define a custom service that represents a {@link
http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client. Using this client we http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client. Using this client we
can make XHR requests for data in an easier way, without having to deal with the lower-level {@link can make XHR requests for data in an easier way, without having to deal with the lower-level {@link
api/ng.$http $http} API, HTTP methods and URLs. api/ng.$http $http} API, HTTP methods and URLs.
@ -185,7 +185,7 @@ describe('PhoneCat controllers', function() {
xyzPhoneData = function() { xyzPhoneData = function() {
return { return {
name: 'phone xyz', name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png'] images: ['image/url1.png', 'image/url2.png']
} }
}; };

View file

@ -43,7 +43,7 @@ __`app/index.html`.__
<pre> <pre>
... ...
<!-- jQuery is used for JavaScript animations (include this before angular.js) --> <!-- jQuery is used for JavaScript animations (include this before angular.js) -->
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script> <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<!-- required module to enable animation support in AngularJS --> <!-- required module to enable animation support in AngularJS -->
<script src="lib/angular/angular-animate.js"></script> <script src="lib/angular/angular-animate.js"></script>
@ -56,10 +56,6 @@ __`app/index.html`.__
... ...
</pre> </pre>
<div class="alert alert-error">
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
</div>
Animations can now be created within the CSS code (`animations.css`) as well as the JavaScript code (`animations.js`). Animations can now be created within the CSS code (`animations.css`) as well as the JavaScript code (`animations.js`).
But before we start, let's create a new module which uses the ngAnimate module as a dependency just like we did before But before we start, let's create a new module which uses the ngAnimate module as a dependency just like we did before
with `ngResource`. with `ngResource`.
@ -157,7 +153,7 @@ __`app/css/animations.css`__
</pre> </pre>
As you can see our `phone-listing` CSS class is combined together with the animation hooks that occur when items are 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: inserted info and removed from the list:
* The `ng-enter` class is applied to the element when a new phone is added to the list and rendered on the page. * The `ng-enter` class is applied to the element when a new phone is added to the list and rendered on the page.
* The `ng-move` class is applied when items are moved around in the list. * The `ng-move` class is applied when items are moved around in the list.
@ -198,7 +194,7 @@ This time, instead of the `ng-repeat` element, let's add it to the element conta
In order to do this, we'll have to make some small changes to the HTML code so that we can have more control over our In order to do this, we'll have to make some small changes to the HTML code so that we can have more control over our
animations between view changes. animations between view changes.
__`app/index.html`.__ __`app/partials/phone-list.html`.__
<pre> <pre>
<div class="view-container"> <div class="view-container">
<div ng-view class="view-frame"></div> <div ng-view class="view-frame"></div>
@ -293,7 +289,8 @@ Let's add another animation to our application. Switching to our `phone-detail.h
we see that we have a nice thumbnail swapper. By clicking on the thumbnails listed on the page, we see that we have a nice thumbnail swapper. By clicking on the thumbnails listed on the page,
the profile phone image changes. But how can we change this around to add animations? the profile phone image changes. But how can we change this around to add animations?
Let's think about it first. Basically, when you click on a thumbnail image, you're changing the state of the profile image to reflect the newly Lets think about it first,
basically when you click on a thumbnail image, you're changing the state of the profile image to reflect the newly
selected thumbnail image. selected thumbnail image.
The best way to specify state changes within HTML is to use classes. The best way to specify state changes within HTML is to use classes.
Much like before, how we used a CSS class to specify Much like before, how we used a CSS class to specify
@ -340,58 +337,45 @@ Although we could do that, let's take the opportunity to learn how to create Jav
__`app/js/animations.js`.__ __`app/js/animations.js`.__
<pre> <pre>
var phonecatAnimations = angular.module('phonecatAnimations', ['ngAnimate']); angular.module('phonecatAnimations', ['ngAnimate'])
phonecatAnimations.animation('.phone', function() { .animation('.phone', function() {
return {
addClass : function(element, className, done) {
if(className != 'active') {
return;
}
element.css({
position: 'absolute',
top: 500,
left: 0,
display: 'block'
});
jQuery(element).animate({
top: 0
}, done);
var animateUp = function(element, className, done) { return function(cancel) {
if(className != 'active') { if(cancel) element.stop();
return; };
} },
element.css({ removeClass : function(element, className, done) {
position: 'absolute', if(className != 'active') return;
top: 500, element.css({
left: 0, position: 'absolute',
display: 'block' left: 0,
}); top: 0
});
jQuery(element).animate({
top: -500
}, done);
jQuery(element).animate({ return function(cancel) {
top: 0 if(cancel) element.stop();
}, done); };
return function(cancel) {
if(cancel) {
element.stop();
} }
}; };
} });
var animateDown = function(element, className, done) {
if(className != 'active') {
return;
}
element.css({
position: 'absolute',
left: 0,
top: 0
});
jQuery(element).animate({
top: -500
}, done);
return function(cancel) {
if(cancel) {
element.stop();
}
};
}
return {
addClass: animateUp,
removeClass: animateDown
};
});
</pre> </pre>
Note that we're using {@link http://jquery.com/ jQuery} to implement the animation. jQuery Note that we're using {@link http://jquery.com/ jQuery} to implement the animation. jQuery
@ -399,6 +383,10 @@ isn't required to do JavaScript animations with AngularJS, but we're going to us
your own JavaScript animation library is beyond the scope of this tutorial. For more on your own JavaScript animation library is beyond the scope of this tutorial. For more on
`jQuery.animate`, see the {@link http://api.jquery.com/animate/ jQuery documentation}. `jQuery.animate`, see the {@link http://api.jquery.com/animate/ jQuery documentation}.
<div class="alert alert-error">
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
</div>
The `addClass` and `removeClass` callback functions are called whenever an a class is added or removed The `addClass` and `removeClass` callback functions are called whenever an a class is added or removed
on the element that contains the class we registered, which is in this case `.phone`. When the `.active` on the element that contains the class we registered, which is in this case `.phone`. When the `.active`
class is added to the element (via the `ng-class` directive) the `addClass` JavaScript callback will class is added to the element (via the `ng-class` directive) the `addClass` JavaScript callback will

View file

@ -8,6 +8,8 @@ previous steps using the `git checkout` command.
For more details and examples of the Angular concepts we touched on in this tutorial, see the For more details and examples of the Angular concepts we touched on in this tutorial, see the
{@link guide/ Developer Guide}. {@link guide/ Developer Guide}.
For several more examples of code, see the {@link cookbook/ Cookbook}.
When you are ready to start developing a project using Angular, we recommend that you bootstrap When you are ready to start developing a project using Angular, we recommend that you bootstrap
your development with the {@link https://github.com/angular/angular-seed angular-seed} project. your development with the {@link https://github.com/angular/angular-seed angular-seed} project.

View file

@ -1,7 +1,5 @@
var ngdoc = require('../src/ngdoc.js'); var ngdoc = require('../src/ngdoc.js');
var DOM = require('../src/dom.js').DOM; var DOM = require('../src/dom.js').DOM;
var gruntUtil = require('../../lib/grunt/utils.js');
describe('ngdoc', function() { describe('ngdoc', function() {
var Doc = ngdoc.Doc; var Doc = ngdoc.Doc;
@ -36,8 +34,8 @@ describe('ngdoc', function() {
var d1 = new Doc('@name a.b.c').parse(); var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng-c').parse(); var d2 = new Doc('@name a.b.ng-c').parse();
var d3 = new Doc('@name some text: more text').parse(); var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].shortName).toEqual('a.b.c'); expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('a.b.ng-c'); expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng-c');
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text'); expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
}); });
@ -289,33 +287,6 @@ describe('ngdoc', function() {
}); });
}); });
describe('api section', function() {
it('should render a "view source" button with link to the source in master', function() {
var doc = new Doc({
id: 'ng.abc',
name: 'ng.abc',
section: 'api',
ngdoc: 'service',
file: 'fooService.js',
line: '333'
});
if (gruntUtil.getVersion().full.indexOf('-') === -1) {
expect(doc.html().match(/^(<a .*?<\/a>)/)[1]).toMatch(
/<a href="http:\/\/github\.com\/angular\/angular\.js\/tree\/v\d+\.\d+\.\d+\/fooService\.js#L333" class="view-source/
);
} else {
expect(doc.html().match(/^(<a .*?<\/a>)/)[1]).toMatch(
/<a href="http:\/\/github\.com\/angular\/angular\.js\/tree\/[a-z0-9]{7}\/fooService\.js#L333" class="view-source/
);
}
});
});
//////////////////////////////////////// ////////////////////////////////////////
describe('TAG', function() { describe('TAG', function() {

View file

@ -20,7 +20,7 @@ describe('Docs Links', function() {
}); });
it('should have an "view source" button', function() { it('should have an "view source" button', function() {
spyOn(gruntUtil, 'getVersion').andReturn({full: '1.2.299'}); spyOn(gruntUtil, 'getVersion').andReturn({cdn: '1.2.299'});
expect(doc.html()). expect(doc.html()).
toContain('<a href="http://github.com/angular/angular.js/tree/v1.2.299/test.js#L42" class="view-source btn btn-action"><i class="icon-zoom-in"> </i> View source</a>'); toContain('<a href="http://github.com/angular/angular.js/tree/v1.2.299/test.js#L42" class="view-source btn btn-action"><i class="icon-zoom-in"> </i> View source</a>');

View file

@ -3,7 +3,7 @@
*/ */
exports.appCache = appCache; exports.appCache = appCache;
var fs = require('q-io/fs'); var fs = require('q-fs');
var Q = require('qq'); var Q = require('qq');
function identity($) {return $;} function identity($) {return $;}

View file

@ -6,8 +6,7 @@ var makeUnique = {
'script.js': true, 'script.js': true,
'unit.js': true, 'unit.js': true,
'spec.js': true, 'spec.js': true,
'scenario.js': true, 'scenario.js': true
'protractorTest.js': true
} }
function ids(list) { function ids(list) {
@ -15,7 +14,7 @@ function ids(list) {
}; };
exports.Example = function(scenarios, protractorTests) { exports.Example = function(scenarios) {
this.module = ''; this.module = '';
this.deps = ['angular.js']; this.deps = ['angular.js'];
this.html = []; this.html = [];
@ -25,8 +24,6 @@ exports.Example = function(scenarios, protractorTests) {
this.unit = []; this.unit = [];
this.scenario = []; this.scenario = [];
this.scenarios = scenarios; this.scenarios = scenarios;
this.protractorTest = [];
this.protractorTests = protractorTests;
} }
exports.Example.prototype.setModule = function(module) { exports.Example.prototype.setModule = function(module) {
@ -47,10 +44,6 @@ exports.Example.prototype.addSource = function(name, content) {
var ext = name == 'scenario.js' ? 'scenario' : name.split('.')[1], var ext = name == 'scenario.js' ? 'scenario' : name.split('.')[1],
id = name; id = name;
if (name == 'protractorTest.js') {
ext = 'protractorTest';
}
if (makeUnique[name] && usedIds[id]) { if (makeUnique[name] && usedIds[id]) {
id = name + '-' + (seqCount++); id = name + '-' + (seqCount++);
} }
@ -63,9 +56,6 @@ exports.Example.prototype.addSource = function(name, content) {
if (ext == 'scenario') { if (ext == 'scenario') {
this.scenarios.push(content); this.scenarios.push(content);
} }
if (ext == 'protractorTest') {
this.protractorTests.push(content);
}
}; };
exports.Example.prototype.enableAnimations = function() { exports.Example.prototype.enableAnimations = function() {
@ -102,7 +92,6 @@ exports.Example.prototype.toHtmlEdit = function() {
out.push(' source-edit-json="' + ids(this.json) + '"'); out.push(' source-edit-json="' + ids(this.json) + '"');
out.push(' source-edit-unit="' + ids(this.unit) + '"'); out.push(' source-edit-unit="' + ids(this.unit) + '"');
out.push(' source-edit-scenario="' + ids(this.scenario) + '"'); out.push(' source-edit-scenario="' + ids(this.scenario) + '"');
out.push(' source-edit-protractor="' + ids(this.protractorTest) + '"');
out.push('></div>\n'); out.push('></div>\n');
return out.join(''); return out.join('');
}; };
@ -118,7 +107,6 @@ exports.Example.prototype.toHtmlTabs = function() {
htmlTabs(this.json); htmlTabs(this.json);
htmlTabs(this.unit); htmlTabs(this.unit);
htmlTabs(this.scenario); htmlTabs(this.scenario);
htmlTabs(this.protractorTest);
out.push('</div>'); out.push('</div>');
return out.join(''); return out.join('');
@ -131,8 +119,7 @@ exports.Example.prototype.toHtmlTabs = function() {
if (name === 'index.html') { if (name === 'index.html') {
wrap = ' ng-html-wrap="' + self.module + ' ' + self.deps.join(' ') + '"'; wrap = ' ng-html-wrap="' + self.module + ' ' + self.deps.join(' ') + '"';
} }
if (name == 'scenario.js') name = 'ngScenario e2e test'; if (name == 'scenario.js') name = 'End to end test';
if (name == 'protractorTest.js') name = 'Protractor e2e test';
out.push( out.push(
'<div class="tab-pane" title="' + name + '">\n' + '<div class="tab-pane" title="' + name + '">\n' +

View file

@ -17,8 +17,6 @@ writer.makeDir('build/docs/', true).then(function() {
return writer.makeDir('build/docs/components/bootstrap'); return writer.makeDir('build/docs/components/bootstrap');
}).then(function() { }).then(function() {
return writer.makeDir('build/docs/components/font-awesome'); return writer.makeDir('build/docs/components/font-awesome');
}).then(function() {
return writer.makeDir('build/docs/e2etests');
}).then(function() { }).then(function() {
console.log('Generating AngularJS Reference Documentation...'); console.log('Generating AngularJS Reference Documentation...');
return reader.collect(); return reader.collect();
@ -55,10 +53,6 @@ writer.makeDir('build/docs/', true).then(function() {
var id = doc.id.replace('angular.Module', 'angular.IModule'); var id = doc.id.replace('angular.Module', 'angular.IModule');
fileFutures.push(writer.output('partials/' + doc.section + '/' + id + '.html', doc.html())); fileFutures.push(writer.output('partials/' + doc.section + '/' + id + '.html', doc.html()));
// If it has a sample Protractor test, output that as well.
if (doc.protractorTests.length) {
fileFutures.push(writer.output('ptore2e/' + doc.section + '/' + id + '_test.js', ngdoc.writeProtractorTest(doc)));
}
}); });
ngdoc.checkBrokenLinks(docs); ngdoc.checkBrokenLinks(docs);
@ -80,10 +74,10 @@ function writeTheRest(writesFuture) {
var versions = ngdoc.ngVersions(); var versions = ngdoc.ngVersions();
var currentVersion = ngdoc.ngCurrentVersion(); var currentVersion = ngdoc.ngCurrentVersion();
writesFuture.push(writer.symlink('../../docs/content/notes', 'build/docs/notes', 'directory')); writesFuture.push(writer.symlink('../../docs/content/notes', 'build/docs/notes', 'dir'));
writesFuture.push(writer.symlinkTemplate('css', 'directory')); writesFuture.push(writer.symlinkTemplate('css', 'dir'));
writesFuture.push(writer.symlink('../../docs/img', 'build/docs/img', 'directory')); writesFuture.push(writer.symlink('../../docs/img', 'build/docs/img', 'dir'));
writesFuture.push(writer.symlinkTemplate('js', 'directory')); writesFuture.push(writer.symlinkTemplate('js', 'dir'));
var manifest = 'manifest="/build/docs/appcache.manifest"'; var manifest = 'manifest="/build/docs/appcache.manifest"';

View file

@ -35,7 +35,6 @@ var lookupMinerrMsg = function (doc) {
exports.trim = trim; exports.trim = trim;
exports.metadata = metadata; exports.metadata = metadata;
exports.scenarios = scenarios; exports.scenarios = scenarios;
exports.writeProtractorTest = writeProtractorTest;
exports.merge = merge; exports.merge = merge;
exports.checkBrokenLinks = checkBrokenLinks; exports.checkBrokenLinks = checkBrokenLinks;
exports.Doc = Doc; exports.Doc = Doc;
@ -51,10 +50,9 @@ exports.ngVersions = function() {
}); });
//match the future version of AngularJS that is set in the package.json file //match the future version of AngularJS that is set in the package.json file
return expandVersions(sortVersionsNaturally(versions), exports.ngCurrentVersion().full); return expandVersions(sortVersionsNatrually(versions), exports.ngCurrentVersion().full);
function expandVersions(versions, latestVersion) { function expandVersions(versions, latestVersion) {
var RC_VERSION = /rc\d/;
//copy the array to avoid changing the versions param data //copy the array to avoid changing the versions param data
//the latest version is not on the git tags list, but //the latest version is not on the git tags list, but
//docs.angularjs.org will always point to master as of 1.2 //docs.angularjs.org will always point to master as of 1.2
@ -65,10 +63,20 @@ exports.ngVersions = function() {
var version = versions[i], var version = versions[i],
split = version.split('.'), split = version.split('.'),
isMaster = version == latestVersion, isMaster = version == latestVersion,
isStable = split[1] % 2 === 0 && !RC_VERSION.test(version); isStable = split[1] % 2 == 0;
var title = 'AngularJS - v' + version; var title = 'AngularJS - v' + version;
//anything that is stable before being unstable is a rc1 version
//just like with AngularJS 1.2.0rc1 (even though it's apart of the
//1.1.5 API
if(isMaster || (isStable && !firstUnstable)) {
isStable = false;
}
else {
firstUnstable = firstUnstable || version;
}
var docsPath = version < '1.0.2' ? 'docs-' + version : 'docs'; var docsPath = version < '1.0.2' ? 'docs-' + version : 'docs';
var url = isMaster ? var url = isMaster ?
@ -87,7 +95,7 @@ exports.ngVersions = function() {
return expanded; return expanded;
}; };
function sortVersionsNaturally(versions) { function sortVersionsNatrually(versions) {
var versionMap = {}, var versionMap = {},
NON_RC_RELEASE_NUMBER = 999; NON_RC_RELEASE_NUMBER = 999;
for(var i = versions.length - 1; i >= 0; i--) { for(var i = versions.length - 1; i >= 0; i--) {
@ -156,7 +164,6 @@ function Doc(text, file, line) {
this.line = line; this.line = line;
} }
this.scenarios = this.scenarios || []; this.scenarios = this.scenarios || [];
this.protractorTests = this.protractorTests || [];
this.requires = this.requires || []; this.requires = this.requires || [];
this.param = this.param || []; this.param = this.param || [];
this.properties = this.properties || []; this.properties = this.properties || [];
@ -294,7 +301,7 @@ Doc.prototype = {
replace(/<example(?:\s+module="([^"]*)")?(?:\s+deps="([^"]*)")?(\s+animations="true")?>([\s\S]*?)<\/example>/gmi, replace(/<example(?:\s+module="([^"]*)")?(?:\s+deps="([^"]*)")?(\s+animations="true")?>([\s\S]*?)<\/example>/gmi,
function(_, module, deps, animations, content) { function(_, module, deps, animations, content) {
var example = new Example(self.scenarios, self.protractorTests); var example = new Example(self.scenarios);
if(animations) { if(animations) {
example.enableAnimations(); example.enableAnimations();
example.addDeps('angular-animate.js'); example.addDeps('angular-animate.js');
@ -331,7 +338,7 @@ Doc.prototype = {
}). }).
replace(/^<doc:example(\s+[^>]*)?>([\s\S]*)<\/doc:example>/mi, function(_, attrs, content) { replace(/^<doc:example(\s+[^>]*)?>([\s\S]*)<\/doc:example>/mi, function(_, attrs, content) {
var html, script, scenario, var html, script, scenario,
example = new Example(self.scenarios, self.protractorTests); example = new Example(self.scenarios);
example.setModule((attrs||'module=""').match(/^\s*module=["'](.*)["']\s*$/)[1]); example.setModule((attrs||'module=""').match(/^\s*module=["'](.*)["']\s*$/)[1]);
content. content.
@ -349,8 +356,6 @@ Doc.prototype = {
}). }).
replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi, function(_, before, content){ replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi, function(_, before, content){
example.addSource('scenario.js', content); example.addSource('scenario.js', content);
}).replace(/(<doc:protractor>)([\s\S]*)(<\/doc:protractor>)/mi, function(_, before, content){
example.addSource('protractorTest.js', content);
}); });
return placeholder(example.toHtml()); return placeholder(example.toHtml());
@ -551,22 +556,10 @@ Doc.prototype = {
self = this, self = this,
minerrMsg; minerrMsg;
var gitTagFromFullVersion = function(version) {
var match = version.match(/sha\.(\w{7})/);
if (match) {
// git sha
return match[1];
}
// git tag
return 'v' + version;
};
if (this.section === 'api') { if (this.section === 'api') {
dom.tag('a', { dom.tag('a', {
href: 'http://github.com/angular/angular.js/tree/' + href: 'http://github.com/angular/angular.js/tree/v' +
gitTagFromFullVersion(gruntUtil.getVersion().full) + '/' + self.file + '#L' + self.line, gruntUtil.getVersion().cdn + '/' + self.file + '#L' + self.line,
class: 'view-source btn btn-action' }, function(dom) { class: 'view-source btn btn-action' }, function(dom) {
dom.tag('i', {class:'icon-zoom-in'}, ' '); dom.tag('i', {class:'icon-zoom-in'}, ' ');
dom.text(' View source'); dom.text(' View source');
@ -1110,28 +1103,12 @@ function scenarios(docs){
} }
} }
function writeProtractorTest(doc){
var lines = [];
lines.push('describe("' + doc.section + '/' + doc.id + '", function() {');
lines.push(' beforeEach(function() {');
lines.push(' browser.get("index-nocache.html#!/' + doc.section + '/' + doc.id + '");');
lines.push(' });');
lines.push('');
doc.protractorTests.forEach(function(test){
lines.push(indentCode(trim(test), 2));
lines.push('');
});
lines.push('});');
lines.push('');
return lines.join('\n');
}
////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
function metadata(docs){ function metadata(docs){
var pages = []; var pages = [];
docs.forEach(function(doc){ docs.forEach(function(doc){
var path = (doc.name || '').split(/(\:\s*)/); var path = (doc.name || '').split(/(\.|\:\s*)/);
for ( var i = 1; i < path.length; i++) { for ( var i = 1; i < path.length; i++) {
path.splice(i, 1); path.splice(i, 1);
} }
@ -1401,29 +1378,15 @@ function explainModuleInstallation(moduleName){
modulePackage = 'angular-' + moduleName, modulePackage = 'angular-' + moduleName,
modulePackageFile = modulePackage + '.js'; modulePackageFile = modulePackage + '.js';
// Deal with inconsistent ngMock naming - doing it verbosely and explicitly here
// rather than cleverly interweaving it in the previous lines to make it obvious
// what is going on
if ( moduleName == 'mock' ) {
modulePackage = 'angular-mocks';
modulePackageFile = modulePackage + '.js';
}
return '<h1>Installation</h1>' + return '<h1>Installation</h1>' +
'<p>First include <code>' + modulePackageFile +'</code> in your HTML:</p><pre><code>' + '<p>First include <code>' + modulePackageFile +'</code> in your HTML:</p><pre><code>' +
' &lt;script src=&quot;angular.js&quot;&gt;\n' + ' &lt;script src=&quot;angular.js&quot;&gt;\n' +
' &lt;script src=&quot;' + modulePackageFile + '&quot;&gt;</pre></code>' + ' &lt;script src=&quot;' + modulePackageFile + '&quot;&gt;</pre></code>' +
'<p>You can download this file from the following places:</p>' + '<p>You can also find this file on the [Google CDN](https://developers.google.com/speed/libraries/devguide#angularjs), ' +
'<ul>' + '<a href="http://bower.io/">Bower</a> (as <code>' + modulePackage + '</code>), ' +
'<li>[Google CDN](https://developers.google.com/speed/libraries/devguide#angularjs)<br>' + 'and on <a href="http://code.angularjs.org/">code.angularjs.org</a>.</p>' +
'e.g. <code>"//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/' + modulePackageFile + '"</code></li>' +
'<li>[Bower](http://bower.io)<br>' +
'e.g. <code>bower install ' + modulePackage + '@X.Y.Z</code></li>' +
'<li><a href="http://code.angularjs.org/">code.angularjs.org</a><br>' +
'e.g. <code>"//code.angularjs.org/X.Y.Z/' + modulePackageFile + '"</code></li>' +
'</ul>' +
'<p>where X.Y.Z is the AngularJS version you are running.</p>' +
'<p>Then load the module in your application by adding it as a dependent module:</p><pre><code>' + '<p>Then load the module in your application by adding it as a dependent module:</p><pre><code>' +
' angular.module(\'app\', [\'' + ngMod + '\']);</pre></code>' + ' angular.module(\'app\', [\'' + ngMod + '\']);</pre></code>' +

View file

@ -7,7 +7,7 @@ exports.collect = collect;
var ngdoc = require('./ngdoc.js'), var ngdoc = require('./ngdoc.js'),
Q = require('qq'), Q = require('qq'),
qfs = require('q-io/fs'), qfs = require('q-fs'),
PATH = require('path'); PATH = require('path');
var NEW_LINE = /\n\r?/; var NEW_LINE = /\n\r?/;

View file

@ -16,4 +16,4 @@ RewriteCond %{HTTP_HOST} ^docs-next\.angularjs\.org$
RewriteRule appcache.manifest http://code.angularjs.org/next/docs/appcache.manifest [R=301] RewriteRule appcache.manifest http://code.angularjs.org/next/docs/appcache.manifest [R=301]
## HTML5 URL Support ## ## HTML5 URL Support ##
RewriteRule ^(guide|api|misc|tutorial)(/.*)?$ index.html RewriteRule ^(guide|api|cookbook|misc|tutorial)(/.*)?$ index.html

View file

@ -144,32 +144,6 @@
.content h4, .content h4,
.content h5 { .content h5 {
margin-top: 1em; margin-top: 1em;
letter-spacing: -0.06em;
}
.content h2 {
font-size: 36px;
margin-bottom: .5em;
}
.content h3 {
font-size: 24px;
border-top: 1px solid #eee;
padding-top: .5em;
}
.content h4 {
font-size: 16px;
margin-top: 1.5em;
}
.content ul {
margin-top: .5em;
}
.content h6 {
text-transform:none;
color:black;
} }
ul.parameters > li > p, ul.parameters > li > p,
@ -246,10 +220,6 @@ ul.events > li > h3 {
text-decoration: none; text-decoration: none;
} }
.tutorial-nav li {
margin-right: 5px;
}
.clear { .clear {
clear: both; clear: both;
} }
@ -552,10 +522,6 @@ pre ol li {
margin-bottom:30px; margin-bottom:30px;
} }
.definition-table td {
vertical-align: top;
}
.component-heading { .component-heading {
text-transform:capitalize; text-transform:capitalize;
} }

View file

@ -26,7 +26,7 @@
} }
var indexFile = (location.pathname.match(/\/(index[^\.]*\.html)/) || ['', ''])[1], var indexFile = (location.pathname.match(/\/(index[^\.]*\.html)/) || ['', ''])[1],
rUrl = /(#!\/|api|guide|misc|tutorial|error|index[^\.]*\.html).*$/, rUrl = /(#!\/|api|guide|misc|tutorial|cookbook|error|index[^\.]*\.html).*$/,
baseUrl = location.href.replace(rUrl, indexFile), baseUrl = location.href.replace(rUrl, indexFile),
jQuery = /index-jq[^\.]*\.html$/.test(baseUrl), jQuery = /index-jq[^\.]*\.html$/.test(baseUrl),
debug = /index[^\.]*-debug\.html$/.test(baseUrl), debug = /index[^\.]*-debug\.html$/.test(baseUrl),
@ -334,6 +334,11 @@
<div id="loading" ng-show="loading">Loading...</div> <div id="loading" ng-show="loading">Loading...</div>
<div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content slide-reveal"></div> <div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content slide-reveal"></div>
<div id="disqus" class="disqus">
<h2>Discussion</h2>
<div id="disqus_thread" class="content-panel-content"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -360,7 +365,7 @@
<footer class="footer"> <footer class="footer">
<div class="container"> <div class="container">
<p class="pull-right"><a back-to-top href="#">Back to top</a></p> <p class="pull-right"><a href="#">Back to top</a></p>
<p> <p>
Super-powered by Google ©2010-2012 Super-powered by Google ©2010-2012

View file

@ -5,19 +5,11 @@ var docsApp = {
filter: {} filter: {}
}; };
docsApp.controller.DocsVersionsCtrl = ['$scope', '$rootScope', '$window', 'NG_VERSIONS', 'NG_VERSION', function($scope, $rootScope, $window, NG_VERSIONS, NG_VERSION) { docsApp.controller.DocsVersionsCtrl = ['$scope', '$window', 'NG_VERSIONS', 'NG_VERSION', function($scope, $window, NG_VERSIONS, NG_VERSION) {
$scope.docs_versions = NG_VERSIONS; $scope.docs_versions = NG_VERSIONS;
$scope.docs_version = NG_VERSIONS[0]; $scope.docs_version = NG_VERSIONS[0];
$scope.jumpToDocsVersion = function(version) { $scope.jumpToDocsVersion = function(version) {
var currentPagePath = ''; $window.location = version.url;
// preserve URL path when switching between doc versions
if (angular.isObject($rootScope.currentPage) && $rootScope.currentPage.section && $rootScope.currentPage.id) {
currentPagePath = '/' + $rootScope.currentPage.section + '/' + $rootScope.currentPage.id;
}
$window.location = version.url + currentPagePath;
}; };
}]; }];
@ -111,6 +103,9 @@ docsApp.serviceFactory.docsSearch = ['$rootScope','lunrSearch', 'NG_PAGES',
angular.forEach(index.search(q), function(result) { angular.forEach(index.search(q), function(result) {
var item = NG_PAGES[result.ref]; var item = NG_PAGES[result.ref];
var section = item.section; var section = item.section;
if(section == 'cookbook') {
section = 'tutorial';
}
results[section] = results[section] || []; results[section] = results[section] || [];
if(results[section].length < 15) { if(results[section].length < 15) {
results[section].push(item); results[section].push(item);
@ -142,19 +137,11 @@ docsApp.directive.docsSearchInput = ['$document',function($document) {
var ESCAPE_KEY_KEYCODE = 27, var ESCAPE_KEY_KEYCODE = 27,
FORWARD_SLASH_KEYCODE = 191; FORWARD_SLASH_KEYCODE = 191;
angular.element($document[0].body).bind('keydown', function(event) { angular.element($document[0].body).bind('keydown', function(event) {
if(event.keyCode == FORWARD_SLASH_KEYCODE && document.activeElement) { var input = element[0];
var activeElement = document.activeElement; if(event.keyCode == FORWARD_SLASH_KEYCODE && document.activeElement != input) {
var activeTagName = activeElement.nodeName.toLowerCase(); event.stopPropagation();
var hasInputFocus = activeTagName == 'input' || activeTagName == 'select' || event.preventDefault();
activeTagName == 'option' || activeTagName == 'textarea' || input.focus();
activeElement.hasAttribute('contenteditable');
if(!hasInputFocus) {
event.stopPropagation();
event.preventDefault();
var input = element[0];
input.focus();
}
} }
}); });
@ -286,10 +273,10 @@ docsApp.directive.docTutorialNav = function(templateMerge) {
element.addClass('btn-group'); element.addClass('btn-group');
element.addClass('tutorial-nav'); element.addClass('tutorial-nav');
element.append(templateMerge( element.append(templateMerge(
'<a href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="icon-step-backward"></i> Previous</li></a>\n' + '<li class="btn btn-primary"><a href="tutorial/{{prev}}"><i class="icon-step-backward"></i> Previous</a></li>\n' +
'<a href="http://angular.github.com/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="icon-play"></i> Live Demo</li></a>\n' + '<li class="btn btn-primary"><a href="http://angular.github.com/angular-phonecat/step-{{seq}}/app"><i class="icon-play"></i> Live Demo</a></li>\n' +
'<a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="icon-search"></i> Code Diff</li></a>\n' + '<li class="btn btn-primary"><a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><i class="icon-search"></i> Code Diff</a></li>\n' +
'<a href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="icon-step-forward"></i></li></a>', props)); '<li class="btn btn-primary"><a href="tutorial/{{next}}">Next <i class="icon-step-forward"></i></a></li>', props));
} }
}; };
}; };
@ -385,21 +372,6 @@ docsApp.directive.errorDisplay = ['$location', 'errorLinkFilter', function ($loc
}]; }];
/**
* backToTop Directive
* @param {Function} $anchorScroll
*
* @description Ensure that the browser scrolls when the anchor is clicked
*/
docsApp.directive.backToTop = ['$anchorScroll', function($anchorScroll) {
return function link(scope, element) {
element.on('click', function(event) {
scope.$apply($anchorScroll);
});
};
}];
docsApp.serviceFactory.angularUrls = function($document) { docsApp.serviceFactory.angularUrls = function($document) {
var urls = {}; var urls = {};
@ -627,6 +599,7 @@ docsApp.serviceFactory.sections = ['NG_PAGES', function sections(NG_PAGES) {
api: [], api: [],
tutorial: [], tutorial: [],
misc: [], misc: [],
cookbook: [],
error: [], error: [],
getPage: function(sectionId, partialId) { getPage: function(sectionId, partialId) {
var pages = sections[sectionId]; var pages = sections[sectionId];
@ -657,7 +630,7 @@ docsApp.serviceFactory.sections = ['NG_PAGES', function sections(NG_PAGES) {
}]; }];
docsApp.controller.DocsController = function($scope, $rootScope, $location, $window, $cookies, sections) { docsApp.controller.DocsController = function($scope, $location, $window, $cookies, sections) {
$scope.fold = function(url) { $scope.fold = function(url) {
if(url) { if(url) {
$scope.docs_fold = '/notes/' + url; $scope.docs_fold = '/notes/' + url;
@ -671,7 +644,7 @@ docsApp.controller.DocsController = function($scope, $rootScope, $location, $win
} }
}; };
var OFFLINE_COOKIE_NAME = 'ng-offline', var OFFLINE_COOKIE_NAME = 'ng-offline',
DOCS_PATH = /^\/(api)|(guide)|(misc)|(tutorial)|(error)/, DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)|(error)/,
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/, INDEX_PATH = /^(\/|\/index[^\.]*.html)$/,
GLOBALS = /^angular\.([^\.]+)$/, GLOBALS = /^angular\.([^\.]+)$/,
ERROR = /^([a-zA-Z0-9_$]+:)?([a-zA-Z0-9_$]+)$/, ERROR = /^([a-zA-Z0-9_$]+:)?([a-zA-Z0-9_$]+)$/,
@ -707,6 +680,7 @@ docsApp.controller.DocsController = function($scope, $rootScope, $location, $win
var currentPageId = $location.path(); var currentPageId = $location.path();
$scope.partialTitle = $scope.currentPage.shortName; $scope.partialTitle = $scope.currentPage.shortName;
$window._gaq.push(['_trackPageview', currentPageId]); $window._gaq.push(['_trackPageview', currentPageId]);
loadDisqus(currentPageId);
}; };
/** stores a cookie that is used by apache to decide which manifest ot send */ /** stores a cookie that is used by apache to decide which manifest ot send */
@ -733,6 +707,7 @@ docsApp.controller.DocsController = function($scope, $rootScope, $location, $win
guide: 'Developer Guide', guide: 'Developer Guide',
misc: 'Miscellaneous', misc: 'Miscellaneous',
tutorial: 'Tutorial', tutorial: 'Tutorial',
cookbook: 'Examples',
error: 'Error Reference' error: 'Error Reference'
}; };
@ -747,9 +722,9 @@ docsApp.controller.DocsController = function($scope, $rootScope, $location, $win
sectionName = SECTION_NAME[sectionId] || sectionId, sectionName = SECTION_NAME[sectionId] || sectionId,
page = sections.getPage(sectionId, partialId); page = sections.getPage(sectionId, partialId);
$rootScope.currentPage = sections.getPage(sectionId, partialId); $scope.currentPage = sections.getPage(sectionId, partialId);
if (!$rootScope.currentPage) { if (!$scope.currentPage) {
$scope.partialTitle = 'Error: Page Not Found!'; $scope.partialTitle = 'Error: Page Not Found!';
} }
@ -917,6 +892,29 @@ docsApp.controller.DocsController = function($scope, $rootScope, $location, $win
return namespace; return namespace;
} }
} }
function loadDisqus(currentPageId) {
// http://docs.disqus.com/help/2/
window.disqus_shortname = 'angularjs-next';
window.disqus_identifier = currentPageId;
window.disqus_url = 'http://docs.angularjs.org' + currentPageId;
if ($location.host() == 'localhost') {
return; // don't display disqus on localhost, comment this out if needed
//window.disqus_developer = 1;
}
// http://docs.disqus.com/developers/universal/
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://angularjs.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
angular.element(document.getElementById('disqus_thread')).html('');
}
}; };

View file

@ -3,7 +3,7 @@
* for testability * for testability
*/ */
var pathUtils = require('path'); var pathUtils = require('path');
var qfs = require('q-io/fs'); var qfs = require('q-fs');
var Q = require('qq'); var Q = require('qq');
var OUTPUT_DIR = pathUtils.join('build','docs'); var OUTPUT_DIR = pathUtils.join('build','docs');
var TEMPLATES_DIR = pathUtils.join('docs','src','templates'); var TEMPLATES_DIR = pathUtils.join('docs','src','templates');
@ -76,7 +76,7 @@ function symlink(from, to, type) {
// qfs will normalize the path arguments for us here // qfs will normalize the path arguments for us here
return qfs.exists(to).then(function(exists) { return qfs.exists(to).then(function(exists) {
if (!exists) { if (!exists) {
return qfs.symbolicLink(to, from, type || 'file'); return qfs.symbolicLink(to, from, type);
} }
}); });
} }

View file

@ -1,12 +1,9 @@
#!/bin/bash #!/bin/bash
echo "#################################"
echo "#### Jenkins Build ############"
echo "#################################"
# Enable tracing and exit on first failure # Enable tracing and exit on first failure
set -xe set -xe
# Define reasonable set of browsers in case we are running manually from commandline # Define reasonable set of browsers in case we are running manually from commandline
if [[ -z "$BROWSERS" ]] if [[ -z "$BROWSERS" ]]
then then
@ -28,15 +25,13 @@ rm -f angular.js.size
npm install --color false npm install --color false
grunt ci-checks package --no-color grunt ci-checks package --no-color
# DOCS generator unit tests #
grunt test:docgen --no-color
# UNIT TESTS # # UNIT TESTS #
grunt test:unit --browsers $BROWSERS --reporters=dots,junit --no-colors --no-color grunt test:unit --browsers $BROWSERS --reporters=dots,junit --no-colors --no-color
# END TO END TESTS # # END TO END TESTS #
grunt test:e2e --browsers $BROWSERS_E2E --reporters=dots,junit --no-colors --no-color grunt test:e2e --browsers $BROWSERS_E2E --reporters=dots,junit --no-colors --no-color
grunt test:protractor
# Promises/A+ TESTS # # Promises/A+ TESTS #
grunt test:promises-aplus --no-color grunt test:promises-aplus --no-color

View file

@ -10,12 +10,12 @@ module.exports = function(config) {
'build/angular.js', 'build/angular.js',
'build/angular-cookies.js', 'build/angular-cookies.js',
'build/angular-mocks.js',
'build/angular-resource.js', 'build/angular-resource.js',
'build/angular-touch.js', 'build/angular-touch.js',
'build/angular-sanitize.js', 'build/angular-sanitize.js',
'build/angular-route.js', 'build/angular-route.js',
'build/angular-animate.js', 'build/angular-animate.js',
'build/angular-mocks.js',
'build/docs/components/lunr.js', 'build/docs/components/lunr.js',
'build/docs/components/google-code-prettify.js', 'build/docs/components/google-code-prettify.js',

25
karma-e2e.conf.js Normal file
View file

@ -0,0 +1,25 @@
var sharedConfig = require('./karma-shared.conf');
module.exports = function(config) {
sharedConfig(config, {testName: 'AngularJS: e2e', logFile: 'karma-e2e.log'});
config.set({
frameworks: [],
files: [
'build/angular-scenario.js',
'node_modules/karma-ng-scenario/lib/adapter.js',
'build/docs/docs-scenario.js'
],
proxies: {
// angular.js, angular-resource.js, etc
'/angular': 'http://localhost:8000/build/angular',
'/': 'http://localhost:8000/build/docs/'
},
junitReporter: {
outputFile: 'test_out/e2e.xml',
suite: 'E2E'
}
});
};

View file

@ -5,26 +5,14 @@ module.exports = function(config, specificOptions) {
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO,
logColors: true, logColors: true,
browsers: ['Chrome'], browsers: ['Chrome'],
browserDisconnectTimeout: 10000, browserDisconnectTimeout: 5000,
browserDisconnectTolerance: 2,
browserNoActivityTimeout: 20000,
// SauceLabs config for local development. // config for Travis CI
sauceLabs: { sauceLabs: {
testName: specificOptions.testName || 'AngularJS', testName: specificOptions.testName || 'AngularJS',
startConnect: true, startConnect: false,
options: { tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
'selenium-version': '2.37.0'
}
},
// BrowserStack config for local development.
browserStack: {
project: 'AngularJS',
name: specificOptions.testName,
startTunnel: true,
timeout: 600 // 10min
}, },
// For more browsers on Sauce Labs see: // For more browsers on Sauce Labs see:
@ -36,14 +24,13 @@ module.exports = function(config, specificOptions) {
}, },
'SL_Firefox': { 'SL_Firefox': {
base: 'SauceLabs', base: 'SauceLabs',
browserName: 'firefox', browserName: 'firefox'
version: '26'
}, },
'SL_Safari': { 'SL_Safari': {
base: 'SauceLabs', base: 'SauceLabs',
browserName: 'safari', browserName: 'safari',
platform: 'OS X 10.9', platform: 'Mac 10.8',
version: '7' version: '6'
}, },
'SL_IE_8': { 'SL_IE_8': {
base: 'SauceLabs', base: 'SauceLabs',
@ -62,77 +49,12 @@ module.exports = function(config, specificOptions) {
browserName: 'internet explorer', browserName: 'internet explorer',
platform: 'Windows 2012', platform: 'Windows 2012',
version: '10' version: '10'
},
'SL_IE_11': {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
'BS_Chrome': {
base: 'BrowserStack',
browser: 'chrome',
os: 'OS X',
os_version: 'Mountain Lion'
},
'BS_Safari': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Mountain Lion'
},
'BS_Firefox': {
base: 'BrowserStack',
browser: 'firefox',
os: 'Windows',
os_version: '8'
},
'BS_IE_8': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '8.0',
os: 'Windows',
os_version: '7'
},
'BS_IE_9': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '9.0',
os: 'Windows',
os_version: '7'
},
'BS_IE_10': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '10.0',
os: 'Windows',
os_version: '8'
},
'BS_IE_11': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '11.0',
os: 'Windows',
os_version: '8.1'
} }
} }
}); });
if (process.env.TRAVIS) { if (process.env.TRAVIS) {
var buildLabel = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
config.logLevel = config.LOG_DEBUG;
config.transports = ['websocket', 'xhr-polling'];
config.browserStack.build = buildLabel;
config.browserStack.startTunnel = false;
config.sauceLabs.build = buildLabel;
config.sauceLabs.startConnect = false;
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
// TODO(vojta): remove once SauceLabs supports websockets. // TODO(vojta): remove once SauceLabs supports websockets.
// This speeds up the capturing a bit, as browsers don't even try to use websocket. // This speeds up the capturing a bit, as browsers don't even try to use websocket.
config.transports = ['xhr-polling']; config.transports = ['xhr-polling'];
@ -140,47 +62,8 @@ module.exports = function(config, specificOptions) {
// Debug logging into a file, that we print out at the end of the build. // Debug logging into a file, that we print out at the end of the build.
config.loggers.push({ config.loggers.push({
type: 'file', type: 'file',
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log') filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log'),
level: config.LOG_DEBUG
}); });
} }
// Terrible hack to workaround inflexibility of log4js:
// - ignore web-server's 404 warnings,
// - ignore DEBUG logs (on Travis), we log them into a file instead.
var IGNORED_404 = [
'/favicon.ico',
'/%7B%7BtestUrl%7D%7D',
'/someSanitizedUrl',
'/{{testUrl}}'
];
var log4js = require('./node_modules/karma/node_modules/log4js');
var layouts = require('./node_modules/karma/node_modules/log4js/lib/layouts');
var originalConfigure = log4js.configure;
log4js.configure = function(log4jsConfig) {
var consoleAppender = log4jsConfig.appenders.shift();
var originalResult = originalConfigure.call(log4js, log4jsConfig);
var layout = layouts.layout(consoleAppender.layout.type, consoleAppender.layout);
log4js.addAppender(function(log) {
var msg = log.data[0];
// ignore web-server's 404s
if (log.categoryName === 'web-server' && log.level.levelStr === config.LOG_WARN &&
IGNORED_404.some(function(ignoredLog) {return msg.indexOf(ignoredLog) !== -1})) {
return;
}
// on Travis, ignore DEBUG statements
if (process.env.TRAVIS && log.level.levelStr === config.LOG_DEBUG) {
return;
}
console.log(layout(log));
});
return originalResult;
};
}; };

View file

@ -1,46 +0,0 @@
var fs = require('fs');
var http = require('http');
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
var HOSTNAME = 'localhost';
var PORTS = require('../grunt/utils').availablePorts;
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
var READY_FILE = process.env.SAUCE_CONNECT_READY_FILE;
// We need to start fake servers, otherwise the tunnel does not start.
var fakeServers = [];
var hosts = [];
PORTS.forEach(function(port) {
fakeServers.push(http.createServer(function() {}).listen(port));
hosts.push({
name: HOSTNAME,
port: port,
sslFlag: 0
});
});
var tunnel = new BrowserStackTunnel({
key: ACCESS_KEY,
hosts: hosts
});
console.log('Starting tunnel on ports', PORTS.join(', '));
tunnel.start(function(error) {
if (error) {
console.error('Can not establish the tunnel', error);
} else {
console.log('Tunnel established.');
fakeServers.forEach(function(server) {
server.close();
});
if (READY_FILE) {
fs.writeFile(READY_FILE, '');
}
}
});
tunnel.on('error', function(error) {
console.error(error);
});

View file

@ -1 +0,0 @@
node ./lib/browser-stack/start-tunnel.js &

View file

@ -61,14 +61,6 @@ module.exports = function(grunt) {
util.startKarma.call(util, this.data, false, this.async()); util.startKarma.call(util, this.data, false, this.async());
}); });
grunt.registerTask('webdriver', 'Update webdriver', function() {
util.updateWebdriver.call(util, this.async());
});
grunt.registerMultiTask('runprotractor', 'Run Protractor integration tests', function() {
util.startProtractor.call(util, this.data, this.async());
});
grunt.registerTask('collect-errors', 'Combine stripped error files', function () { grunt.registerTask('collect-errors', 'Combine stripped error files', function () {
util.collectErrors(); util.collectErrors();
}); });

View file

@ -2,26 +2,9 @@ var fs = require('fs');
var shell = require('shelljs'); var shell = require('shelljs');
var grunt = require('grunt'); var grunt = require('grunt');
var spawn = require('child_process').spawn; var spawn = require('child_process').spawn;
var semver = require('semver');
var version; var version;
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n'; var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
var PORT_MIN = 8000;
var PORT_MAX = 9999;
var TRAVIS_BUILD_NUMBER = parseInt(process.env.TRAVIS_BUILD_NUMBER, 10);
var getRandomPorts = function() {
if (!process.env.TRAVIS) {
return [9876, 9877];
}
// Generate two numbers between PORT_MIN and PORT_MAX, based on TRAVIS_BUILD_NUMBER.
return [
PORT_MIN + (TRAVIS_BUILD_NUMBER % (PORT_MAX - PORT_MIN)),
PORT_MIN + ((TRAVIS_BUILD_NUMBER + 100) % (PORT_MAX - PORT_MIN))
];
};
module.exports = { module.exports = {
init: function() { init: function() {
@ -33,85 +16,29 @@ module.exports = {
getVersion: function(){ getVersion: function(){
if (version) return version; if (version) return version;
var package = JSON.parse(fs.readFileSync('package.json', 'UTF-8')); var package = JSON.parse(fs.readFileSync('package.json', 'UTF-8'));
try { var match = package.version.match(/^([^\-]*)(?:\-(.+))?$/);
var semver = match[1].split('.');
var hash = shell.exec('git rev-parse --short HEAD', {silent: true}).output.replace('\n', '');
var gitTag = getTagOfCurrentCommit(); var fullVersion = match[1];
var semVerVersion, codeName, fullVersion;
if (gitTag) {
// tagged release
fullVersion = semVerVersion = semver.valid(gitTag);
codeName = getTaggedReleaseCodeName(gitTag);
} else {
// snapshot release
semVerVersion = getSnapshotVersion();
fullVersion = semVerVersion + '-' + getSnapshotSuffix();
codeName = 'snapshot'
}
var versionParts = semVerVersion.match(/(\d+)\.(\d+)\.(\d+)/); if (match[2]) {
fullVersion += '-';
version = { fullVersion += (match[2] == 'snapshot') ? hash : match[2];
full: fullVersion,
major: versionParts[1],
minor: versionParts[2],
dot: versionParts[3],
codename: codeName,
cdn: package.cdnVersion
};
return version;
} catch (e) {
grunt.fail.warn(e);
} }
function getTagOfCurrentCommit() { version = {
var gitTagResult = shell.exec('git describe --exact-match', {silent:true}); full: fullVersion,
var gitTagOutput = gitTagResult.output.trim(); major: semver[0],
var branchVersionPattern = new RegExp(package.branchVersion.replace('.', '\\.').replace('*', '\\d+')); minor: semver[1],
if (gitTagResult.code === 0 && gitTagOutput.match(branchVersionPattern)) { dot: semver[2].replace(/rc\d+/, ''),
return gitTagOutput; codename: package.codename,
} else { cdn: package.cdnVersion
return null; };
}
}
function getTaggedReleaseCodeName(tagName) { return version;
var tagMessage = shell.exec('git cat-file -p '+ tagName +' | grep "codename"', {silent:true}).output;
var codeName = tagMessage && tagMessage.match(/codename\((.*)\)/)[1];
if (!codeName) {
throw new Error("Could not extract release code name. The message of tag "+tagName+
" must match '*codename(some release name)*'");
}
return codeName;
}
function getSnapshotVersion() {
var oldTags = shell.exec('git tag -l v'+package.branchVersion, {silent:true}).output.trim().split('\n');
// ignore non semver versions.
oldTags = oldTags.filter(function(version) {
return version && semver.valid(version);
});
if (oldTags.length) {
oldTags.sort(semver.compare);
semVerVersion = oldTags[oldTags.length-1];
if (semVerVersion.indexOf('-') !== -1) {
semVerVersion = semver.inc(semVerVersion, 'prerelease');
} else {
semVerVersion = semver.inc(semVerVersion, 'patch');
}
} else {
semVerVersion = semver.valid(package.branchVersion.replace(/\*/g, '0'));
}
return semVerVersion;
}
function getSnapshotSuffix() {
var jenkinsBuild = process.env.TRAVIS_BUILD_NUMBER || process.env.BUILD_NUMBER || 'local';
var hash = shell.exec('git rev-parse --short HEAD', {silent: true}).output.replace('\n', '');
return 'build.'+jenkinsBuild+'+sha.'+hash;
}
}, },
@ -136,38 +63,6 @@ module.exports = {
}, },
updateWebdriver: function(done){
var p = spawn('node', ['node_modules/protractor/bin/webdriver-manager', 'update']);
p.stdout.pipe(process.stdout);
p.stderr.pipe(process.stderr);
p.on('exit', function(code){
if(code !== 0) grunt.fail.warn('Webdriver failed to update');
done();
});
},
startProtractor: function(config, done){
var sauceUser = grunt.option('sauceUser');
var sauceKey = grunt.option('sauceKey');
var tunnelIdentifier = grunt.option('capabilities.tunnel-identifier');
var sauceBuild = grunt.option('capabilities.build');
var args = ['node_modules/protractor/bin/protractor', config];
if (sauceUser) args.push('--sauceUser=' + sauceUser);
if (sauceKey) args.push('--sauceKey=' + sauceKey);
if (tunnelIdentifier) args.push('--capabilities.tunnel-identifier=' + tunnelIdentifier);
if (sauceBuild) args.push('--capabilities.build=' + sauceBuild);
var p = spawn('node', args);
p.stdout.pipe(process.stdout);
p.stderr.pipe(process.stderr);
p.on('exit', function(code){
if(code !== 0) grunt.fail.warn('Protractor test(s) failed. Exit code: ' + code);
done();
});
},
wrap: function(src, name){ wrap: function(src, name){
src.unshift('src/' + name + '.prefix'); src.unshift('src/' + name + '.prefix');
src.push('src/' + name + '.suffix'); src.push('src/' + name + '.suffix');
@ -217,7 +112,7 @@ module.exports = {
var processed = src var processed = src
.replace(/"NG_VERSION_FULL"/g, NG_VERSION.full) .replace(/"NG_VERSION_FULL"/g, NG_VERSION.full)
.replace(/"NG_VERSION_MAJOR"/, NG_VERSION.major) .replace(/"NG_VERSION_MAJOR"/, NG_VERSION.major)
.replace(/"NG_VERSION_MINOR"/, NG_VERSION.minor) .replace(/"NG_VERSION_ MINOR"/, NG_VERSION.minor)
.replace(/"NG_VERSION_DOT"/, NG_VERSION.dot) .replace(/"NG_VERSION_DOT"/, NG_VERSION.dot)
.replace(/"NG_VERSION_CDN"/, NG_VERSION.cdn) .replace(/"NG_VERSION_CDN"/, NG_VERSION.cdn)
.replace(/"NG_VERSION_CODENAME"/, NG_VERSION.codename); .replace(/"NG_VERSION_CODENAME"/, NG_VERSION.codename);
@ -273,7 +168,7 @@ module.exports = {
var mapFile = minFile + '.map'; var mapFile = minFile + '.map';
var mapFileName = mapFile.match(/[^\/]+$/)[0]; var mapFileName = mapFile.match(/[^\/]+$/)[0];
var errorFileName = file.replace(/\.js$/, '-errors.json'); var errorFileName = file.replace(/\.js$/, '-errors.json');
var versionNumber = this.getVersion().full; var versionNumber = this.getVersion().number;
shell.exec( shell.exec(
'java ' + 'java ' +
this.java32flags() + ' ' + this.java32flags() + ' ' +
@ -400,7 +295,5 @@ module.exports = {
}, },
// see http://saucelabs.com/docs/connect#localhost // see http://saucelabs.com/docs/connect#localhost
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876], sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876]
// pseudo-random port numbers for BrowserStack
availablePorts: getRandomPorts()
}; };

View file

@ -2,6 +2,6 @@
# Wait for Connect to be ready before exiting # Wait for Connect to be ready before exiting
while [ ! -f $BROWSER_PROVIDER_READY_FILE ]; do while [ ! -f $SAUCE_CONNECT_READY_FILE ]; do
sleep .5 sleep .5
done done

View file

@ -36,8 +36,8 @@ ARGS=""
if [ ! -z "$TRAVIS_JOB_NUMBER" ]; then if [ ! -z "$TRAVIS_JOB_NUMBER" ]; then
ARGS="$ARGS --tunnel-identifier $TRAVIS_JOB_NUMBER" ARGS="$ARGS --tunnel-identifier $TRAVIS_JOB_NUMBER"
fi fi
if [ ! -z "$BROWSER_PROVIDER_READY_FILE" ]; then if [ ! -z "$SAUCE_CONNECT_READY_FILE" ]; then
ARGS="$ARGS --readyfile $BROWSER_PROVIDER_READY_FILE" ARGS="$ARGS --readyfile $SAUCE_CONNECT_READY_FILE"
fi fi

View file

@ -1,49 +1,43 @@
{ {
"name": "angularjs", "name": "angularjs",
"branchVersion": "1.2.*", "version": "1.2.0",
"cdnVersion": "1.2.11", "cdnVersion": "1.2.0-rc.3",
"codename": "timely-delivery",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/angular/angular.js.git" "url": "https://github.com/angular/angular.js.git"
}, },
"devDependencies": { "devDependencies": {
"grunt": "~0.4.2", "grunt": "~0.4.1",
"grunt-bump": "~0.0.13",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-jshint": "~0.7.2",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-jscs-checker": "~0.3.2",
"grunt-merge-conflict": "~0.0.1",
"grunt-parallel": "~0.3.1",
"grunt-shell": "~0.4.0",
"load-grunt-tasks": "~0.3.0",
"bower": "~1.2.2", "bower": "~1.2.2",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-copy": "~0.4.1",
"jasmine-node": "~1.11.0", "jasmine-node": "~1.11.0",
"q": "~0.9.2", "q": "~0.9.2",
"q-io": "~1.10.6", "q-fs": "~0.1.36",
"qq": "~0.3.5", "qq": "~0.3.5",
"shelljs": "~0.2.6", "shelljs": "~0.2.6",
"karma": "0.11.12", "karma": "~0.11",
"karma-jasmine": "0.1.5", "karma-jasmine": "~0.1.0",
"karma-chrome-launcher": "0.1.2", "karma-chrome-launcher": "~0.1.0",
"karma-firefox-launcher": "0.1.3", "karma-firefox-launcher": "~0.1.0",
"karma-ng-scenario": "0.1.0", "karma-ng-scenario": "~0.1.0",
"karma-junit-reporter": "0.2.1", "karma-junit-reporter": "git://github.com/karma-runner/karma-junit-reporter#karma-0.11",
"karma-sauce-launcher": "0.2.0", "karma-sauce-launcher": "~0.1.1",
"karma-script-launcher": "0.1.0", "karma-script-launcher": "~0.1.0",
"karma-browserstack-launcher": "0.0.7",
"protractor": "~0.17.0",
"yaml-js": "~0.0.8", "yaml-js": "~0.0.8",
"marked": "0.2.9", "marked": "~0.2.9",
"rewire": "1.1.3", "rewire": "1.1.3",
"grunt-jasmine-node": "~0.1.0",
"grunt-parallel": "~0.3.1",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-merge-conflict": "~0.0.1",
"promises-aplus-tests": "~1.3.2", "promises-aplus-tests": "~1.3.2",
"grunt-shell": "~0.4.0",
"semver": "~2.1.0", "semver": "~2.1.0",
"lodash": "~2.1.0", "lodash": "~2.1.0"
"browserstacktunnel-wrapper": "~1.1.1"
}, },
"licenses": [ "licenses": [
{ {
@ -51,5 +45,7 @@
"url": "https://github.com/angular/angular.js/blob/master/LICENSE" "url": "https://github.com/angular/angular.js/blob/master/LICENSE"
} }
], ],
"dependencies": {} "dependencies": {
"grunt-contrib-jshint": "~0.6.4"
}
} }

View file

@ -1,31 +0,0 @@
exports.config = {
allScriptsTimeout: 11000,
specs: [
'build/docs/ptore2e/**/*.js',
'test/e2e/docsAppE2E.js'
],
capabilities: {
'browserName': 'chrome'
},
baseUrl: 'http://localhost:8000/build/docs/',
framework: 'jasmine',
onPrepare: function() {
// Disable animations so e2e tests run more quickly
var disableNgAnimate = function() {
angular.module('disableNgAnimate', []).run(function($animate) {
$animate.enabled(false);
});
};
browser.addMockModule('disableNgAnimate', disableNgAnimate);
},
jasmineNodeOpts: {
defaultTimeoutInterval: 30000
}
};

34
release-commit.sh Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
function catch_errors() {
echo "ERROR. That's life."
exit 1
}
trap catch_errors ERR
TMP_FILE='changelog.tmp'
CHANGELOG_FILE='CHANGELOG.md'
echo "Getting current version..."
VERSION=`./version.js --current`
echo "Generating changelog..."
./changelog.js $VERSION $TMP_FILE
cat $CHANGELOG_FILE >> $TMP_FILE
mv -f $TMP_FILE $CHANGELOG_FILE
echo "Updating version..."
./version.js --remove-snapshot
echo "CONFIRM TO COMMIT"
read WHATEVER
echo "Creating commit..."
git commit version.yaml CHANGELOG.md -m "chore(relase): cutting the v$VERSION release"
echo "Creating tag..."
git tag "v$VERSION"

View file

@ -1,54 +0,0 @@
#!/bin/bash
# Script for updating angular-phonecat repo from current local build.
echo "#################################"
echo "## Update angular-phonecat ###"
echo "#################################"
ARG_DEFS=(
"--action=(prepare|publish)"
"[--no-test=(true|false)]"
)
function init {
TMP_DIR=$(resolveDir ../../tmp)
BUILD_DIR=$(resolveDir ../../build)
REPO_DIR=$TMP_DIR/angular-phonecat
NEW_VERSION=$(cat $BUILD_DIR/version.txt)
}
function prepare {
echo "-- Cloning angular-phonecat"
git clone git@github.com:angular/angular-phonecat.git $REPO_DIR
#
# copy the files from the build
#
echo "-- Updating angular-phonecat"
cd $REPO_DIR
./scripts/private/update-angular.sh $BUILD_DIR
# Test
if [[ $NO_TEST != "true" ]]; then
./scripts/private/test-all.sh
fi
# Generate demo
./scripts/private/snapshot-web.sh
git checkout gh-pages
git pull
rm -r step*
mv angular-phonecat-snapshots-web/step* .
git add step*
git commit -am "Angular $NEW_VERSION release"
}
function publish {
cd $REPO_DIR
echo "-- Pushing angular-phonecat"
git push origin master -f --tags
git push origin gh-pages -f
}
source $(dirname $0)/../utils.inc

View file

@ -1,44 +0,0 @@
#!/bin/bash
# Script for updating angular-seed repo from current local build.
echo "#################################"
echo "## Update angular-seed ###"
echo "#################################"
ARG_DEFS=(
"--action=(prepare|publish)"
"[--no-test=(true|false)]"
)
function init {
TMP_DIR=$(resolveDir ../../tmp)
BUILD_DIR=$(resolveDir ../../build)
REPO_DIR=$TMP_DIR/angular-seed
NEW_VERSION=$(cat $BUILD_DIR/version.txt)
}
function prepare {
echo "-- Cloning angular-seed"
git clone git@github.com:angular/angular-seed.git $REPO_DIR
#
# copy the files from the build
#
echo "-- Updating angular-seed"
cd $REPO_DIR
./scripts/update-angular.sh $BUILD_DIR
# Test
if [[ $NO_TEST != "true" ]]; then
./scripts/test-all.sh
fi
}
function publish {
cd $REPO_DIR
echo "-- Pushing angular-seed"
git push origin master
}
source $(dirname $0)/../utils.inc

Some files were not shown because too many files have changed in this diff Show more