Compare commits

..

83 Commits

Author SHA1 Message Date
Brian Ford fef0cfc837 chore(CHANGELOG.md): add changelog for 1.2.16 2014-04-03 14:42:19 -07:00
Bobdina 5de845506b chore(build): make version-info.js run on windows
Replaced grep with match
Windows operating systems do not have grep by default

Closes #6912.
2014-04-03 13:47:46 -07:00
Matias Niemelä 38ea542662 fix($animate): ensure the CSS driver properly works with SVG elements
The default CSS driver in ngAnimate directly uses node.className when reading
the CSS class string on the given element. While this works fine with standard
HTML DOM elements, SVG elements have their own DOM property. By switching to use
node.getAttribute, ngAnimate can extract the element's className value without
throwing an exception.

When using jQuery over jqLite, ngAnimate will not properly handle SVG elements
for an animation. This is because jQuery doesn't process SVG elements within it's
DOM operation code by default. To get this to work, simply include the jquery.svg.js
JavaScript file into your application.

Closes #6030
2014-04-03 15:48:08 -04:00
Igor Minar 2db66f5b69 fix(Scope): revert the __proto__ cleanup as that could cause regressions
When a async task interacts with a scope that has been destroyed already
and if it interacts with a property that is prototypically inherited from
some parent scope then resetting proto would make these inherited properties
inaccessible and would result in NPEs
2014-04-03 12:40:42 -07:00
Brian Ford 55fe6d6331 fix(ngClass): handle ngClassOdd/Even affecting the same classes
The basic approach is to introduce a new elt.data() called $classCounts that keeps
track of how many times ngClass, ngClassEven, or ngClassOdd tries to add a given class.
The class is added only when the count goes from 0 to 1, and removed only when the
count hits 0.

To avoid duplicating work, some of the logic for checking which classes
to add/remove move into this directive and the directive calls $animate.

Closes #5271
2014-04-03 11:58:29 -07:00
Andreas Krummsdorf f4c08fee85 style(loader.js): correct JSDoc tags of the params of the function module(name, requires, configFn)
This will improve the hints for IDE's which support the Google Closure Compiler (e.g. Webstorm)
2014-04-03 09:39:29 -07:00
Pascal Precht 71bc451f95 docs(guide): fix link in "Complementary libraries" section
the link to `angular-translate` is outdated. this commit fixes it.
2014-04-03 09:27:51 -07:00
Igor Minar 7e4e696ec3 fix(Scope): more scope clean up on $destroy to minimize leaks
Due to a known V8 memory leak[1] we need to perform extra cleanup to make it easier
for GC to collect this scope object.

V8 leaks are due to strong references from optimized code (fixed in M34) and inline
caches (fix in works). Inline caches are caches that the virtual machine builds on the
fly to speed up property access for javascript objects. These caches contain strong
references to objects so under certain conditions this can create a leak.

The reason why these leaks are extra bad for Scope instances is that scopes hold on
to ton of stuff, so when a single scope leaks, it makes a ton of other stuff leak.

This change removes references to objects that might be holding other big
objects. This means that even if the destroyed scope leaks, the child scopes
should not leak because we are not explicitly holding onto them.

Additionally in  theory we should also help make the current scope eligible for GC
by changing properties of the current Scope object.

I was able to manually verify that this fixes the problem for the following
example app: http://plnkr.co/edit/FrSw6SCEVODk02Ljo8se

Given the nature of the problem I'm not 100% sure that this will work around
the V8 problem in scenarios common for Angular apps, but I guess it's better
than nothing.

This is a second attempt to enhance the cleanup, the first one failed  and was
reverted because it was too aggressive and caused problems for existing apps.
See: #6897

[1] V8 bug: https://code.google.com/p/v8/issues/detail?id=2073

Closes #6794
Closes #6856
Closes #6968
2014-04-03 00:25:48 -07:00
Stephanie Nacios Staub 6da44a4410 docs(tutorial): update instructions for running tests in step 2
Fixing outdated instructions on how to run the test

Closes #6972
2014-04-03 01:02:37 -04:00
Igor Minar a81195c6ca chore(rootScopeSpec): fix a typo in spec description 2014-04-02 21:25:36 -07:00
Yiling Lu 102a3201c1 docs(tutorial): remove reference to old webserver script
script/web-server.js is not present anymore. This doc might be referencing a previous version of the
code. Currently the only way to start the server seems to be using "npm start".

Closes #6966
2014-04-03 00:00:30 -04:00
Jonathan Woodard 59244a7776 docs(guide/bootstrap): remove extra call to angular.module()
There was an extra call to angular.module() not being used in 'getter' mode. While this doesn't
break the demo app, it does look kind of weird, so lets toss it.

Closes #6969
2014-04-02 23:57:16 -04:00
Tobias Bosch 293cb1fc5d docs(ngForm): clarify the purpose of ngForm
Related to #6704 and #2513.
2014-04-02 17:23:42 -07:00
Alexander Harding 81395ac298 test($compile): add tests for <option> or <optgroup> tags as root template nodes 2014-04-02 19:43:08 -04:00
Caitlin Potter ba66c4f3cc refactor(jqLite): IE8-support fixes
ddb8081982 refactors jqLite, and removes support
for IE8. This patch extends this to prevent the CL from breaking IE8, and to
remain suitable for the 1.2.x branch.

Closes #6963
2014-04-02 19:41:28 -04:00
Caitlin Potter 4ea57e7e96 refactor(jqLite): make HTML-parsing constructor more robust
Previously, the jqLite constructor was limited and would be unable to circumvent many of the HTML5
spec's "allowed content" policies for various nodes. This led to complicated and gross hacks around
this in the HTML compiler.

This change refactors these hacks by simplifying them, and placing them in jqLite rather than in
$compile, in order to better support these things, and simplify code.

While the new jqLite constructor is still not even close to as robust as jQuery, it should be more
than suitable enough for the needs of the framework, while adding minimal code.

Closes #6941
Closes #6958
2014-04-02 19:40:56 -04:00
Tero Parviainen 6e420ff28d fix($parse): mark constant unary minus expressions as constant
Previously, constant numbers with a unary minus sign were not treated as constants. This fix corrects
this behaviour, and may provide a small performance boost for certain applications, due to constant
watches being automatically unregistered after their first listener call.

Closes #6932
2014-04-02 10:07:44 -04:00
Julie 5393814756 docs(tutorial): update tutorial steps to discuss protractor
Closes #6940
2014-04-02 08:47:46 -04:00
b9chris fab59e7515 docs($location): fix link to Developer Guide for "Using $location"
Closes #6946
2014-04-02 08:09:45 -04:00
Igor Minar 553c252d5c revert: fix(Scope): aggressively clean up scope on $destroy to minimize leaks
This reverts commit f552f25171.

The commit is causing regressions.

Closes #6897
2014-04-01 16:43:25 -07:00
Peter Bacon Darwin e145a8df72 docs(tutorial): update to match changes to phonecat 2014-04-01 18:22:47 +01:00
Peter Bacon Darwin b49d0cc6e7 docs(css): ensure all type-hints have a background color
If the type of a type-hint was not recognized, say a "Promise", then
the background color was left as white.  Given that the default
foreground color is also white, this meant that such type-hints were
invisible.

Closes #6934
2014-04-01 13:51:15 +01:00
Peter Bacon Darwin 97b171ecb2 chore(grunt): add jscs task to test task
It is too easy to forget to check jscs for things like trailing whitespace
before pushing commits, such as simple doc changes.  This then breaks the
build and is messy.  Adding jscs to the test task gives people a slightly
better chance of catching these before pushing.
2014-04-01 13:51:15 +01:00
Joseph Orbegoso Pea 245de33c00 docs(guide/bootstrap): add note about ngApp and manual bootstrap 2014-03-31 16:58:34 -07:00
Igor Minar 8d4d437e8c fix(Scope): aggressively clean up scope on $destroy to minimize leaks
Due to a known V8 memory leak[1] we need to perform extra cleanup to make it easier
for GC to collect this scope object.

The theory is that the V8 leaks are due to inline caches which are caches
built on the fly to speed up property access for javascript objects.

By cleaning the scope object and removing all properties, we clean up ICs
as well and so no leaks occur.

I was able to manually verify that this fixes the problem for the following
example app: http://plnkr.co/edit/FrSw6SCEVODk02Ljo8se?p=preview

Given the nature of the problem I'm not 100% sure that this will work around
the V8 problem in scenarios common for Angular apps, but I guess it's better
than nothing.

[1] V8 bug: https://code.google.com/p/v8/issues/detail?id=2073

Closes #6794
Closes #6856
2014-03-28 17:23:41 -04:00
mrmrs 7287dbf71d chore(docs): remove px declaration from x,y coordinates in header svg 2014-03-28 17:07:20 -04:00
sgrebnov da88449f25 fix(doc-gen): Run Gulp on Windows too
Using node_module/.bin/gulp will enable to gulp command to run
both on Windows and Linux. In its current form, the default action of
executing a Javascript file on Windows does not use node.
Requires quotes around the command to correctly resolve path on Windows

Closes #6346
2014-03-28 17:07:13 -04:00
Diego Algorta eaf1f8546d docs(faq): fix link to Closure Library
The previous link throws a 404.
2014-03-28 17:07:03 -04:00
John K. Paul 20d926cc45 docs(guide/directive): fix broken link
Fix broken internal link in directive documentation.

Closes #6802
2014-03-28 17:06:52 -04:00
Sekib Omazic 9ae9c1c0da docs(error/ng/btstrpd): fix typo in error page
Minimal typo fix

Closes #6803
2014-03-28 17:06:16 -04:00
Matias Niemelä 8a5972461c chore($animate): fix broken IE8 test 2014-03-28 13:27:30 -04:00
Matias Niemelä 35d635cbcb fix($animate): prevent cancellation timestamp from being too far in the future
Closes #6748
2014-03-28 12:26:13 -04:00
David I. Lehn db2a4c04d6 docs($sce): fix typo.
Closes #6882
2014-03-27 20:28:33 -04:00
tamakisquare 0d62257c5f docs(guide/filter): mention that filters can be used in directives
The doc mentions filters can be used in services and controllers but directives
aren't mentioned. This could lead to confusion for beginners.
2014-03-27 15:37:29 -07:00
Uri Goldshtein f911b84aef docs(guide): add ngStorage to specific topics 2014-03-27 14:31:07 -07:00
jim lyndon 32c09c1d19 feat($http): add xhr statusText to completeRequest callback
Makes xhr status text accessible is $http success/error callback.
See www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-statustext

Closes #2335
Closes #2665
Closes #6713
2014-03-27 17:09:04 -04:00
ChrisRose 26064375ca docs(filter/orderBy): fixed typo 2014-03-27 13:53:49 -07:00
Alex Sanford 7a294369ab docs(ngResource): clarify behaviour of $promise
Closes #6753
2014-03-27 16:36:37 -04:00
winkler1 fbab287ea2 docs(ngShowHide): fix typo 'hrml' -> 'html'
Typo 'hrml'

Oops!

Closes #6874
2014-03-27 14:39:14 -04:00
Narretz 254dcee93d docs(guide/scope): fix links to $interpolate
Closes #6877
2014-03-27 14:33:49 -04:00
William Bagayoko 69e5c369d7 chore(docs): remove unneeded Bootstrap/jQuery files from distribution 2014-03-27 12:33:02 +00:00
wbyoko c0ccbb7b6a docs(error/index): add header
Closes #6849
2014-03-26 17:23:19 -07:00
wbyoko b87713687e docs(misc/index): add header; general links
Closes #6850
2014-03-26 17:23:19 -07:00
wbyoko 950ffb5a84 docs(misc/started): add header
Closes #6851
2014-03-26 17:23:19 -07:00
Narretz 259003056d docs($compile): add note about recursive compilation in templates
Closes #3079
Closes #6869
2014-03-26 16:43:11 -07:00
Tobias Bosch fedc4194d9 chore(release): simplify scripts so that they can be tested locally
The `git fetch --all` resulted in an error if in the local `.gitconfig`
a remote was configured that does not exist in the bower/code.anguarjs.org
repositories (e.g. "remote "upstream-prs"").
2014-03-26 16:28:26 -07:00
Tobias Bosch 16862705e1 chore(release): remove after CDN script
The homepage (angularjs.org) and the docs now calculate the
current cdn version on every build, so there is no need
for an after-cdn script.
2014-03-26 16:28:19 -07:00
Tobias Bosch c694c96e4c chore(release): calculate the cdnVersion on every build
The CDN version of angular is now calculated on every build,
by looking at the tags in angular/angular.js, sorting them
by semver and checking against ajax.googleapis.com which
one is available.
2014-03-26 16:28:02 -07:00
Tobias Bosch 5ac8a6e74a chore(release): don't update phonecat and seed during a release
This is no more needed as phonecat and seed are using bower
now to get the angular version.
2014-03-26 16:26:54 -07:00
Matias Niemelä 0e5106ec2c fix($animate): run CSS animations before JS animations to avoid style inheritance
If a JS animation is run before a CSS animation then the JS animation may end up writing style
data to the element. If any transition or animation style data is written then it may end up
being accidentally inherited into the CSS animation hanlder that ngAnimate uses. This may result
in an unexpected outcome due to the tweaks and hacks that the CSS handler places on the element.
If the CSS animation is run before the JS animation then, if there are no transitions on the style
attribute nor within the global CSS on the page then nothing will happen and the JS animation can
work as expected.

Closes #6675
2014-03-26 12:13:50 -04:00
Igor Minar 9091b77fe6 docs(guide/unit-testing): fix link 2014-03-26 03:50:49 -07:00
Alex Miller 849f998be3 docs(guide/migration): clarify some confusing points
Closes #6756
2014-03-26 03:41:21 -07:00
Nikita Tovstoles 8bc77b68b3 docs(guide/unit-testing): recommend pre-compiling templates
quite a few folks struggle with how to test directives with external templates.
karma-ng-html2js-preprocessor provides an easy solution but the issues is not
raised in the docs.
2014-03-25 17:58:32 -07:00
Brian Ford 95bd046881 docs(guide/filter): fix example style
* use -Controller suffix
* use array annotations
2014-03-25 17:37:08 -07:00
Brian Ford bfce9126e1 docs(guide/directive): fix example style
* use -Controller suffix
* use array annotations
2014-03-25 17:37:01 -07:00
Brian Ford bf2264e2aa docs(error/$injector/unpr): use Controller suffix 2014-03-25 17:13:40 -07:00
Brian Ford 5ced7b20ff docs(guide/controller): use -Controller suffix
Previously, the convention was to end controllers with -Ctrl.
The new convention is to use -Controller
2014-03-25 17:09:15 -07:00
David Pope 114cf9e418 docs($rootScope.Scope): link to angular.equals in Scope.$watch docs 2014-03-25 16:17:24 -07:00
jfortunato 1b61b73f28 docs(tutorial/step_02): fix typo 2014-03-25 15:58:04 -07:00
Brian Ford d657d63a21 docs(ngEventDirs): link to info on $event
Closes #6724
2014-03-25 15:55:37 -07:00
Brian Ford c5bb3a9098 docs(guide/expression): add section on $event 2014-03-25 15:55:26 -07:00
Brian Ford 77edce5ded docs(angular.bootstrap): fix param type to DOMElement 2014-03-25 14:29:01 -07:00
Adam Bradley f37cd4e4ef docs(css): Add background to .type-hint-domelement
`.type-hint-domelement` does not have a background color assigned to it.
DOM element type hints are now proudly displayed with CadetBlue.
2014-03-25 14:29:01 -07:00
Igor Minar 2acc91098b docs(errors/$injector/nomod): add info about forgetting to load the module file
Closes #3752
2014-03-25 14:12:04 -07:00
Caitlin Potter 5b1c89931e docs($cacheFactory): document cache instance methods
These were apparently entirely undocumented. I'm not sure if they're intended
to be private, but in case they're not, I've written some initial docs for them
2014-03-25 13:35:36 -07:00
Patrice Chalin 99a2ad381b chore(CONTRIBUTING): merge relevant updates from angular.dart
Back port changes to angular.dart `CONTRIBUTING.md`, as suggested by
@vicb.
2014-03-25 13:31:01 -07:00
Trevor Ewen 300263abec docs($cacheFactory): add example 2014-03-25 13:28:00 -07:00
Wesley Cho b0bcf18892 docs($compile): add controllerAs example 2014-03-25 13:08:58 -07:00
Emma Guo ea3b6310c9 docs(README): use svg badge 2014-03-25 13:00:59 -07:00
Brian Ford 764a3beecc docs(guide/migration): add header 2014-03-25 12:54:02 -07:00
Brian Ford a603330e7a docs(guide/concepts): improve formatting and clarity 2014-03-25 12:48:23 -07:00
Brian Ford 1f842b1c63 docs(guide/e2e-testing): improve formatting and clarity 2014-03-25 12:48:14 -07:00
Brian Ford 3b09f1bf4e docs(guide/ie): fix header formatting 2014-03-25 12:48:07 -07:00
Brian Ford 561ddc9ff1 docs(guide/i18n): improve content and formatting 2014-03-25 12:25:44 -07:00
Brian Ford 512ecf8f1b docs(guide/ie): note dropping IE8 in 1.3 2014-03-25 11:47:16 -07:00
Uri Goldshtein 6636f1d03f docs(guide): add ui-router to complementary libraries 2014-03-25 14:06:47 -04:00
Teddy Wing 7cccb8b777 docs(ngAnimate): change "&#64" to "@" symbol
Previously, we had problems with code that contained symbols that looked
like jsdoc directives.  This has now been fixed so we can convert these
HTML character codes back to @ signs.

Closes #6822
Closes #6826
2014-03-25 06:55:42 +00:00
Renat Yakubov a275d539f9 fix(filter.ngdoc): Check if "input" variable is defined
By default, "greeting" textfield in this example is prepopulated with "hello" text, but it's pretty easy to copy just filter code to use it in your app. If your textfield is empty while app loads, you'll get an error: "Error: [$interpolate:interr] Can't interpolate: Reverse: {{greeting|reverse}} TypeError: Cannot read property 'length' of undefined". To prevent this, we should check "input" variable, and proceed only in case it is defined.

Closes #6819.
2014-03-24 16:11:28 -07:00
Tobias Bosch 17fa2468bc chore(release): update cdn version 2014-03-24 16:11:27 -07:00
Luke Eller fb2ae5660e docs(guides/directive): add (') to contraction
add apostrophe (') to contraction
2014-03-24 19:09:02 -04:00
cgwyllie 2c4b3573cc docs($http): fix auth default headers example 2014-03-24 19:00:24 -04:00
Caitlin Potter b2363e3102 fix(input): don't perform HTML5 validation on updated model-value
Running html5-validation immediately after model-value is updated is incorrect, because the view
has not updated, and HTML5 constraint validation has not adjusted.

Closes #6796
Closes #6806
2014-03-24 10:57:42 -04:00
Peter Bacon Darwin edfca4c769 chore(npm-shrinkwrap): update dgeni-packages dependency 2014-03-22 22:36:05 +00:00
79 changed files with 1491 additions and 848 deletions
+46
View File
@@ -1,3 +1,49 @@
<a name="1.2.16"></a>
# 1.2.16 badger-enumeration (2014-04-03)
## Bug Fixes
- **$animate:**
- ensure the CSS driver properly works with SVG elements
([38ea5426](https://github.com/angular/angular.js/commit/38ea542662b2b74703d583e3a637d65369fc26eb),
[#6030](https://github.com/angular/angular.js/issues/6030))
- prevent cancellation timestamp from being too far in the future
([35d635cb](https://github.com/angular/angular.js/commit/35d635cbcbdc20f304781655f3563111afa6567f),
[#6748](https://github.com/angular/angular.js/issues/6748))
- run CSS animations before JS animations to avoid style inheritance
([0e5106ec](https://github.com/angular/angular.js/commit/0e5106ec2ccc8596c589b89074d3b27d27bf395a),
[#6675](https://github.com/angular/angular.js/issues/6675))
- **$parse:** mark constant unary minus expressions as constant
([6e420ff2](https://github.com/angular/angular.js/commit/6e420ff28d9b3e76ac2c3598bf3797540ef8a1d3),
[#6932](https://github.com/angular/angular.js/issues/6932))
- **Scope:**
- revert the __proto__ cleanup as that could cause regressions
([2db66f5b](https://github.com/angular/angular.js/commit/2db66f5b695a06cff62a52e55e55d1a0a25eec2f))
- more scope clean up on $destroy to minimize leaks
([7e4e696e](https://github.com/angular/angular.js/commit/7e4e696ec3adf9d6fc77a7aa7e0909a9675fd43a),
[#6794](https://github.com/angular/angular.js/issues/6794), [#6856](https://github.com/angular/angular.js/issues/6856), [#6968](https://github.com/angular/angular.js/issues/6968))
- aggressively clean up scope on $destroy to minimize leaks
([8d4d437e](https://github.com/angular/angular.js/commit/8d4d437e8cd8d7cebab5d9ae5c8bcfeef2118ce9),
[#6794](https://github.com/angular/angular.js/issues/6794), [#6856](https://github.com/angular/angular.js/issues/6856))
- **filter.ngdoc:** Check if "input" variable is defined
([a275d539](https://github.com/angular/angular.js/commit/a275d539f9631d6ec64d03814b3b09420e6cf1ee),
[#6819](https://github.com/angular/angular.js/issues/6819))
- **input:** don't perform HTML5 validation on updated model-value
([b2363e31](https://github.com/angular/angular.js/commit/b2363e31023df8240113f68b4e01d942f8009b60),
[#6796](https://github.com/angular/angular.js/issues/6796), [#6806](https://github.com/angular/angular.js/issues/6806))
- **ngClass:** handle ngClassOdd/Even affecting the same classes
([55fe6d63](https://github.com/angular/angular.js/commit/55fe6d6331e501325c2658df8995dcc083fc4ffb),
[#5271](https://github.com/angular/angular.js/issues/5271))
## Features
- **$http:** add xhr statusText to completeRequest callback
([32c09c1d](https://github.com/angular/angular.js/commit/32c09c1d195fcb98f6e29fc7e554a867f4762301),
[#2335](https://github.com/angular/angular.js/issues/2335), [#2665](https://github.com/angular/angular.js/issues/2665), [#6713](https://github.com/angular/angular.js/issues/6713))
<a name="v1.2.15"></a>
# v1.2.15 beer-underestimating (2014-03-21)
+35 -29
View File
@@ -50,7 +50,7 @@ Comment on an issue to let others know what you're working on, or create a new i
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
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.
@@ -92,16 +92,19 @@ Before you submit your pull request consider the following guidelines:
git checkout -b my-fix-branch master
```
* 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`):
* Create your patch, **including appropriate test cases**.
* Follow our [Coding Rules](#coding-rules).
* Run the full Angular test suite, as described in the [developer documentation][dev-doc],
and ensure that all tests pass.
* Commit your changes using a descriptive commit message that follows our
[commit message conventions](#commit-message-format) and passes our commit message presubmit hook
`validate-commit-msg.js`. Adherence to the [commit message conventions](#commit-message-format)
is required because release notes are automatically generated from these messages.
```shell
git commit -a
```
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
* Build your changes locally to ensure all the tests pass
@@ -109,15 +112,17 @@ Before you submit your pull request consider the following guidelines:
grunt test
```
* Push your branch to Github:
* 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
repository to update the Pull Request:
* In GitHub, send a pull request to `angular:master`.
* If we suggest changes then
* Make the required updates.
* Re-run the Angular test suite to ensure tests are still passing.
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
```shell
git rebase master -i
@@ -126,10 +131,12 @@ Before you submit your pull request consider the following guidelines:
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
#### After your pull request is merged
After your pull request is merged, you can safely delete your branch and pull the changes
from the main (upstream) repository:
* Delete the remote branch on Github:
* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
```shell
git push origin --delete my-fix-branch
@@ -245,24 +252,23 @@ You can find out more detailed information about contributing in the
[github]: https://github.com/angular/angular.js
[Google Closure I18N library]: https://code.google.com/p/closure-library/source/browse/closure/goog/i18n/
[list]: https://groups.google.com/forum/?fromgroups#!forum/angular
[contribute]: http://docs.angularjs.org/misc/contribute
[stackoverflow]: http://stackoverflow.com/questions/tagged/angularjs
[groups]: https://groups.google.com/forum/?fromgroups#!forum/angular
[angular-dev]: https://groups.google.com/forum/?fromgroups#!forum/angular-dev
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
[plunker]: http://plnkr.co/edit
[jsfiddle]: http://jsfiddle.net/
[ngDocs]: https://github.com/angular/angular.js/wiki/Writing-AngularJS-Documentation
[unit-testing]: http://docs.angularjs.org/guide/dev_guide.unit-testing
[js-style-guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
[contributing]: http://docs.angularjs.org/misc/contribute
[individual-cla]: http://code.google.com/legal/individual-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#
[github-pr-helper]: https://chrome.google.com/webstore/detail/github-pr-helper/mokbklfnaddkkbolfldepnkfmanfhpen
[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
[contribute]: http://docs.angularjs.org/misc/contribute
[contributing]: http://docs.angularjs.org/misc/contribute
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
[github]: https://github.com/angular/angular.js
[groups]: https://groups.google.com/forum/?fromgroups#!forum/angular
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
[js-style-guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
[jsfiddle]: http://jsfiddle.net/
[list]: https://groups.google.com/forum/?fromgroups#!forum/angular
[ngDocs]: https://github.com/angular/angular.js/wiki/Writing-AngularJS-Documentation
[plunker]: http://plnkr.co/edit
[stackoverflow]: http://stackoverflow.com/questions/tagged/angularjs
[unit-testing]: http://docs.angularjs.org/guide/dev_guide.unit-testing
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/CONTRIBUTING.md?pixel)](https://github.com/igrigorik/ga-beacon)
+2 -2
View File
@@ -10,7 +10,7 @@ module.exports = function(grunt) {
grunt.loadTasks('lib/grunt');
var NG_VERSION = versionInfo.currentVersion;
NG_VERSION.cdn = versionInfo.currentPackage.cdnVersion;
NG_VERSION.cdn = versionInfo.cdnVersion;
var dist = 'angular-'+ NG_VERSION.full;
//global beforeEach
@@ -279,7 +279,7 @@ module.exports = function(grunt) {
//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', 'jscs', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
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:modules', 'Run the Karma module tests with Karma', ['tests:modules']);
+1 -1
View File
@@ -1,4 +1,4 @@
AngularJS [![Build Status](https://travis-ci.org/angular/angular.js.png?branch=master)](https://travis-ci.org/angular/angular.js)
AngularJS [![Build Status](https://travis-ci.org/angular/angular.js.svg?branch=master)](https://travis-ci.org/angular/angular.js)
=========
AngularJS lets you write client-side web applications as if you had a smarter browser. It lets you
+6 -1
View File
@@ -422,6 +422,7 @@ iframe.example {
.type-hint {
display:inline-block;
background: gray;
}
.variables-matrix .type-hint {
@@ -470,6 +471,10 @@ iframe.example {
background: rgb(90, 84, 189);
}
.type-hint-domelement {
background: rgb(95, 158, 160);
}
.runnable-example-frame {
width:100%;
height:300px;
@@ -563,7 +568,7 @@ h4 {
}
.return-arguments td:first-child {
width:100px;
width:100px;
}
ul.methods > li,
+15 -8
View File
@@ -3,24 +3,31 @@
@fullName Module Unavailable
@description
This error occurs when trying to "re-open" a module that has not yet been defined.
This error occurs when you declare a dependency on a module that isn't defined anywhere or hasn't
been loaded in the current browser context.
When you receive this error, check that the name of the module in question is correct and that the
file in which this module is defined has been loaded (either via `<script>` tag, loader like
require.js, or testing harness like karma).
A less common reason for this error is trying to "re-open" a module that has not yet been defined.
To define a new module, call {@link angular.module angular.module} with a name
and an array of dependent modules, like so:
```
```js
// When defining a module with no module dependencies,
// the requires array should be defined and empty.
// the array of dependencies should be defined and empty.
var myApp = angular.module('myApp', []);
```
To retrieve a reference to the same module for further configuration, call
`angular.module` without the `requires` array.
`angular.module` without the array argument.
```
```js
var myApp = angular.module('myApp');
```
Calling `angular.module` without the `requires` array when the module has not yet
been defined causes this error to be thrown. To fix it, define your module with
a name and an empty array, as in the first example above.
Calling `angular.module` without the array of dependencies when the module has not yet been defined
causes this error to be thrown. To fix it, define your module with a name and an empty array, as in
the first example above.
+2 -2
View File
@@ -9,7 +9,7 @@ correctly. For example:
```
angular.module('myApp', [])
.controller('myCtrl', ['myService', function (myService) {
.controller('MyController', ['myService', function (myService) {
// Do something with myService
}]);
```
@@ -20,7 +20,7 @@ sure each dependency is defined will fix the problem.
```
angular.module('myApp', [])
.service('myService', function () { /* ... */ })
.controller('myCtrl', ['myService', function (myService) {
.controller('MyController', ['myService', function (myService) {
// Do something with myService
}]);
```
+2
View File
@@ -2,6 +2,8 @@
@name Error Reference
@description
# Error Reference
Use the Error Reference manual to find information about error conditions in
your AngularJS app. Errors thrown in production builds of AngularJS will log
links to this site on the console.
+1 -1
View File
@@ -20,7 +20,7 @@ application.
</html>
```
Note that for bootrapping purposes, the `<html>` element is the same as `document`, so the following
Note that for bootstrapping purposes, the `<html>` element is the same as `document`, so the following
will also throw an error.
```
+26 -13
View File
@@ -7,6 +7,7 @@
This page explains the Angular initialization process and how you can manually initialize Angular
if necessary.
## Angular `<script>` Tag
This example shows the recommended path for integrating Angular with what we call automatic
@@ -79,7 +80,6 @@ If the {@link ng.directive:ngApp `ng-app`} directive is found then Angular will:
## Manual Initialization
If you need to have more control over the initialization process, you can use a manual
bootstrapping method instead. Examples of when you'd need to do this include using script loaders
or the need to perform an operation before Angular compiles a page.
@@ -88,24 +88,36 @@ Here is an example of manually initializing Angular:
```html
<!doctype html>
<html xmlns:ng="http://angularjs.org">
<body>
Hello {{'World'}}!
<script src="http://code.angularjs.org/angular.js"></script>
<script>
angular.element(document).ready(function() {
angular.module('myApp', []);
angular.bootstrap(document, ['myApp']);
});
</script>
</body>
<html>
<body>
Hello {{'World'}}!
<script src="http://code.angularjs.org/angular.js"></script>
<script>
angular.module('myApp', [])
.controller('MyController', ['$scope', function ($scope) {
$scope.greetMe = 'World';
}]);
angular.element(document).ready(function() {
angular.bootstrap(document, ['myApp']);
});
</script>
</body>
</html>
```
Note that we have provided the name of our application module to be loaded into the injector as the second
Note that we provided the name of our application module to be loaded into the injector as the second
parameter of the {@link angular.bootstrap} function. Notice that `angular.bootstrap` will not create modules
on the fly. You must create any custom {@link guide/module modules} before you pass them as a parameter.
You should call `angular.bootstrap()` *after* you've loaded or defined your modules.
You cannot add controllers, services, directives, etc after an application bootstraps.
<div class="alert alert-warning">
**Note:** You should not use the ng-app directive when manually bootstrapping your app.
</div>
This is the sequence that your code should follow:
1. After the page and all of the code is loaded, find the root element of your AngularJS
@@ -114,6 +126,7 @@ This is the sequence that your code should follow:
2. Call {@link angular.bootstrap} to {@link compiler compile} the element into an
executable, bi-directionally bound application.
## Deferred Bootstrap
This feature enables tools like Batarang and test runners to
+12 -12
View File
@@ -2,10 +2,10 @@
@name Conceptual Overview
@description
There are some concepts within Angular that you should understand before creating your first application.
This section touches all important parts of Angular really quickly using a simple example.
However, it won't explain all details.
For a more in-depth explanation, have a look at the {@link tutorial/ tutorial}.
# Conceptual Overview
This section briefly touches on all of the important parts of AngularJS using a simple example.
For a more in-depth explanation, see the {@link tutorial/ tutorial}.
| Concept | Description |
|------------------|------------------------------------------|
@@ -25,7 +25,7 @@ For a more in-depth explanation, have a look at the {@link tutorial/ tutorial}.
|{@link concepts#service Service} | reusable business logic independent of views |
# A first example: Data binding
## A first example: Data binding
In the following example we will build a form to calculate the costs of an invoice in different currencies.
@@ -97,7 +97,7 @@ recalculated and the DOM is updated with their values.
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
# 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
different currencies and also pay the invoice.
@@ -181,7 +181,7 @@ The following graphic shows how everything works together after we introduced th
<img style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding2.png">
# 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
is a good practice to move view independent logic from the controller into a so called
@@ -297,7 +297,7 @@ Angular uses this array syntax to define the dependencies so that the DI also wo
the code, which will most probably rename the argument name of the controller constructor function
to something shorter like `a`.
# Accessing the backend
## Accessing the backend
Let's finish our example by fetching the exchange rates from the Yahoo Finance API.
The following example shows how this is done with Angular:
@@ -379,8 +379,8 @@ The following example shows how this is done with Angular:
</example>
What changed?
Our `currencyConverter` service of the `finance` module now uses the
{@link ng.$http $http} service, a builtin service provided by Angular
for accessing the backend. It is a wrapper around [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
and [JSONP](http://en.wikipedia.org/wiki/JSONP) transports. Details can be found in the api docs of that service.
Our `currencyConverter` service of the `finance` module now uses the {@link ng.$http `$http`}, a
built-in service provided by Angular for accessing a server backend. `$http` is a wrapper around
[`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
and [JSONP](http://en.wikipedia.org/wiki/JSONP) transports.
+22 -22
View File
@@ -134,7 +134,7 @@ string "very". Depending on which button is clicked, the `spice` model is set to
<example module="spicyApp1">
<file name="index.html">
<div ng-controller="SpicyCtrl">
<div ng-controller="SpicyController">
<button ng-click="chiliSpicy()">Chili</button>
<button ng-click="jalapenoSpicy()">Jalapeño</button>
<p>The food is {{spice}} spicy!</p>
@@ -143,7 +143,7 @@ string "very". Depending on which button is clicked, the `spice` model is set to
<file name="app.js">
var myApp = angular.module('spicyApp1', []);
myApp.controller('SpicyCtrl', ['$scope', function($scope){
myApp.controller('SpicyController', ['$scope', function($scope) {
$scope.spice = 'very';
$scope.chiliSpicy = function() {
@@ -160,9 +160,9 @@ string "very". Depending on which button is clicked, the `spice` model is set to
Things to notice in the example above:
- The `ng-controller` directive is used to (implicitly) create a scope for our template, and the
scope is augmented (managed) by the `SpicyCtrl` Controller.
- `SpicyCtrl` is just a plain JavaScript function. As an (optional) naming convention the name
starts with capital letter and ends with "Ctrl" or "Controller".
scope is augmented (managed) by the `SpicyController` Controller.
- `SpicyController` is just a plain JavaScript function. As an (optional) naming convention the name
starts with capital letter and ends with "Controller" or "Controller".
- Assigning a property to `$scope` creates or updates the model.
- 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
@@ -175,7 +175,7 @@ previous example.
<example module="spicyApp2">
<file name="index.html">
<div ng-controller="SpicyCtrl">
<div ng-controller="SpicyController">
<input ng-model="customSpice">
<button ng-click="spicy('chili')">Chili</button>
<button ng-click="spicy(customSpice)">Custom spice</button>
@@ -185,18 +185,18 @@ previous example.
<file name="app.js">
var myApp = angular.module('spicyApp2', []);
myApp.controller('SpicyCtrl', ['$scope', function($scope){
myApp.controller('SpicyController', ['$scope', function($scope) {
$scope.customSpice = "wasabi";
$scope.spice = 'very';
$scope.spicy = function(spice){
$scope.spicy = function(spice) {
$scope.spice = spice;
};
}]);
</file>
</example>
Notice that the `SpicyCtrl` Controller now defines just one method called `spicy`, which takes one
Notice that the `SpicyController` 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
constant `'chili'` in the binding for the first button and a model property `customSpice` (bound to an
input box) in the second button.
@@ -213,13 +213,13 @@ more information about scope inheritance.
<example module="scopeInheritance">
<file name="index.html">
<div class="spicy">
<div ng-controller="MainCtrl">
<div ng-controller="MainController">
<p>Good {{timeOfDay}}, {{name}}!</p>
<div ng-controller="ChildCtrl">
<div ng-controller="ChildController">
<p>Good {{timeOfDay}}, {{name}}!</p>
<div ng-controller="GrandChildCtrl">
<div ng-controller="GrandChildController">
<p>Good {{timeOfDay}}, {{name}}!</p>
</div>
</div>
@@ -234,14 +234,14 @@ more information about scope inheritance.
</file>
<file name="app.js">
var myApp = angular.module('scopeInheritance', []);
myApp.controller('MainCtrl', ['$scope', function($scope){
myApp.controller('MainController', ['$scope', function($scope) {
$scope.timeOfDay = 'morning';
$scope.name = 'Nikki';
}]);
myApp.controller('ChildCtrl', ['$scope', function($scope){
myApp.controller('ChildController', ['$scope', function($scope) {
$scope.name = 'Mattie';
}]);
myApp.controller('GrandChildCtrl', ['$scope', function($scope){
myApp.controller('GrandChildController', ['$scope', function($scope) {
$scope.timeOfDay = 'evening';
$scope.name = 'Gingerbreak Baby';
}]);
@@ -252,11 +252,11 @@ Notice how we nested three `ng-controller` directives in our template. This will
scopes being created for our view:
- The root scope
- The `MainCtrl` scope, which contains `timeOfDay` and `name` properties
- The `ChildCtrl` scope, which inherits the `timeOfDay` property but overrides (hides) the `name`
- The `MainController` scope, which contains `timeOfDay` and `name` properties
- The `ChildController` scope, which inherits the `timeOfDay` property but overrides (hides) the `name`
property from the previous
- The `GrandChildCtrl` scope, which overrides (hides) both the `timeOfDay` property defined in `MainCtrl`
and the `name` property defined in `ChildCtrl`
- The `GrandChildController` scope, which overrides (hides) both the `timeOfDay` property defined in `MainController`
and the `name` property defined in `ChildController`
Inheritance works with methods in the same way as it does with properties. So in our previous
examples, all of the properties could be replaced with methods that return string values.
@@ -316,11 +316,11 @@ describe('state', function() {
beforeEach(inject(function($rootScope, $controller) {
mainScope = $rootScope.$new();
$controller('MainCtrl', {$scope: mainScope});
$controller('MainController', {$scope: mainScope});
childScope = mainScope.$new();
$controller('ChildCtrl', {$scope: childScope});
$controller('ChildController', {$scope: childScope});
grandChildScope = childScope.$new();
$controller('GrandChildCtrl', {$scope: grandChildScope});
$controller('GrandChildController', {$scope: grandChildScope});
}));
it('should have over and selected', function() {
+48 -47
View File
@@ -43,13 +43,13 @@ determines when to use a given directive.
In the following example, we say that the `<input>` element **matches** the `ngModel` directive.
```javascript
```html
<input ng-model="foo">
```
The following also **matches** `ngModel`:
```javascript
```html
<input data-ng:model="foo">
```
@@ -70,12 +70,12 @@ Here are some equivalent examples of elements that match `ngBind`:
<example module="docsBindExample">
<file name="script.js">
angular.module('docsBindExample', [])
.controller('Ctrl1', function Ctrl1($scope) {
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 October 4, 1947)';
});
}]);
</file>
<file name="index.html">
<div ng-controller="Ctrl1">
<div ng-controller="Controller">
Hello <input ng-model='name'> <hr/>
<span ng-bind="name"></span> <br/>
<span ng:bind="name"></span> <br/>
@@ -86,7 +86,7 @@ Here are some equivalent examples of elements that match `ngBind`:
</file>
<file name="protractorTest.js">
it('should show off bindings', function() {
expect(element(by.css('div[ng-controller="Ctrl1"] span[ng-bind]')).getText())
expect(element(by.css('div[ng-controller="Controller"] span[ng-bind]')).getText())
.toBe('Max Karl Ernst Ludwig Planck (April 23, 1858 October 4, 1947)');
});
</file>
@@ -218,12 +218,12 @@ Let's create a directive that simply replaces its contents with a static templat
<example module="docsSimpleDirective">
<file name="script.js">
angular.module('docsSimpleDirective', [])
.controller('Ctrl', function($scope) {
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
})
}])
.directive('myCustomer', function() {
return {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
@@ -231,7 +231,7 @@ Let's create a directive that simply replaces its contents with a static templat
});
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div ng-controller="Controller">
<div my-customer></div>
</div>
</file>
@@ -257,12 +257,12 @@ using `templateUrl` instead:
<example module="docsTemplateUrlDirective">
<file name="script.js">
angular.module('docsTemplateUrlDirective', [])
.controller('Ctrl', function($scope) {
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
})
}])
.directive('myCustomer', function() {
return {
templateUrl: 'my-customer.html'
@@ -270,7 +270,7 @@ using `templateUrl` instead:
});
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div ng-controller="Controller">
<div my-customer></div>
</div>
</file>
@@ -302,12 +302,12 @@ Let's change our directive to use `restrict: 'E'`:
<example module="docsRestrictDirective">
<file name="script.js">
angular.module('docsRestrictDirective', [])
.controller('Ctrl', function($scope) {
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
})
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
@@ -317,7 +317,7 @@ Let's change our directive to use `restrict: 'E'`:
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div ng-controller="Controller">
<my-customer></my-customer>
</div>
</file>
@@ -358,18 +358,18 @@ re-use such a directive:
<example module="docsScopeProblemExample">
<file name="script.js">
angular.module('docsScopeProblemExample', [])
.controller('NaomiCtrl', function($scope) {
.controller('NaomiController', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
})
.controller('IgorCtrl', function($scope) {
}])
.controller('IgorController', ['$scope', function($scope) {
$scope.customer = {
name: 'Igor',
address: '123 Somewhere'
};
})
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
@@ -378,11 +378,11 @@ re-use such a directive:
});
</file>
<file name="index.html">
<div ng-controller="NaomiCtrl">
<div ng-controller="NaomiController">
<my-customer></my-customer>
</div>
<hr>
<div ng-controller="IgorCtrl">
<div ng-controller="IgorController">
<my-customer></my-customer>
</div>
</file>
@@ -400,10 +400,10 @@ we call an **isolate scope**. To do this, we can use a directive's `scope` optio
<example module="docsIsolateScopeDirective">
<file name="script.js">
angular.module('docsIsolateScopeDirective', [])
.controller('Ctrl', function($scope) {
.controller('Controller', ['$scope', function($scope) {
$scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
$scope.igor = { name: 'Igor', address: '123 Somewhere' };
})
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
@@ -415,7 +415,7 @@ we call an **isolate scope**. To do this, we can use a directive's `scope` optio
});
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
<hr>
<my-customer info="igor"></my-customer>
@@ -473,11 +473,11 @@ within our directive's template:
<example module="docsIsolationExample">
<file name="script.js">
angular.module('docsIsolationExample', [])
.controller('Ctrl', function($scope) {
.controller('Controller', ['$scope', function($scope) {
$scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
$scope.vojta = { name: 'Vojta', address: '3456 Somewhere Else' };
})
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
@@ -489,7 +489,7 @@ within our directive's template:
});
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
</div>
</file>
@@ -510,7 +510,7 @@ that you explicitly pass in.
<div class="alert alert-warning">
**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
See the {@link guide/directive#isolating-the-scope-of-a-directive
"Isolating the Scope of a Directive"} section for more information about isolate scopes.
</div>
@@ -543,10 +543,10 @@ We also want to remove the `$interval` if the directive is deleted so we don't i
<example module="docsTimeDirective">
<file name="script.js">
angular.module('docsTimeDirective', [])
.controller('Ctrl2', function($scope) {
.controller('Controller', ['$scope', function($scope) {
$scope.format = 'M/d/yy h:mm:ss a';
})
.directive('myCurrentTime', function($interval, dateFilter) {
}])
.directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) {
function link(scope, element, attrs) {
var format,
@@ -574,10 +574,10 @@ We also want to remove the `$interval` if the directive is deleted so we don't i
return {
link: link
};
});
}]);
</file>
<file name="index.html">
<div ng-controller="Ctrl2">
<div ng-controller="Controller">
Date format: <input ng-model="format"> <hr/>
Current time is: <span my-current-time="format"></span>
</div>
@@ -619,9 +619,9 @@ To do this, we need to use the `transclude` option.
<example module="docsTransclusionDirective">
<file name="script.js">
angular.module('docsTransclusionDirective', [])
.controller('Ctrl', function($scope) {
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
})
}])
.directive('myDialog', function() {
return {
restrict: 'E',
@@ -631,7 +631,7 @@ To do this, we need to use the `transclude` option.
});
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div ng-controller="Controller">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
</file>
@@ -650,9 +650,9 @@ that redefines `name` as `Jeff`. What do you think the `{{name}}` binding will r
<example module="docsTransclusionExample">
<file name="script.js">
angular.module('docsTransclusionExample', [])
.controller('Ctrl', function($scope) {
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
})
}])
.directive('myDialog', function() {
return {
restrict: 'E',
@@ -666,7 +666,7 @@ that redefines `name` as `Jeff`. What do you think the `{{name}}` binding will r
});
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div ng-controller="Controller">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
</file>
@@ -701,7 +701,7 @@ own behavior to it.
<example module="docsIsoFnBindExample">
<file name="script.js">
angular.module('docsIsoFnBindExample', [])
.controller('Ctrl', function($scope, $timeout) {
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
$scope.name = 'Tobias';
$scope.hideDialog = function () {
$scope.dialogIsHidden = true;
@@ -709,7 +709,7 @@ own behavior to it.
$scope.dialogIsHidden = false;
}, 2000);
};
})
}])
.directive('myDialog', function() {
return {
restrict: 'E',
@@ -722,7 +722,7 @@ own behavior to it.
});
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div ng-controller="Controller">
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
Check out the contents, {{name}}!
</my-dialog>
@@ -737,7 +737,7 @@ own behavior to it.
</example>
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 it's registered.
We saw earlier how to use `=attr` in the `scope` option, but in the above example, we're using
`&attr` instead. The `&` binding allows a directive to trigger evaluation of an expression in
@@ -747,7 +747,8 @@ callback functions to directive behaviors.
When the user clicks the `x` in the dialog, the directive's `close` function is called, thanks to
`ng-click.` This call to `close` on the isolated scope actually evaluates the expression
`hideDialog()` in the context of the original scope, thus running `Ctrl`'s `hideDialog` function.
`hideDialog()` in the context of the original scope, thus running `Controller`'s `hideDialog`
function.
<div class="alert alert-success">
**Best Practice:** use `&attr` in the `scope` option when you want your directive
@@ -766,8 +767,8 @@ element?
<example module="dragModule">
<file name="script.js">
angular.module('dragModule', []).
directive('myDraggable', function($document) {
angular.module('dragModule', [])
.directive('myDraggable', ['$document', function($document) {
return function(scope, element, attr) {
var startX = 0, startY = 0, x = 0, y = 0;
@@ -801,7 +802,7 @@ element?
$document.unbind('mouseup', mouseup);
}
};
});
}]);
</file>
<file name="index.html">
<span my-draggable>Drag ME</span>
+58 -50
View File
@@ -3,10 +3,12 @@
@name E2E Testing
@description
**Angular Scenario Runner is in maintenance mode - If you're starting a new Angular project,
consider using [Protractor](https://github.com/angular/protractor).**
<div class="alert alert-danger">
**Note:** Angular Scenario Runner is depricated. If you're starting a new Angular project,
consider using [Protractor](https://github.com/angular/protractor).
</div>
# E2E Testing with the Angular Scenario Runner
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
verify the correctness of new features, catch bugs and notice regressions.
@@ -14,16 +16,22 @@ verify the correctness of new features, catch bugs and notice regressions.
To solve this problem, we have built an Angular Scenario Runner which simulates user interactions
that will help you verify the health of your Angular application.
# Overview
## Overview
You write scenario tests in JavaScript. These tests describe how your application should behave
given a certain interaction in a specific state. A scenario is comprised of one or more `it` blocks
(you can think of these as the requirements of your application), which in turn are made of
**commands** and **expectations**. Commands tell the Runner to do something with the application
(such as navigate to a page or click on a button), and expectations tell the Runner to assert
something about the state (such as the value of a field or the current URL). If any expectation
fails, the runner marks the `it` as "failed" and continues on to the next one. Scenarios may also
have **beforeEach** and **afterEach** blocks, which will be run before (or after) each `it` block,
regardless of whether they pass or fail.
given a certain interaction in a specific state.
A scenario is comprised of one or more `it` blocks that describe the requirements of your
application. `it` blocks are made of **commands** and **expectations**. Commands tell the Runner
to do something with the application such as navigate to a page or click on a button. Expectations
tell the Runner to assert something about the application's state, such as the value of a field or
the current URL.
If any expectation within an `it` block fails, the runner marks the `it` as "failed" and continues
on to the next block.
Scenarios may also have `beforeEach` and `afterEach` blocks, which will be run before or after
each `it` block regardless of whether the block passes or fails.
<img src="img/guide/scenario_runner.png">
@@ -54,136 +62,136 @@ the only button on the page, and then it verifies that there are 10 items listed
The API section below lists the available commands and expectations for the Runner.
# API
## API
Source: https://github.com/angular/angular.js/blob/master/src/ngScenario/dsl.js
## pause()
### `pause()`
Pauses the execution of the tests until you call `resume()` in the console (or click the resume
link in the Runner UI).
## sleep(seconds)
### `sleep(seconds)`
Pauses the execution of the tests for the specified number of `seconds`.
## browser().navigateTo(url)
### `browser().navigateTo(url)`
Loads the `url` into the test frame.
## browser().navigateTo(url, fn)
### `browser().navigateTo(url, fn)`
Loads the URL returned by `fn` into the testing frame. The given `url` is only used for the test
output. Use this when the destination URL is dynamic (that is, the destination is unknown when you
write the test).
## browser().reload()
### `browser().reload()`
Refreshes the currently loaded page in the test frame.
## browser().window().href()
### `browser().window().href()`
Returns the window.location.href of the currently loaded page in the test frame.
## browser().window().path()
### `browser().window().path()`
Returns the window.location.pathname of the currently loaded page in the test frame.
## browser().window().search()
### `browser().window().search()`
Returns the window.location.search of the currently loaded page in the test frame.
## browser().window().hash()
### `browser().window().hash()`
Returns the window.location.hash (without `#`) of the currently loaded page in the test frame.
## browser().location().url()
### `browser().location().url()`
Returns the {@link ng.$location $location.url()} of the currently loaded page in
the test frame.
## browser().location().path()
### `browser().location().path()`
Returns the {@link ng.$location $location.path()} of the currently loaded page in
the test frame.
## browser().location().search()
### `browser().location().search()`
Returns the {@link ng.$location $location.search()} of the currently loaded page
in the test frame.
## browser().location().hash()
### `browser().location().hash()`
Returns the {@link ng.$location $location.hash()} of the currently loaded page in
the test frame.
## expect(future).{matcher}
### `expect(future).{matcher}`
Asserts the value of the given `future` satisfies the `matcher`. All API statements return a
`future` object, which get a `value` assigned after they are executed. Matchers are defined using
`angular.scenario.matcher`, and they use the value of futures to run the expectation. For example:
`expect(browser().location().href()).toEqual('http://www.google.com')`. Available matchers
are presented further down this document.
## expect(future).not().{matcher}
### `expect(future).not().{matcher}`
Asserts the value of the given `future` satisfies the negation of the `matcher`.
## using(selector, label)
### `using(selector, label)`
Scopes the next DSL element selection.
## binding(name)
### `binding(name)`
Returns the value of the first binding matching the given `name`.
## input(name).enter(value)
### `input(name).enter(value)`
Enters the given `value` in the text field with the corresponding ng-model `name`.
## input(name).check()
### `input(name).check()`
Checks/unchecks the checkbox with the corresponding ng-model `name`.
## input(name).select(value)
### `input(name).select(value)`
Selects the given `value` in the radio button with the corresponding ng-model `name`.
## input(name).val()
### `input(name).val()`
Returns the current value of an input field with the corresponding ng-model `name`.
## repeater(selector, label).count()
### `repeater(selector, label).count()`
Returns the number of rows in the repeater matching the given jQuery `selector`. The `label` is
used for test output.
## repeater(selector, label).row(index)
### `repeater(selector, label).row(index)`
Returns an array with the bindings in the row at the given `index` in the repeater matching the
given jQuery `selector`. The `label` is used for test output.
## repeater(selector, label).column(binding)
### `repeater(selector, label).column(binding)`
Returns an array with the values in the column with the given `binding` in the repeater matching
the given jQuery `selector`. The `label` is used for test output.
## select(name).option(value)
### `select(name).option(value)`
Picks the option with the given `value` on the select with the given ng-model `name`.
## select(name).options(value1, value2...)
### `select(name).options(value1, value2...)`
Picks the options with the given `values` on the multi select with the given ng-model `name`.
## element(selector, label).count()
### `element(selector, label).count()`
Returns the number of elements that match the given jQuery `selector`. The `label` is used for test
output.
## element(selector, label).click()
### `element(selector, label).click()`
Clicks on the element matching the given jQuery `selector`. The `label` is used for test output.
## element(selector, label).query(fn)
### `element(selector, label).query(fn)`
Executes the function `fn(selectedElements, done)`, where selectedElements are the elements that
match the given jQuery `selector` and `done` is a function that is called at the end of the `fn`
function. The `label` is used for test output.
## element(selector, label).{method}()
### `element(selector, label).{method}()`
Returns the result of calling `method` on the element matching the given jQuery `selector`, where
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
`scrollTop`, `offset`. The `label` is used for test output.
## element(selector, label).{method}(value)
### `element(selector, label).{method}(value)`
Executes the `method` passing in `value` on the element matching the given jQuery `selector`, where
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
`scrollTop`, `offset`. The `label` is used for test output.
## element(selector, label).{method}(key)
### `element(selector, label).{method}(key)`
Returns the result of calling `method` passing in `key` on the element matching the given jQuery
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
`label` is used for test output.
## element(selector, label).{method}(key, value)
### `element(selector, label).{method}(key, value)`
Executes the `method` passing in `key` and `value` on the element matching the given jQuery
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
`label` is used for test output.
# Matchers
## Matchers
Matchers are used in combination with the `expect(...)` function as described above and can
be negated with `not()`. For instance: `expect(element('h1').text()).not().toEqual('Error')`.
@@ -221,10 +229,10 @@ expect(value).toBeLessThan(expected)
expect(value).toBeGreaterThan(expected)
```
# Example
## Example
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples.
## Conditional actions with element(...).query(fn)
### Conditional actions with element(...).query(fn)
E2E testing with angular scenario is highly asynchronous and hides a lot of complexity by
queueing actions and expectations that can handle futures. From time to time, you might need
@@ -308,6 +316,6 @@ element('.btn-danger').click();
element('.btn-danger').click();
```
# Caveats
## Caveats
`ngScenario` does not work with apps that manually bootstrap using `angular.bootstrap`. You must use the `ng-app` directive.
+45 -2
View File
@@ -2,7 +2,9 @@
@name Expressions
@description
Expressions are JavaScript-like code snippets that are usually placed in bindings such as
# Angular Expressions
Angular expressions are JavaScript-like code snippets that are usually placed in bindings such as
`{{ expression }}`.
For example, these are valid expressions in Angular:
@@ -88,7 +90,7 @@ You can try evaluating different expressions here:
</example>
# Context
## Context
Angular does not use JavaScript's `eval()` to evaluate expressions. Instead Angular's
{@link ng.$parse $parse} service processes these expressions.
@@ -154,3 +156,44 @@ You cannot write a control flow statement in an expression. The reason behind th
Angular philosophy that application logic should be in controllers, not the views. If you need a
conditional, loop, or to throw from a view expression, delegate to a JavaScript method instead.
## `$event`
Directives like {@link ng.directive:ngClick `ngClick`} and {@link ng.directive:ngFocus `ngFocus`}
expose a `$event` object within the scope of that expression.
<example module="eventExampleApp">
<file name="index.html">
<div ng-controller="EventController">
<button ng-click="clickMe($event)">Event</button>
<p><code>$event</code>: <pre> {{$event | json}}</pre></p>
<p><code>clickEvent</code>: <pre>{{clickEvent | json}}</pre></p>
</div>
</file>
<file name="script.js">
angular.module('eventExampleApp', []).
controller('EventController', ['$scope', function($scope) {
/*
* expose the event object to the scope
*/
$scope.clickMe = function(clickEvent) {
$scope.clickEvent = simpleKeys(clickEvent);
console.log(clickEvent);
};
/*
* return a copy of an object with only non-object keys
* we need this to avoid circular references
*/
function simpleKeys (original) {
return Object.keys(original).reduce(function (obj, key) {
obj[key] = typeof original[key] === 'object' ? '{ ... }' : original[key];
return obj;
}, {});
}
}]);
</file>
</example>
Note in the example above how we can pass in `$event` to `clickMe`, but how it does not show up
in `{{$event}}`. This is because `$event` is outside the scope of that binding.
+14 -14
View File
@@ -29,12 +29,12 @@ E.g. the markup `{{ 1234 | number:2 }}` formats the number 1234 with 2 decimal p
{@link ng.filter:number `number`} filter. The resulting value is `1,234.00`.
## Using filters in controllers and services
## Using filters in controllers, services, and directives
You can also use filters in controllers and services. For this, add a dependency with the name `<filterName>Filter`
to your controller or service. E.g. using the dependency `numberFilter` will inject the number filter.
The injected argument is a function that takes the value to format as first argument and filter parameters
starting with the second argument.
You can also use filters in controllers, services, and directives. For this, inject a dependency
with the name `<filterName>Filter` to your controller/service/directive. E.g. using the dependency
`numberFilter` will inject the number filter. The injected argument is a function that takes the
value to format as first argument and filter parameters starting with the second argument.
The example below uses the filter called {@link ng.filter:filter `filter`}.
This filter reduces arrays into sub arrays based on
@@ -89,9 +89,9 @@ function.
The following sample filter reverses a text string. In addition, it conditionally makes the
text upper-case.
<example module="MyReverseModule">
<example module="myReverseModule">
<file name="index.html">
<div ng-controller="Ctrl">
<div ng-controller="Controller">
<input ng-model="greeting" type="text"><br>
No filter: {{greeting}}<br>
Reverse: {{greeting|reverse}}<br>
@@ -100,9 +100,10 @@ text upper-case.
</file>
<file name="script.js">
angular.module('MyReverseModule', []).
filter('reverse', function() {
angular.module('myReverseModule', [])
.filter('reverse', function() {
return function(input, uppercase) {
input = input || '';
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
@@ -113,11 +114,10 @@ text upper-case.
}
return out;
};
});
function Ctrl($scope) {
$scope.greeting = 'hello';
}
})
.controller('Controller', ['$scope', function($scope) {
$scope.greeting = 'hello';
}]);
</file>
</example>
+73 -59
View File
@@ -2,53 +2,54 @@
@name i18n and l10n
@description
# I18n and L10n in AngularJS
# i18n and l10n
**What is i18n and l10n?**
Internationalization (i18n) is the process of developing products in such a way that they can be
localized for languages and cultures easily. Localization (l10n), is the process of adapting
applications and text to enable their usability in a particular cultural or linguistic market. For
application developers, internationalizing an application means abstracting all of the strings and
other locale-specific bits (such as date or currency formats) out of the application. Localizing an
application means providing translations and localized formats for the abstracted bits.
Internationalization, abbreviated i18n, is the process of developing products in such a way that
they can be localized for languages and cultures easily. Localization, abbreviated l10n, is the
process of adapting applications and text to enable their usability in a particular cultural or
linguistic market. For application developers, internationalizing an application means abstracting
all of the strings and other locale-specific bits (such as date or currency formats) out of the
application. Localizing an application means providing translations and localized formats for the
abstracted bits.
**What level of support for i18n/l10n is currently in Angular?**
## How does Angular support i18n/l10n?
Currently, Angular supports i18n/l10n for
[datetime](http://docs.angularjs.org/#!/api/ng.filter:date),
[number](http://docs.angularjs.org/#!/api/ng.filter:number) and
[currency](http://docs.angularjs.org/#!/api/ng.filter:currency) filters.
Angular supports i18n/l10n for {@link ng.filter:date date}, {@link ng.filter:number number} and
{@link ng.filter:currency currency} filters.
Additionally, Angular supports localizable pluralization support provided by the {@link
ng.directive:ngPluralize ngPluralize directive}.
Additionally, Angular supports localizable pluralization support through the {@link
ng.directive:ngPluralize `ngPluralize` directive}.
All localizable Angular components depend on locale-specific rule sets managed by the {@link
ng.$locale $locale service}.
ng.$locale `$locale` service}.
For readers who want to jump straight into examples, we have a few web pages that showcase how to
use Angular filters with various locale rule sets. You can find these examples either on
[Github](https://github.com/angular/angular.js/tree/master/i18n/e2e) or in the i18n/e2e folder of
Angular development package.
There a few examples that showcase how to use Angular filters with various locale rule sets in the
[`i18n/e2e` directory](https://github.com/angular/angular.js/tree/master/i18n/e2e) of the Angular
source code.
**What is a locale id?**
## What is a locale ID?
A locale is a specific geographical, political, or cultural region. The most commonly used locale
ID consists of two parts: language code and country code. For example, en-US, en-AU, zh-CN are all
valid locale IDs that have both language codes and country codes. Because specifying a country code
in locale ID is optional, locale IDs such as en, zh, and sk are also valid. See the
[ICU ](http://userguide.icu-project.org/locale) website for more information about using locale IDs.
ID consists of two parts: language code and country code. For example, `en-US`, `en-AU`, and
`zh-CN` are all valid locale IDs that have both language codes and country codes. Because
specifying a country code in locale ID is optional, locale IDs such as `en`, `zh`, and `sk` are
also valid. See the [ICU](http://userguide.icu-project.org/locale) website for more information
about using locale IDs.
## Supported locales in Angular
**Supported locales in Angular**
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
[here](https://github.com/angular/angular.js/tree/master/src/ngLocale)
# Providing locale rules to Angular
## Providing locale rules to Angular
There are two approaches to providing locale rules to Angular:
**1. Pre-bundled rule sets**
### 1. Pre-bundled rule sets
You can pre-bundle the desired locale file with Angular by concatenating the content of the
locale-specific file to the end of `angular.js` or `angular.min.js` file.
@@ -61,7 +62,7 @@ locale, you can do the following:
When the application containing `angular_de-de.js` script instead of the generic angular.js script
starts, Angular is automatically pre-configured with localization rules for the german locale.
**2. Including locale js script in index.html page**
### 2. Including a locale script in `index.html`
You can also include the locale specific js file in the index.html page. For example, if one client
requires German locale, you would serve index_de-de.html which will look something like this:
@@ -77,48 +78,61 @@ requires German locale, you would serve index_de-de.html which will look somethi
</html>
```
**Comparison of the two approaches**
Both approaches described above requires you to prepare different index.html pages or js files for
each locale that your app may be localized into. You also need to configure your server to serve
### Comparison of the two approaches
Both approaches described above require you to prepare different `index.html` pages or JavaScript
files for each locale that your app may use. You also need to configure your server to serve
the correct file that correspond to the desired locale.
However, the second approach (Including locale js script in index.html page) is likely to be slower
because an extra script needs to be loaded.
The second approach (including the locale JavaScript file in `index.html`) may be slower because
an extra script needs to be loaded.
# "Gotchas"
## Caveats
**Currency symbol "gotcha"**
Although Angular makes i18n convenient, there are several things you need to be conscious of as you
develop your app.
Angular's [currency filter](http://docs.angularjs.org/#!/api/ng.filter:currency) allows
you to use the default currency symbol from the {@link ng.$locale locale service},
or you can provide the filter with a custom currency symbol. If your app will be used only in one
locale, it is fine to rely on the default currency symbol. However, if you anticipate that viewers
in other locales might use your app, you should provide your own currency symbol to make sure the
actual value is understood.
### Currency symbol
For example, if you want to display an account balance of 1000 dollars with the following binding
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
browser will specify the locale as ja, and the balance of '¥1000.00' will be shown instead. This
will really upset your client.
Angular's {@link ng.filter:currency currency filter} allows you to use the default currency symbol
from the {@link ng.$locale locale service}, or you can provide the filter with a custom currency
symbol.
<div class="alert alert-success">
**Best Practice:** If your app will be used only in one locale, it is fine to rely on the default
currency symbol. If you anticipate that viewers in other locales might use your app, you should
explicitly provide a currency symbol.
</div>
Let's say you are writing a banking app and you want to display an account balance of 1000 dollars.
You write the following binding using the currency filter:
```html
{{ 1000 | currency }}
```
If your app is currently in the `en-US` locale, the browser will show `$1000.00`. If someone in the
Japanese locale (`ja`) views your app, their browser will show a balance of `¥1000.00` instead.
This is problematinc because $1000 is not the same as ¥1000.
In this case, you need to override the default currency symbol by providing the
[currency filter](http://docs.angularjs.org/#!/api/ng.filter:currency) with a currency symbol as
a parameter when you configure the filter, for example, {{ 1000 | currency:"USD$"}}. This way,
Angular will always show a balance of 'USD$1000' and disregard any locale changes.
{@link ng.filter:currency} currency filter with a currency symbol as a parameter.
**Translation length "gotcha"**
If we change the above to `{{ 1000 | currency:"USD$"}}`, Angular will always show a balance of
`USD$1000` regardless of locale.
Keep in mind that translated strings/datetime formats can vary greatly in length. For example,
`June 3, 1977` will be translated to Spanish as `3 de junio de 1977`. There are bound to be other
more extreme cases. Hence, when internationalizing your apps, you need to apply CSS rules
accordingly and do thorough testing to make sure UI components do not overlap.
### Translation length
Translated strings/datetime formats can vary greatly in length. For example, `June 3, 1977` will be
translated to Spanish as `3 de junio de 1977`.
**Timezones**
When internationalizing your app, you need to do thorough testing to make sure UI components behave
as expected even when their contents vary greatly in content size.
Keep in mind that Angular datetime filter uses the time zone settings of the browser. So the same
### Timezones
The Angular datetime filter uses the time zone settings of the browser. The same
application will show different time information depending on the time zone settings of the
computer that the application is running on. Neither Javascript nor Angular currently supports
computer that the application is running on. Neither JavaScript nor Angular currently supports
displaying the date with a timezone specified by the developer.
+18 -13
View File
@@ -1,25 +1,30 @@
@ngdoc overview
@name Internet Explorer Compatibility
@name Internet Explorer Compatibility
@description
# Overview
# Internet Explorer Compatibility
<div class="alert alert-warning">
**Note:** AngularJS 1.3 is dropping support for IE8. Read more about it on
[our blog](http://blog.angularjs.org/2013/12/angularjs-13-new-release-approaches.html).
AngularJS 1.2 will continue to support IE8, but the core team does not plan to spend time
addressing issues specific to IE8 or earlier.
</div>
This document describes the Internet Explorer (IE) idiosyncrasies when dealing with custom HTML
attributes and tags. Read this document if you are planning on deploying your Angular application
on IE v8.0 or earlier.
on IE8 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.
The project currently supports and will attempt to fix bugs for IE9 and above. The continuous
integration server runs all the tests against IE9, IE10, and IE11. See
[Travis CI](https://travis-ci.org/angular/angular.js) and
[ci.angularjs.org](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)
We do not run tests on IE8 and below. A subset of the AngularJS functionality may work on these
browesers, but it is up to you to test and decide whether it works for your particular app.
# Short Version
## Short Version
To make your Angular application work on IE please make sure that:
@@ -92,7 +97,7 @@ The **important** parts are:
happy.
# Long Version
## Long Version
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.
+3 -1
View File
@@ -57,6 +57,7 @@ In Angular applications, you move the job of filling page templates with data fr
* **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)
* **Visualization:** [SVG](http://gaslight.co/blog/angular-backed-svgs), [D3.js](http://www.ng-newsletter.com/posts/d3-on-angular.html)
* **Local Storage and session:** [ngStorage](https://github.com/gsklee/ngStorage)
## Tools
@@ -69,10 +70,11 @@ In Angular applications, you move the job of filling page templates with data fr
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://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/)
* **RESTful services:** [Restangular](https://github.com/mgonto/restangular)
* **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/)
* **Advanced Routing:** [UI-Router](https://github.com/angular-ui/ui-router)
## Deployment
+46 -7
View File
@@ -2,6 +2,7 @@
@name Migrating from 1.0 to 1.2
@description
# Migrating from 1.0 to 1.2
AngularJS version 1.2 introduces several breaking changes that may require changes to your
application's source code.
@@ -26,7 +27,7 @@ below should still apply, but you may want to consult the
<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#you-can-only-bind-one-expression-to You can only bind one expression to <code>*[src]</code>, <code>*[ng-src]</code> or <code>action</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>
@@ -49,6 +50,7 @@ below should still apply, but you may want to consult the
<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>
<li>{@link guide/migration#services-can-now-return-functions Services can now return functions}</li>
<li>{@link guide/migration#modifying-the-dom-outside-digest-cycle Modifying the DOM outside digest cycle}</li>
</ul>
@@ -138,17 +140,18 @@ $routeProvider.when('/Book1/:book/Chapter/:chapter/:highlight*/edit',
See [04cebcc1](https://github.com/angular/angular.js/commit/04cebcc133c8b433a3ac5f72ed19f3631778142b).
## You can only bind one expression to `*[src]` or `*[ng-src]`
## You can only bind one expression to `*[src]`, `*[ng-src]` or `action`
With the exception of `<a>` and `<img>` elements, you cannot bind more than one expression to the
`src` attribute of elements.
`src` or `action` 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.
bindings such as bindings for `iframe[src]`, `object[src]`, etc. In addition, this requirement is
enforced for `form` tags with `action` attributes.
<table class="table table-bordered code-table">
<thead>
@@ -541,9 +544,45 @@ See [1adf29af](https://github.com/angular/angular.js/commit/1adf29af13890d612868
## 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.
If you declare a scope option on a directive, that directive will have an
[isolate scope](https://github.com/angular/angular.js/wiki/Understanding-Scopes). In Angular 1.0, if a
directive with an isolate scope is used on an element, all directives on that same element have access
to the same isolate scope. For example, say we have the following directives:
```
// This directive declares an isolate scope.
.directive('isolateScope', function() {
return {
scope: {},
link: function($scope) {
console.log('one = ' + $scope.$id);
}
};
})
// This directive does not.
.directive('nonIsolateScope', function() {
return {
link: function($scope) {
console.log('two = ' + $scope.$id);
}
};
});
```
Now what happens if we use both directives on the same element?
```
<div isolate-scope non-isolate-scope></div>
```
In Angular 1.0, the nonIsolateScope directive will have access to the isolateScope directives scope. The
log statements will print the same id, because the scope is the same. But in Angular 1.2, the nonIsolateScope
will not use the same scope as isolateScope. Instead, it will inherit the parent scope. The log statements
will print different ids.
If your code depends on the Angular 1.0 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**
+3 -3
View File
@@ -388,7 +388,7 @@ user enters text into the text field.
1. the {@link ng.directive:ngModel ng-model} and {@link
ng.directive:input input} {@link guide/directive
directive} set up a `keydown` listener on the `<input>` control.
2. the {@link ng.$interpolate &#123;&#123;name&#125;&#125; } interpolation
2. the {@link ng.$interpolate interpolation}
sets up a {@link ng.$rootScope.Scope#$watch $watch} to be notified of
`name` changes.
2. During the runtime phase:
@@ -400,8 +400,8 @@ user enters text into the text field.
3. Angular applies the `name = 'X';` to the model.
4. The {@link ng.$rootScope.Scope#$digest $digest} loop begins
5. The {@link ng.$rootScope.Scope#$watch $watch} list detects a change
on the `name` property and notifies the {@link ng.$interpolate
&#123;&#123;name&#125;&#125; } interpolation, which in turn updates the DOM.
on the `name` property and notifies the {@link ng.$interpolate interpolation},
which in turn updates the DOM.
6. Angular exits the execution context, which in turn exits the `keydown` event and with it
the JavaScript execution context.
7. The browser re-renders the view with update text.
+6
View File
@@ -337,6 +337,12 @@ We inject the $compile service and $rootScope before each jasmine test. The $com
to render the aGreatEye directive. After rendering the directive we ensure that the directive has
replaced the content and "lidless, wreathed in flame, 2 times" is present.
### Testing Directives With External Templates
If your directive uses `templateUrl`, consider using
[karma-ng-html2js-preprocessor](https://github.com/karma-runner/karma-ng-html2js-preprocessor)
to pre-compile HTML templates and thus avoid having to load them over HTTP during test execution.
Otherwise you may run into issues if the test directory hierarchy differs from the application's.
## Sample project
See the [angular-seed](https://github.com/angular/angular-seed) project for an example.
+1 -1
View File
@@ -72,7 +72,7 @@ The size of the file is < 36KB compressed and minified.
### Can I use the open-source Closure Library with Angular?
Yes, you can use widgets from the [Closure Library](http://code.google.com/closure/library)
Yes, you can use widgets from the [Closure Library](https://developers.google.com/closure/library/)
in Angular.
### Does Angular use the jQuery library?
+7
View File
@@ -1,3 +1,10 @@
@ngdoc overview
@name Miscellaneous
@description
# Miscellaneous Links
- {@link misc/started Getting Started}
- {@link misc/downloading Downloading AngularJS}
- {@link misc/faq Frequently Asked Questions}
- {@link misc/contribute Building AngularJS}
+2
View File
@@ -2,6 +2,8 @@
@name Getting Started
@description
# Getting Started
We want you to have an easy time while starting to use Angular. We've put together the following steps on your path to
becoming an Angular expert.
+147 -82
View File
@@ -3,6 +3,7 @@
@step -1
@description
# PhoneCat Tutorial App
A great way to get introduced to AngularJS is to work through this tutorial, which walks you through
the construction of an AngularJS web app. The app you will build is a catalog that displays a list
@@ -12,8 +13,8 @@ details for any device.
<img class="diagram" src="img/tutorial/catalog_screen.png" width="488" height="413" alt="demo
application running in the browser">
Work through the tutorial to see how Angular makes browsers smarter — without the use of extensions
or plug-ins. As you work through the tutorial, you will:
Work through the tutorial to see how Angular makes browsers smarter — without the use of native
extensions or plug-ins. As you work through the tutorial, you will:
* See examples of how to use client-side data binding and dependency injection to build dynamic
views of data that change immediately in response to user actions.
@@ -22,15 +23,13 @@ views of data that change immediately in response to user actions.
* Learn how to use Angular services to make common web tasks, such as getting data into your app,
easier.
And all of this works in any browser without modification to the browser!
When you finish the tutorial you will be able to:
* Create a dynamic application that works in any browser.
* Create a dynamic application that works in all modern browsers.
* Define the differences between Angular and common JavaScript frameworks.
* Understand how data binding works in AngularJS.
* Use the angular-seed project to quickly boot-strap your own projects.
* Create and run tests.
* Create and run unit tests.
* Create and run end to end tests.
* Identify resources for learning more about AngularJS.
The tutorial guides you through the entire process of building a simple application, including
@@ -43,86 +42,152 @@ really digging into it. If you're looking for a shorter introduction to AngularJ
# Working with the code
You can follow this tutorial and hack on the code in either the Mac/Linux or the Windows
environment. The tutorial relies on the use of Git versioning system for source code management.
You can follow along with this tutorial and hack on the code in either the Mac/Linux or the Windows
environment. The tutorial relies on the use of the [Git][git] versioning system for source code
management.
You don't need to know anything about Git to follow the tutorial. Select one of the tabs below
and follow the instructions for setting up your computer.
<div class="tabbable" show="true">
<div class="tab-pane well" id="git-mac" title="Git on Mac/Linux">
<ol>
<li><p>You'll need Git, which you can get from
<a href="http://git-scm.com/download" title="Git site download">the Git site</a>.</p></li>
<li><p>Clone the angular-phonecat repository located at
<a href="https://github.com/angular/angular-phonecat" title="Github Phonecat Repo">Github</a> by
running the following command:</p>
<pre>git clone https://github.com/angular/angular-phonecat.git</pre>
<p>This command creates the <code>angular-phonecat</code> directory in your current directory.</p></li>
<li><p>Change your current directory to <code>angular-phonecat</code>:</p>
<pre>cd angular-phonecat</pre>
<p>The tutorial instructions, from now on, assume you are running all commands from the
<code>angular-phonecat</code> directory.</p></li>
<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/" title="NodeJS org">Node.js</a> v0.10 or better installed
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
command in a terminal window:</p></li>
<pre>node --version</pre>
<div class="alert alert-info">**Helpful note:** If you need to run a different version of
node.js in your local environment, consider installing
<a href="https://github.com/creationix/nvm" title="Node Version Manager Github Repo link">
Node Version Manager (nvm)</a>.</div>
<p>Additionally install <a href="http://karma-runner.github.io/" title="Karma site">Karma</a> and
its plugins if you don't have it already:</p>
<pre>
npm install
</pre></li>
<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>
to run <code>scripts/web-server.js</code>, a simple bundled http server.</p></li>
</ol>
</div>
<div class="tab-pane well" id="git-win" title="Git on Windows">
<ol>
<li><p>You will need Node.js and Karma to run unit tests, so please verify that you have
<a href="http://nodejs.org/" title="NodeJS site">Node.js</a> v0.10 or better installed
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
command in a terminal window:</p>
<pre>node --version</pre>
<div class="alert alert-info">**Helpful note:** If you need to run a different version of
node.js in your local environment, consider installing
<a href="https://github.com/creationix/nvm" title="Node Version Manager Github Repo link">
Node Version Manager (nvm)</a>.</div>
<p>Additionally install <a href="http://karma-runner.github.io/" title="Karma site">Karma</a>
if you don't have it already:</p>
<pre>npm install -g karma</pre>
</li>
<li><p>You'll also need Git, which you can get from
<a href="http://git-scm.com/download" title="Git site download">the Git site</a>.</p></li>
<li><p>Clone the angular-phonecat repository located at <a
href="https://github.com/angular/angular-phonecat" "Github Angular-phonecat Repo">Github</a> by running
the following command:</p><pre>git clone https://github.com/angular/angular-phonecat.git</pre>
<p>This command creates the <code>angular-phonecat</code> directory in your current directory.</p></li>
<li><p>Change your current directory to <code>angular-phonecat</code>:</p>
<pre>cd angular-phonecat</pre>
<p>The tutorial instructions assume you are running all commands from the <code>angular-phonecat</code>
directory.</p>
<p>You should run all <code>git</code> commands from Git bash.</p>
<p>Other commands like <code>test.bat</code> or <code>e2e-test.bat</code> should be 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 already
installed, you can use <code>node</code> to run <code>scripts\web-server.js</code>, a simple bundled
http server.</p></li>
</ol>
</div>
## Install Git
The last thing to do is to make sure your computer has a web browser and a good text editor installed. Now,
let's get some cool stuff done!
You'll need Git, which you can get from [the Git site][git].
<a href="tutorial/step_00" title="Next Step"><span class="btn btn-primary">Get Started!</span></a>
Clone the [angular-phonecat repository][angular-phonecat] located at GitHub by running the following
command:
```
git clone https://github.com/angular/angular-phonecat.git
```
This command creates the `angular-phonecat` directory in your current directory.
Change your current directory to `angular-phonecat`.
```
cd angular-phonecat
```
The tutorial instructions, from now on, assume you are running all commands from the
`angular-phonecat` directory.
## Install Node.js
If you want to run the built-in web-server and the test tools then you will also need
Node.js v0.10, or later.
You can download Node.js from the [node.js website][node].
You can check the version of Node.js that you have installed by running the following command:
```
node --version
```
<div class="alert alert-info"><strong>Helpful note:<strong> If you need to run a different versions of node.js
in your local environment, consider installing
<a href="https://github.com/creationix/nvm" title="Node Version Manager Github Repo link">
Node Version Manager (nvm)
</a>.
</div>
Once you have Node.js installed you can install the tool dependencies by running:
```
npm install
```
This command will download the following tools, into the `node_modules` directive:
- [Bower][bower] - client-side code package manager
- [http-server][http-server] - simple local static web server
- [Karma][karma] - unit test runner
- [protractor][protractor] - end 2 end test runner
Running `npm install` will also automatically run `bower install`, which will download the Angular
framework into the `bower_components` directory.
The project is preconfigured with a number of npm helper scripts to make it easy to run the common
tasks that you will need while developing.
## Running Development Web Server
The project is preconfigured to provide a simple static web server for hosting the application.
Start the web server by running:
```
npm start
```
Now you can browse to the application at:
```
http://localhost:8000/app/index.html
```
## Running Unit Tests
The project is preconfigured to use [Karma][karma] to run the unit tests for the application. Start
Karma by running:
```
npm test
```
This will start the Karma unit test runner, open up a Chrome browser and execute all the unit tests
in this browser. Karma will then sit and watch all the source and test JavaScript files.
Whenever one of these files changes Karma re-runs all the unit tests.
It is good to leave this running all the time as you will get immediate feedback from Karma as you
are working on the code.
## Running End to End Tests
The project is preconfigured to use [Protractor][protractor] to run the end to end tests for the
application. Set up the binaries protractor needs to run by running:
```
npm run update-webdriver
```
(You will only need to do this once) Execute the Protractor test scripts against your application
by running:
```
npm run protractor
```
This will start the Protractor end to end test runner, open up a Chrome browser and execute all the
end to end test scripts in this browser. Once the test scripts finish, the browser will close down
and Protractor will exit.
It is good to run the end to end tests whenever you make changes to the HTML views or want to check
that the application as a whole is executing correctly.
<div class="alert alert-info"><strong>Helpful note:</strong> Protractor requires that a web server is running
and serving up the application at the default URL: `http://localhost:8000/app/index.html`. You can
start the provided development server by running `npm start`.
</div>
# Get Started
Now your environment is ready, it is time to get started developing the Angular PhoneCat
application.
<a href="tutorial/step_00" title="Next Step"><span class="btn btn-primary">Step 0 - Bootstrapping</span></a>
[git]: http://git-scm.com/download
[angular-phonecat]: https://github.com/angular/angular-phonecat
[node]: http://nodejs.org/
[protractor]: https://github.com/angular/protractor
[bower]: http://bower.io/
[http-server]: https://github.com/nodeapps/http-server
[karma]: https://github.com/karma-runner/karma
+1 -1
View File
@@ -25,7 +25,7 @@ angular-seed, and run the application in the browser.
<ul>
<li><b>For node.js users:</b>
<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>npm start</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>
</ol>
+3 -3
View File
@@ -192,8 +192,8 @@ 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
`./scripts/test.sh` (if you are on Windows, run scripts\test.bat) to start the Karma server (the
config file necessary to start the server is located at `./config/karma.conf.js`).
`npm test` to start the Karma server (the config file necessary to start the server is
located at `./test/karma.conf.js`).
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.
@@ -226,7 +226,7 @@ To run the test, do the following:
Refresh your browser and verify that it says "Hello, World!".
* Update the unit test for the controler in ./tests/unit/controlersSpec.js to reflect the previous change. For example by adding:
* Update the unit test for the controller in ./tests/unit/controllersSpec.js to reflect the previous change. For example by adding:
expect(scope.name).toBe('World');
+26 -31
View File
@@ -96,18 +96,23 @@ describe('PhoneCat App', function() {
describe('Phone list view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html');
browser.get('app/index.html');
});
it('should filter the phone list as user types into the search box', function() {
expect(repeater('.phones li').count()).toBe(3);
input('query').enter('nexus');
expect(repeater('.phones li').count()).toBe(1);
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
input('query').enter('motorola');
expect(repeater('.phones li').count()).toBe(2);
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
});
});
@@ -117,20 +122,14 @@ Even though the syntax of this test looks very much like our controller unit tes
Jasmine, the end-to-end test uses APIs of {@link guide/dev_guide.e2e-testing Angular's end-to-end
test runner}.
To run the end-to-end test, open one of the following in a new browser tab:
Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
will exit after the test run and will not automatically rerun the test suite on every file change.
To rerun the test suite, execute `npm run protractor` again.
* node.js users: http://localhost:8000/test/e2e/runner.html
* users with other http servers:
`http://localhost:[port-number]/[context-path]/test/e2e/runner.html`
* casual reader: http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html
Previously we've seen how Karma can be used to execute unit tests. Well, it can also run the
end-to-end tests! Use `./scripts/e2e-test.sh` (if you are on Windows, run `scripts\e2e-test.bat`) script for that. End-to-end tests are slow, so unlike
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` or `e2e-test.bat` script again.
Note: You must ensure you've installed the karma-ng-scenario framework plugin prior to running the
`e2e-test.sh` script. You can do this by issuing `npm install` into your terminal.
Note: You must ensure you've installed the protractor and updated webdriver prior to running the
`npm run protractor`. You can do this by issuing `npm install` and `npm run update-webdriver` into
your terminal.
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
@@ -173,28 +172,24 @@ ngBindTemplate} directives, which are invisible to the user while the page is lo
```js
it('should display the current filter value within an element with id "status"',
function() {
expect(element('#status').text()).toMatch(/Current filter: \s*$/);
var statusElement = element(by.id('status'));
expect(statusElement.getText()).toMatch(/Current filter: \s*$/);
input('query').enter('nexus');
element(by.model('query')).sendKeys('nexus');
expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);
expect(statusElement.getText()).toMatch(/Current filter: nexus\s*$/);
//alternative version of the last assertion that tests just the value of the binding
using('#status').expect(binding('query')).toBe('nexus');
expect(statusElement.element(by.binding('query'))).toBe('nexus');
});
```
Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test
pass, edit the `index.html` template to add a `div` or `p` element with `id` `"status"` and content
with the `query` binding, prefixed by "Current filter:". For instance:
Re-run `npm run protractor` to see the test fail. To make the test pass, edit the `index.html`
template to add a `div` or `p` element with `id` `"status"` and content with the `query` binding,
prefixed by "Current filter:". For instance:
<div id="status">Current filter: {{query}}</div>
* Add a `pause()` statement inside of an end-to-end test and rerun it. You'll see the runner pause;
this gives you the opportunity to explore the state of your application while it is displayed in
the browser. The app is live! You can change the search query to prove it. Notice how useful this
is for troubleshooting end-to-end tests.
# Summary
+23 -15
View File
@@ -152,28 +152,36 @@ __`test/e2e/scenarios.js`:__
```js
...
it('should be possible to control phone order via the drop down select box',
function() {
//let's narrow the dataset to make the test assertions shorter
input('query').enter('tablet');
it('should be possible to control phone order via the drop down select box', function() {
expect(repeater('.phones li', 'Phone List').column('phone.name')).
toEqual(["Motorola XOOM\u2122 with Wi-Fi",
"MOTOROLA XOOM\u2122"]);
var phoneNameColumn = element.all(by.repeater('phone in phones').column('{{phone.name}}'));
var query = element(by.model('query'));
select('orderProp').option('Alphabetical');
function getNames() {
return phoneNameColumn.map(function(elm) {
return elm.getText();
});
}
expect(repeater('.phones li', 'Phone List').column('phone.name')).
toEqual(["MOTOROLA XOOM\u2122",
"Motorola XOOM\u2122 with Wi-Fi"]);
});
...
query.sendKeys('tablet'); //let's narrow the dataset to make the test assertions shorter
expect(getNames()).toEqual([
"Motorola XOOM\u2122 with Wi-Fi",
"MOTOROLA XOOM\u2122"
]);
element(by.model('orderProp')).findElement(by.css('option[value="name"]')).click();
expect(getNames()).toEqual([
"MOTOROLA XOOM\u2122",
"Motorola XOOM\u2122 with Wi-Fi"
]);
});...
```
The end-to-end test verifies that the ordering mechanism of the select box is working correctly.
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
`runner.html` to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html).
You can now rerun `npm run protractor` to see the tests run.
# Experiments
+7 -5
View File
@@ -76,9 +76,12 @@ __`test/e2e/scenarios.js`__:
```js
...
it('should render phone specific links', function() {
input('query').enter('nexus');
element('.phones li a').click();
expect(browser().location().url()).toBe('/phones/nexus-s');
var query = element(by.model('query'));
query.sendKeys('nexus');
element(by.css('.phones li a')).click();
browser.getLocationAbsUrl().then(function(url) {
expect(url.split('#')[1]).toBe('/phones/nexus-s');
});
});
...
```
@@ -86,8 +89,7 @@ __`test/e2e/scenarios.js`__:
We added a new end-to-end test to verify that the app is generating correct links to the phone
views that we will implement in the upcoming steps.
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
runner to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-6/test/e2e/runner.html).
You can now rerun `npm run protractor` to see the tests run.
# Experiments
+5 -6
View File
@@ -267,22 +267,21 @@ to various URLs and verify that the correct view was rendered.
});
...
describe('Phone detail view', function() {
describe('Phone detail view', function() {
beforeEach(function() {
browser().navigateTo('app/index.html#/phones/nexus-s');
browser.get('app/index.html#/phones/nexus-s');
});
it('should display placeholder page with phoneId', function() {
expect(binding('phoneId')).toBe('nexus-s');
expect(element(by.binding('phoneId')).getText()).toBe('nexus-s');
});
});
});
```
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
runner to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-7/test/e2e/runner.html).
You can now rerun `npm run protractor` to see the tests run.
# Experiments
+3 -4
View File
@@ -166,20 +166,19 @@ __`test/e2e/scenarios.js`:__
describe('Phone detail view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html#/phones/nexus-s');
browser.get('app/index.html#/phones/nexus-s');
});
it('should display nexus-s page', function() {
expect(binding('phone.name')).toBe('Nexus S');
expect(element(by.binding('phone.name')).getText()).toBe('Nexus S');
});
});
...
```
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
runner to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html).
You can now rerun `npm run protractor` to see the tests run.
# Experiments
+7 -8
View File
@@ -90,23 +90,22 @@ __`test/e2e/scenarios.js`:__
...
it('should display the first phone image as the main phone image', function() {
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
it('should swap main image if a thumbnail image is clicked on', function() {
element('.phone-thumbs li:nth-child(3) img').click();
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg');
element(by.css('.phone-thumbs li:nth-child(3) img')).click();
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);
element('.phone-thumbs li:nth-child(1) img').click();
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
element(by.css('.phone-thumbs li:nth-child(1) img')).click();
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
});
});
```
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
runner to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-10/test/e2e/runner.html).
You can now rerun `npm run protractor` to see the tests run.
# Experiments
+5 -5
View File
@@ -6,7 +6,7 @@ var basePackage = require('./config');
module.exports = function(config) {
var cdnUrl = "//ajax.googleapis.com/ajax/libs/angularjs/" + versionInfo.currentPackage.cdnVersion;
var cdnUrl = "//ajax.googleapis.com/ajax/libs/angularjs/" + versionInfo.cdnVersion;
var getVersion = function(component, sourceFolder, packageFile) {
sourceFolder = sourceFolder || '../bower_components';
@@ -62,7 +62,7 @@ module.exports = function(config) {
'js/docs.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/dist/css/bootstrap.css',
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.css',
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
'css/prettify-theme.css',
'css/docs.css',
@@ -97,7 +97,7 @@ module.exports = function(config) {
'js/docs.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/dist/css/bootstrap.min.css',
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
'css/prettify-theme.css',
'css/docs.css',
@@ -136,7 +136,7 @@ module.exports = function(config) {
'js/docs.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/dist/css/bootstrap.min.css',
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
'css/prettify-theme.css',
'css/docs.css',
@@ -171,7 +171,7 @@ module.exports = function(config) {
'js/docs.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/dist/css/bootstrap.min.css',
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
'css/prettify-theme.css',
'css/docs.css',
+2 -2
View File
@@ -38,11 +38,11 @@ gulp.task('build-app', function() {
gulp.task('assets', ['bower'], function() {
return merge(
gulp.src(['app/assets/**/*']).pipe(gulp.dest(outputFolder)),
copyComponent('bootstrap'),
copyComponent('bootstrap', '/dist/**/*'),
copyComponent('open-sans-fontface'),
copyComponent('lunr.js','/*.js'),
copyComponent('google-code-prettify'),
copyComponent('jquery'),
copyComponent('jquery', '/jquery.*'),
copyComponent('marked', '/**/*.js', '../node_modules', 'package.json')
);
});
+1 -1
View File
@@ -5,7 +5,7 @@
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="687px" height="176px" viewBox="0 0 687 176" overflow="visible" enable-background="new 0 0 687 176"
x="0" y="0" width="687px" height="176px" viewBox="0 0 687 176" overflow="visible" enable-background="new 0 0 687 176"
xml:space="preserve">
<defs>
</defs>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

+1 -1
View File
@@ -35,7 +35,7 @@ module.exports = function(grunt) {
grunt.registerTask('docs', 'create angular docs', function(){
var gruntProc = shelljs.exec('node_modules/gulp/bin/gulp.js --gulpfile docs/gulpfile.js');
var gruntProc = shelljs.exec('"node_modules/.bin/gulp" --gulpfile docs/gulpfile.js');
if (gruntProc.code !== 0) {
throw new Error('doc generation failed');
}
+29 -4
View File
@@ -4,7 +4,7 @@ var shell = require('shelljs');
var semver = require('semver');
var _ = require('lodash');
var currentPackage, previousVersions;
var currentPackage, previousVersions, cdnVersion;
/**
@@ -46,7 +46,8 @@ var getGitRepoInfo = function() {
* @return {String} The codename if found, otherwise null/undefined
*/
var getCodeName = function(tagName) {
var tagMessage = shell.exec('git cat-file -p '+ tagName +' | grep "codename"', {silent:true}).output;
var gitCatOutput = shell.exec('git cat-file -p '+ tagName, {silent:true}).output;
var tagMessage = gitCatOutput.match(/^.*codename.*$/mg)[0];
var codeName = tagMessage && tagMessage.match(/codename\((.*)\)/)[1];
if (!codeName) {
throw new Error("Could not extract release code name. The message of tag "+tagName+
@@ -105,10 +106,10 @@ var getPreviousVersions = function() {
// not contain all commits when cloned with git clone --depth=...
// Needed e.g. for Travis
var repo_url = currentPackage.repository.url;
var tagResults = shell.exec('git ls-remote --tags ' + repo_url + ' | grep -o -e "v[0-9].*[0-9]$"',
var tagResults = shell.exec('git ls-remote --tags ' + repo_url,
{silent: true});
if ( tagResults.code === 0 ) {
return _(tagResults.output.trim().split('\n'))
return _(tagResults.output.match(/v[0-9].*[0-9]$/mg))
.map(function(tag) {
var version = semver.parse(tag);
return version;
@@ -131,6 +132,29 @@ var getPreviousVersions = function() {
}
};
var getCdnVersion = function() {
return _(previousVersions)
.filter(function(tag) {
return semver.satisfies(tag, currentPackage.branchVersion);
})
.reverse()
.reduce(function(cdnVersion, version) {
if (!cdnVersion) {
// Note: need to use shell.exec and curl here
// as version-infos returns its result synchronously...
var cdnResult = shell.exec('curl http://ajax.googleapis.com/ajax/libs/angularjs/'+version+'/angular.min.js '+
'--head --write-out "%{http_code}" -o /dev/null -silent',
{silent: true});
if ( cdnResult.code === 0 ) {
var statusCode = cdnResult.output.trim();
if (statusCode === '200') {
cdnVersion = version;
}
}
}
return cdnVersion;
}, null);
}
/**
* Get the unstable snapshot version
@@ -179,4 +203,5 @@ var getSnapshotVersion = function() {
exports.currentPackage = currentPackage = getPackage();
exports.gitRepoInfo = gitRepoInfo = getGitRepoInfo();
exports.previousVersions = previousVersions = getPreviousVersions();
exports.cdnVersion = cdnVersion = getCdnVersion();
exports.currentVersion = getTaggedVersion() || getSnapshotVersion();
+1 -1
View File
@@ -524,7 +524,7 @@
}
},
"dgeni-packages": {
"version": "0.8.1",
"version": "0.8.2",
"dependencies": {
"lodash": {
"version": "2.4.1"
-1
View File
@@ -1,7 +1,6 @@
{
"name": "angularjs",
"branchVersion": "1.2.*",
"cdnVersion": "1.2.14",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
-54
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
-44
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
-30
View File
@@ -1,30 +0,0 @@
#!/bin/sh
# Script for updating cdnVersion of angular.js
echo "###################################"
echo "## Update angular.js cdnVersion ###"
echo "###################################"
ARG_DEFS=(
"--cdn-version=(.*)"
"--action=(prepare|publish)"
)
function init {
cd ../..
}
function prepare {
replaceJsonProp "package.json" "cdnVersion" "(.*)" "$CDN_VERSION"
git add package.json
git commit -m "chore(release): update cdn version"
}
function publish {
BRANCH=$(git rev-parse --abbrev-ref HEAD)
# push the commits to github
git push origin $BRANCH
}
source $(dirname $0)/../utils.inc
-56
View File
@@ -1,56 +0,0 @@
#!/bin/sh
# Script for updating angularjs.org repo
echo "#################################"
echo "##### Update angularjs.org ######"
echo "#################################"
ARG_DEFS=(
"--action=(prepare|publish)"
"--cdn-version=(.*)"
)
function init {
BASE_DIR=$(resolveDir ../..)
TMP_DIR=$BASE_DIR/tmp
REPO_DIR=$TMP_DIR/angularjs.org
BRANCH_PATTERN=$(readJsonProp "$BASE_DIR/package.json" "branchVersion")
BUILD_DIR=$BASE_DIR/build
}
function prepare {
echo "-- Cloning angularjs.org"
git clone git@github.com:angular/angularjs.org.git $REPO_DIR
#
# update files
#
echo "-- Updating angularjs.org"
cd $REPO_DIR
VERSION_REGEX="[-a-z0-9\.\+]+"
# Replace the version in the script links that reference the Google CDN
# e.g. <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.1/angular.js"></script>
replaceInFile "index.html" "(http:\/\/ajax.googleapis.com\/ajax\/libs\/angularjs\/)$VERSION_REGEX" "\1$CDN_VERSION"
# Replace the version in the script links that reference code.angularjs.org
# e.g. <script src="http://code.angularjs.org/1.3.0-beta.1/i18n/angular-locale_sk.js"></script>
replaceInFile "index.html" "(code\.angularjs\.org\/)$VERSION_REGEX" "\1$CDN_VERSION"
# Replace the version of the branch that we are updating
echo $BRANCH_PATTERN
echo $CDN_VERSION
replaceInFile "js/download-data.js" "branch:[ ]+'($BRANCH_PATTERN)',[ ]+version:[ ]+'$VERSION_REGEX'" "branch: '\1', version: '$CDN_VERSION'"
git add index.html
git add js/download-data.js
git commit -m "update(version): update angular version to $CDN_VERSION for branch $BRANCH_PATTERN"
}
function publish {
cd $REPO_DIR
echo "-- Pushing angularjs.org"
git push origin master
}
source $(dirname $0)/../utils.inc
-6
View File
@@ -50,12 +50,6 @@ function prepare {
if [ -f $BUILD_DIR/$repo.js ] # ignore i18l
then
echo "-- Updating files in bower-$repo"
cd $TMP_DIR/bower-$repo
git reset --hard HEAD
git checkout master
git fetch --all
git reset --hard origin/master
cd $SCRIPT_DIR
cp $BUILD_DIR/$repo.* $TMP_DIR/bower-$repo/
fi
done
-6
View File
@@ -38,12 +38,6 @@ function prepare {
#
echo "-- Updating code.angularjs.org"
mkdir $REPO_DIR/$NEW_VERSION
cd $REPO_DIR
git reset --hard HEAD
git checkout master
git fetch --all
git reset --hard origin/master
cd $SCRIPT_DIR
cp -r $BUILD_DIR/* $REPO_DIR/$NEW_VERSION/
#
-41
View File
@@ -1,41 +0,0 @@
#!/bin/sh
ARG_DEFS=(
# require the git dryrun flag so the script can't be run without
# thinking about this!
"--git-push-dryrun=(true|false)"
"--cdn-version=(.*)"
)
function init {
NG_ARGS=("$@")
if [[ ! $VERBOSE ]]; then
VERBOSE=false
fi
VERBOSE_ARG="--verbose=$VERBOSE"
}
function phase {
ACTION_ARG="--action=$1"
CDN_VERSION_ARG="--cdn-version=$CDN_VERSION"
./scripts/angular.js/publish-cdn-version.sh $ACTION_ARG $CDN_VERSION_ARG $VERBOSE_ARG
./scripts/angularjs.org/publish.sh $ACTION_ARG $CDN_VERSION_ARG $VERBOSE_ARG
}
function checkCdn {
STATUS=$(curl http://ajax.googleapis.com/ajax/libs/angularjs/$CDN_VERSION/angular.min.js --write-out '%{http_code}' -o /dev/null -silent)
if [[ $STATUS != 200 ]]; then
echo "Could not find release $CDN_VERSION on CDN"
exit 1
fi
}
function run {
cd ../..
checkCdn
phase prepare
phase publish
}
source $(dirname $0)/../utils.inc
+1 -3
View File
@@ -56,12 +56,10 @@ function phase {
../code.angularjs.org/publish.sh $ACTION_ARG $VERBOSE_ARG
../bower/publish.sh $ACTION_ARG $VERBOSE_ARG
../angular-seed/publish.sh $ACTION_ARG $VERBOSE_ARG --no-test=true
../angular-phonecat/publish.sh $ACTION_ARG $VERBOSE_ARG --no-test=true
}
function run {
# First prepare all scripts (build, test, commit, tag, ...),
# First prepare all scripts (build, commit, tag, ...),
# so we are sure everything is all right
phase prepare
# only then publish to github
+1
View File
@@ -135,6 +135,7 @@
"JQLitePrototype": false,
"addEventListenerFn": false,
"removeEventListenerFn": false,
"jqLiteIsTextNode": false,
/* apis.js */
"hashKey": false,
+1 -1
View File
@@ -1276,7 +1276,7 @@ function angularInit(element, bootstrap) {
* </file>
* </example>
*
* @param {Element} element DOM element which is the root of angular application.
* @param {DOMElement} element DOM element which is the root of angular application.
* @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
* Each item in the array should be the name of a predefined module or a (DI annotated)
* function that will be invoked by the injector as a run block.
+71 -7
View File
@@ -179,6 +179,75 @@ function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArgu
}
}
var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;
var HTML_REGEXP = /<|&#?\w+;/;
var TAG_NAME_REGEXP = /<([\w:]+)/;
var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi;
var wrapMap = {
'option': [1, '<select multiple="multiple">', '</select>'],
'thead': [1, '<table>', '</table>'],
'col': [2, '<table><colgroup>', '</colgroup></table>'],
'tr': [2, '<table><tbody>', '</tbody></table>'],
'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
'_default': [0, "", ""]
};
wrapMap.optgroup = wrapMap.option;
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;
function jqLiteIsTextNode(html) {
return !HTML_REGEXP.test(html);
}
function jqLiteBuildFragment(html, context) {
var elem, tmp, tag, wrap,
fragment = context.createDocumentFragment(),
nodes = [], i, j, jj;
if (jqLiteIsTextNode(html)) {
// Convert non-html into a text node
nodes.push(context.createTextNode(html));
} else {
tmp = fragment.appendChild(context.createElement('div'));
// Convert html into DOM nodes
tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
wrap = wrapMap[tag] || wrapMap._default;
tmp.innerHTML = '<div>&#160;</div>' +
wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
tmp.removeChild(tmp.firstChild);
// Descend through wrappers to the right content
i = wrap[0];
while (i--) {
tmp = tmp.lastChild;
}
for (j=0, jj=tmp.childNodes.length; j<jj; ++j) nodes.push(tmp.childNodes[j]);
tmp = fragment.firstChild;
tmp.textContent = "";
}
// Remove wrapper from fragment
fragment.textContent = "";
fragment.innerHTML = ""; // Clear inner HTML
return nodes;
}
function jqLiteParseHTML(html, context) {
context = context || document;
var parsed;
if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
return [context.createElement(parsed[1])];
}
return jqLiteBuildFragment(html, context);
}
/////////////////////////////////////////////
function JQLite(element) {
if (element instanceof JQLite) {
@@ -195,14 +264,9 @@ function JQLite(element) {
}
if (isString(element)) {
var div = document.createElement('div');
// Read about the NoScope elements here:
// http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
div.innerHTML = '<div>&#160;</div>' + element; // IE insanity to make NoScope elements work!
div.removeChild(div.firstChild); // remove the superfluous div
jqLiteAddNodes(this, div.childNodes);
jqLiteAddNodes(this, jqLiteParseHTML(element));
var fragment = jqLite(document.createDocumentFragment());
fragment.append(this); // detach the elements from the temporary DOM div.
fragment.append(this);
} else {
jqLiteAddNodes(this, element);
}
+2 -2
View File
@@ -72,8 +72,8 @@ function setupModuleLoader(window) {
* {@link angular.bootstrap} to simplify this process for you.
*
* @param {!string} name The name of the module to create or retrieve.
* @param {Array.<string>=} requires If specified then new module is being created. If
* unspecified then the module is being retrieved for further configuration.
<<<<<* @param {!Array.<string>=} requires If specified then new module is being created. If
>>>>>* unspecified then the module is being retrieved for further configuration.
* @param {Function} configFn Optional configuration function for the module. Same as
* {@link angular.Module#config Module#config()}.
* @returns {module} new module with the {@link angular.Module} api.
+153 -2
View File
@@ -5,7 +5,8 @@
* @name $cacheFactory
*
* @description
* Factory that constructs cache objects and gives access to them.
* Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
* them.
*
* ```js
*
@@ -37,6 +38,46 @@
* - `{void}` `removeAll()` — Removes all cached values.
* - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
*
* @example
<example module="cacheExampleApp">
<file name="index.html">
<div ng-controller="CacheController">
<input ng-model="newCacheKey" placeholder="Key">
<input ng-model="newCacheValue" placeholder="Value">
<button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
<p ng-if="keys.length">Cached Values</p>
<div ng-repeat="key in keys">
<span ng-bind="key"></span>
<span>: </span>
<b ng-bind="cache.get(key)"></b>
</div>
<p>Cache Info</p>
<div ng-repeat="(key, value) in cache.info()">
<span ng-bind="key"></span>
<span>: </span>
<b ng-bind="value"></b>
</div>
</div>
</file>
<file name="script.js">
angular.module('cacheExampleApp', []).
controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
$scope.keys = [];
$scope.cache = $cacheFactory('cacheId');
$scope.put = function(key, value) {
$scope.cache.put(key, value);
$scope.keys.push(key);
};
}]);
</file>
<file name="style.css">
p {
margin: 10px 0 3px;
}
</file>
</example>
*/
function $CacheFactoryProvider() {
@@ -56,8 +97,65 @@ function $CacheFactoryProvider() {
freshEnd = null,
staleEnd = null;
/**
* @ngdoc type
* @name $cacheFactory.Cache
*
* @description
* A cache object used to store and retrieve data, primarily used by
* {@link $http $http} and the {@link ng.directive:script script} directive to cache
* templates and other data.
*
* ```js
* angular.module('superCache')
* .factory('superCache', ['$cacheFactory', function($cacheFactory) {
* return $cacheFactory('super-cache');
* }]);
* ```
*
* Example test:
*
* ```js
* it('should behave like a cache', inject(function(superCache) {
* superCache.put('key', 'value');
* superCache.put('another key', 'another value');
*
* expect(superCache.info()).toEqual({
* id: 'super-cache',
* size: 2
* });
*
* superCache.remove('another key');
* expect(superCache.get('another key')).toBeUndefined();
*
* superCache.removeAll();
* expect(superCache.info()).toEqual({
* id: 'super-cache',
* size: 0
* });
* }));
* ```
*/
return caches[cacheId] = {
/**
* @ngdoc method
* @name $cacheFactory.Cache#put
* @function
*
* @description
* Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
* retrieved later, and incrementing the size of the cache if the key was not already
* present in the cache. If behaving like an LRU cache, it will also remove stale
* entries from the set.
*
* It will not insert undefined values into the cache.
*
* @param {string} key the key under which the cached data is stored.
* @param {*} value the value to store alongside the key. If it is undefined, the key
* will not be stored.
* @returns {*} the value stored.
*/
put: function(key, value) {
if (capacity < Number.MAX_VALUE) {
var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
@@ -76,7 +174,17 @@ function $CacheFactoryProvider() {
return value;
},
/**
* @ngdoc method
* @name $cacheFactory.Cache#get
* @function
*
* @description
* Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
*
* @param {string} key the key of the data to be retrieved
* @returns {*} the value stored.
*/
get: function(key) {
if (capacity < Number.MAX_VALUE) {
var lruEntry = lruHash[key];
@@ -90,6 +198,16 @@ function $CacheFactoryProvider() {
},
/**
* @ngdoc method
* @name $cacheFactory.Cache#remove
* @function
*
* @description
* Removes an entry from the {@link $cacheFactory.Cache Cache} object.
*
* @param {string} key the key of the entry to be removed
*/
remove: function(key) {
if (capacity < Number.MAX_VALUE) {
var lruEntry = lruHash[key];
@@ -108,6 +226,14 @@ function $CacheFactoryProvider() {
},
/**
* @ngdoc method
* @name $cacheFactory.Cache#removeAll
* @function
*
* @description
* Clears the cache object of any entries.
*/
removeAll: function() {
data = {};
size = 0;
@@ -116,6 +242,15 @@ function $CacheFactoryProvider() {
},
/**
* @ngdoc method
* @name $cacheFactory.Cache#destroy
* @function
*
* @description
* Destroys the {@link $cacheFactory.Cache Cache} object entirely,
* removing it from the {@link $cacheFactory $cacheFactory} set.
*/
destroy: function() {
data = null;
stats = null;
@@ -124,6 +259,22 @@ function $CacheFactoryProvider() {
},
/**
* @ngdoc method
* @name $cacheFactory.Cache#info
* @function
*
* @description
* Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
*
* @returns {object} an object with the following properties:
* <ul>
* <li>**id**: the id of the cache instance</li>
* <li>**size**: the number of entries kept in the cache instance</li>
* <li>**...**: any additional properties from the options object when creating the
* cache.</li>
* </ul>
*/
info: function() {
return extend({}, stats, {size: size});
}
+22 -25
View File
@@ -64,6 +64,7 @@
* restrict: 'A',
* scope: false,
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
* controllerAs: 'stringAlias',
* require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
* compile: function compile(tElement, tAttrs, transclude) {
* return {
@@ -281,6 +282,16 @@
* apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
* should be done in a linking function rather than in a compile function.
* </div>
* <div class="alert alert-warning">
* **Note:** The compile function cannot handle directives that recursively use themselves in their
* own templates or compile functions. Compiling these directives results in an infinite loop and a
* stack overflow errors.
*
* This can be avoided by manually using $compile in the postLink function to imperatively compile
* a directive's template instead of relying on automatic template compilation via `template` or
* `templateUrl` declaration or manual compilation inside the compile function.
* </div>
*
* <div class="alert alert-error">
* **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
@@ -502,8 +513,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
TABLE_CONTENT_REGEXP = /^<\s*(tr|th|td|thead|tbody|tfoot)(\s+[^>]*)?>/i;
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
@@ -1245,7 +1255,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (directive.replace) {
replaceDirective = directive;
$template = directiveTemplateContents(directiveValue);
if (jqLiteIsTextNode(directiveValue)) {
$template = [];
} else {
$template = jqLite(directiveValue);
}
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
@@ -1644,27 +1658,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
function directiveTemplateContents(template) {
var type;
template = trim(template);
if ((type = TABLE_CONTENT_REGEXP.exec(template))) {
type = type[1].toLowerCase();
var table = jqLite('<table>' + template + '</table>');
if (/(thead|tbody|tfoot)/.test(type)) {
return table.children(type);
}
table = table.children('tbody');
if (type === 'tr') {
return table.children('tr');
}
return table.children('tr').contents();
}
return jqLite('<div>' +
template +
'</div>').contents();
}
function compileTemplateUrl(directives, $compileNode, tAttrs,
$rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
var linkQueue = [],
@@ -1689,7 +1682,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
content = denormalizeTemplate(content);
if (origAsyncDirective.replace) {
$template = directiveTemplateContents(content);
if (jqLiteIsTextNode(content)) {
$template = [];
} else {
$template = jqLite(content);
}
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
+4
View File
@@ -215,6 +215,10 @@ function FormController(element, attrs, $scope, $animate) {
* does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
* sub-group of controls needs to be determined.
*
* Note: the purpose of `ngForm` is to group controls,
* but not to be a replacement for the `<form>` tag with all of its capabilities
* (e.g. posting to the server, ...).
*
* @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
* related scope, under this name.
*
-1
View File
@@ -450,7 +450,6 @@ function addNativeHtml5Validators(ctrl, validatorName, element) {
return value;
};
ctrl.$parsers.push(validator);
ctrl.$formatters.push(validator);
}
}
+81 -27
View File
@@ -2,7 +2,7 @@
function classDirective(name, selector) {
name = 'ngClass' + name;
return function() {
return ['$animate', function($animate) {
return {
restrict: 'AC',
link: function(scope, element, attr) {
@@ -20,46 +20,100 @@ function classDirective(name, selector) {
// jshint bitwise: false
var mod = $index & 1;
if (mod !== old$index & 1) {
var classes = flattenClasses(scope.$eval(attr[name]));
var classes = arrayClasses(scope.$eval(attr[name]));
mod === selector ?
attr.$addClass(classes) :
attr.$removeClass(classes);
addClasses(classes) :
removeClasses(classes);
}
});
}
function addClasses(classes) {
var newClasses = digestClassCounts(classes, 1);
attr.$addClass(newClasses);
}
function removeClasses(classes) {
var newClasses = digestClassCounts(classes, -1);
attr.$removeClass(newClasses);
}
function digestClassCounts (classes, count) {
var classCounts = element.data('$classCounts') || {};
var classesToUpdate = [];
forEach(classes, function (className) {
if (count > 0 || classCounts[className]) {
classCounts[className] = (classCounts[className] || 0) + count;
if (classCounts[className] === +(count > 0)) {
classesToUpdate.push(className);
}
}
});
element.data('$classCounts', classCounts);
return classesToUpdate.join(' ');
}
function updateClasses (oldClasses, newClasses) {
var toAdd = arrayDifference(newClasses, oldClasses);
var toRemove = arrayDifference(oldClasses, newClasses);
toRemove = digestClassCounts(toRemove, -1);
toAdd = digestClassCounts(toAdd, 1);
if (toAdd.length === 0) {
$animate.removeClass(element, toRemove);
} else if (toRemove.length === 0) {
$animate.addClass(element, toAdd);
} else {
$animate.setClass(element, toAdd, toRemove);
}
}
function ngClassWatchAction(newVal) {
if (selector === true || scope.$index % 2 === selector) {
var newClasses = flattenClasses(newVal || '');
if(!oldVal) {
attr.$addClass(newClasses);
} else if(!equals(newVal,oldVal)) {
attr.$updateClass(newClasses, flattenClasses(oldVal));
var newClasses = arrayClasses(newVal || []);
if (!oldVal) {
addClasses(newClasses);
} else if (!equals(newVal,oldVal)) {
var oldClasses = arrayClasses(oldVal);
updateClasses(oldClasses, newClasses);
}
}
oldVal = copy(newVal);
}
function flattenClasses(classVal) {
if(isArray(classVal)) {
return classVal.join(' ');
} else if (isObject(classVal)) {
var classes = [], i = 0;
forEach(classVal, function(v, k) {
if (v) {
classes.push(k);
}
});
return classes.join(' ');
}
return classVal;
}
}
};
};
function arrayDifference(tokens1, tokens2) {
var values = [];
outer:
for(var i = 0; i < tokens1.length; i++) {
var token = tokens1[i];
for(var j = 0; j < tokens2.length; j++) {
if(token == tokens2[j]) continue outer;
}
values.push(token);
}
return values;
}
function arrayClasses (classVal) {
if (isArray(classVal)) {
return classVal;
} else if (isString(classVal)) {
return classVal.split(' ');
} else if (isObject(classVal)) {
var classes = [], i = 0;
forEach(classVal, function(v, k) {
if (v) {
classes.push(k);
}
});
return classes;
}
return classVal;
}
}];
}
/**
+16 -14
View File
@@ -11,7 +11,7 @@
* @element ANY
* @priority 0
* @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
* click. (Event object is available as `$event`)
* click. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -92,7 +92,7 @@ forEach(
* @element ANY
* @priority 0
* @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
* mousedown. (Event object is available as `$event`)
* mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -116,7 +116,7 @@ forEach(
* @element ANY
* @priority 0
* @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
* mouseup. (Event object is available as `$event`)
* mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -139,7 +139,7 @@ forEach(
* @element ANY
* @priority 0
* @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
* mouseover. (Event object is available as `$event`)
* mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -163,7 +163,7 @@ forEach(
* @element ANY
* @priority 0
* @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
* mouseenter. (Event object is available as `$event`)
* mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -187,7 +187,7 @@ forEach(
* @element ANY
* @priority 0
* @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
* mouseleave. (Event object is available as `$event`)
* mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -211,7 +211,7 @@ forEach(
* @element ANY
* @priority 0
* @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
* mousemove. (Event object is available as `$event`)
* mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -278,7 +278,8 @@ forEach(
*
* @element ANY
* @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
* keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
* keypress. ({@link guide/expression#-event- Event object is available as `$event`}
* and can be interrogated for keyCode, altKey, etc.)
*
* @example
<example>
@@ -303,7 +304,8 @@ forEach(
*
* @element form
* @priority 0
* @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`)
* @param {expression} ngSubmit {@link guide/expression Expression} to eval.
* ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -354,7 +356,7 @@ forEach(
* @element window, input, select, textarea, a
* @priority 0
* @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
* focus. (Event object is available as `$event`)
* focus. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
* See {@link ng.directive:ngClick ngClick}
@@ -370,7 +372,7 @@ forEach(
* @element window, input, select, textarea, a
* @priority 0
* @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
* blur. (Event object is available as `$event`)
* blur. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
* See {@link ng.directive:ngClick ngClick}
@@ -386,7 +388,7 @@ forEach(
* @element window, input, select, textarea, a
* @priority 0
* @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
* copy. (Event object is available as `$event`)
* copy. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -407,7 +409,7 @@ forEach(
* @element window, input, select, textarea, a
* @priority 0
* @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
* cut. (Event object is available as `$event`)
* cut. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
@@ -428,7 +430,7 @@ forEach(
* @element window, input, select, textarea, a
* @priority 0
* @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
* paste. (Event object is available as `$event`)
* paste. ({@link guide/expression#-event- Event object is available as `$event`})
*
* @example
<example>
+1 -1
View File
@@ -171,7 +171,7 @@ var ngShowDirective = ['$animate', function($animate) {
* in AngularJS and sets the display style to none (using an !important flag).
* For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
*
* ```hrml
* ```html
* <!-- when $scope.myValue is truthy (element is hidden) -->
* <div ng-hide="myValue"></div>
*
+1 -1
View File
@@ -22,7 +22,7 @@
* - `Array`: An array of function or string predicates. The first predicate in the array
* is used for sorting, but when two items are equivalent, the next predicate is used.
*
* @param {boolean=} reverse Reverse the order the array.
* @param {boolean=} reverse Reverse the order of the array.
* @returns {Array} Sorted copy of the source array.
*
* @example
+10 -8
View File
@@ -277,7 +277,7 @@ function $HttpProvider() {
*
* ```
* module.run(function($http) {
* $http.defaults.headers.common.Authentication = 'Basic YmVlcDpib29w'
* $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
* });
* ```
*
@@ -571,6 +571,7 @@ function $HttpProvider() {
* - **status** `{number}` HTTP status code of the response.
* - **headers** `{function([headerName])}` Header getter function.
* - **config** `{Object}` The configuration object that was used to generate the request.
* - **statusText** `{string}` HTTP status text of the response.
*
* @property {Array.<Object>} pendingRequests Array of config objects for currently pending
* requests. This is primarily meant to be used for debugging purposes.
@@ -945,9 +946,9 @@ function $HttpProvider() {
} else {
// serving from cache
if (isArray(cachedResp)) {
resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]), cachedResp[3]);
} else {
resolvePromise(cachedResp, 200, {});
resolvePromise(cachedResp, 200, {}, 'OK');
}
}
} else {
@@ -971,17 +972,17 @@ function $HttpProvider() {
* - resolves the raw $http promise
* - calls $apply
*/
function done(status, response, headersString) {
function done(status, response, headersString, statusText) {
if (cache) {
if (isSuccess(status)) {
cache.put(url, [status, response, parseHeaders(headersString)]);
cache.put(url, [status, response, parseHeaders(headersString), statusText]);
} else {
// remove promise from the cache
cache.remove(url);
}
}
resolvePromise(response, status, headersString);
resolvePromise(response, status, headersString, statusText);
if (!$rootScope.$$phase) $rootScope.$apply();
}
@@ -989,7 +990,7 @@ function $HttpProvider() {
/**
* Resolves the raw $http promise.
*/
function resolvePromise(response, status, headers) {
function resolvePromise(response, status, headers, statusText) {
// normalize internal statuses to 0
status = Math.max(status, 0);
@@ -997,7 +998,8 @@ function $HttpProvider() {
data: response,
status: status,
headers: headersGetter(headers),
config: config
config: config,
statusText : statusText
});
}
+6 -4
View File
@@ -97,7 +97,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
completeRequest(callback,
status || xhr.status,
response,
responseHeaders);
responseHeaders,
xhr.statusText || '');
}
};
@@ -138,7 +139,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
xhr && xhr.abort();
}
function completeRequest(callback, status, response, headersString) {
function completeRequest(callback, status, response, headersString, statusText) {
// cancel timeout and subsequent timeout promise resolution
timeoutId && $browserDefer.cancel(timeoutId);
jsonpDone = xhr = null;
@@ -151,9 +152,10 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
}
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
status = status == 1223 ? 204 : status;
status = status === 1223 ? 204 : status;
statusText = statusText || '';
callback(status, response, headersString);
callback(status, response, headersString, statusText);
$browser.$$completeOutstandingRequest(noop);
}
};
+1 -2
View File
@@ -508,8 +508,7 @@ function locationGetterSetter(property, preprocess) {
* - Clicks on a link.
* - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
*
* For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
* Services: Using $location}
* For more information see {@link guide/$location Developer Guide: Using $location}
*/
/**
+5 -1
View File
@@ -393,7 +393,11 @@ var Parser = function (lexer, $filter, options) {
this.options = options;
};
Parser.ZERO = function () { return 0; };
Parser.ZERO = extend(function () {
return 0;
}, {
constant: true
});
Parser.prototype = {
constructor: Parser,
+22 -4
View File
@@ -309,7 +309,8 @@ function $RootScopeProvider(){
* - `function(newValue, oldValue, scope)`: called with current and previous values as
* parameters.
*
* @param {boolean=} objectEquality Compare object for equality rather than for reference.
* @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
* comparing for reference equality.
* @returns {function()} Returns a deregistration function for this listener.
*/
$watch: function(watchExp, listener, objectEquality) {
@@ -730,15 +731,32 @@ function $RootScopeProvider(){
forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
// sever all the references to parent scopes (after this cleanup, the current scope should
// not be retained by any of our references and should be eligible for garbage collection)
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
// This is bogus code that works around Chrome's GC leak
// see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
// All of the code below is bogus code that works around V8's memory leak via optimized code
// and inline caches.
//
// see:
// - https://code.google.com/p/v8/issues/detail?id=2073#c26
// - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
// - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
this.$$childTail = null;
this.$$childTail = this.$root = null;
// don't reset these to null in case some async task tries to register a listener/watch/task
this.$$listeners = {};
this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];
// prevent NPEs since these methods have references to properties we nulled out
this.$destroy = this.$digest = this.$apply = noop;
this.$on = this.$watch = function() { return noop; };
},
/**
+1 -1
View File
@@ -522,7 +522,7 @@ function $SceDelegateProvider() {
* | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. |
* | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
* | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. |
* | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contens are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
* | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
* | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. |
*
* ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
+10 -7
View File
@@ -106,11 +106,11 @@
* -webkit-animation: enter_sequence 1s linear; /&#42; Safari/Chrome &#42;/
* animation: enter_sequence 1s linear; /&#42; IE10+ and Future Browsers &#42;/
* }
* &#64-webkit-keyframes enter_sequence {
* @-webkit-keyframes enter_sequence {
* from { opacity:0; }
* to { opacity:1; }
* }
* &#64keyframes enter_sequence {
* @keyframes enter_sequence {
* from { opacity:0; }
* to { opacity:1; }
* }
@@ -332,9 +332,12 @@ angular.module('ngAnimate', ['ng'])
//operation which performs CSS transition and keyframe
//animations sniffing. This is always included for each
//element animation procedure if the browser supports
//transitions and/or keyframe animations
//transitions and/or keyframe animations. The default
//animation is added to the top of the list to prevent
//any previous animations from affecting the element styling
//prior to the element being animated.
if ($sniffer.transitions || $sniffer.animations) {
classes.push('');
matches.push($injector.get(selectors['']));
}
for(var i=0; i < classes.length; i++) {
@@ -1109,7 +1112,7 @@ angular.module('ngAnimate', ['ng'])
//but it may not need to cancel out the existing timeout
//if the timestamp is less than the previous one
var futureTimestamp = Date.now() + (totalTime * 1000);
var futureTimestamp = Date.now() + totalTime;
if(futureTimestamp <= closingTimestamp) {
return;
}
@@ -1208,7 +1211,7 @@ angular.module('ngAnimate', ['ng'])
parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
parentID = parentCounter;
}
return parentID + '-' + extractElementNode(element).className;
return parentID + '-' + extractElementNode(element).getAttribute('class');
}
function animateSetup(animationEvent, element, className, calculationDecorator) {
@@ -1313,7 +1316,7 @@ angular.module('ngAnimate', ['ng'])
function animateRun(animationEvent, element, className, activeAnimationComplete) {
var node = extractElementNode(element);
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(node.className.indexOf(className) == -1 || !elementData) {
if(node.getAttribute('class').indexOf(className) == -1 || !elementData) {
activeAnimationComplete();
return;
}
+25 -21
View File
@@ -1090,12 +1090,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
responsesPush = angular.bind(responses, responses.push),
copy = angular.copy;
function createResponse(status, data, headers) {
function createResponse(status, data, headers, statusText) {
if (angular.isFunction(status)) return status;
return function() {
return angular.isNumber(status)
? [status, data, headers]
? [status, data, headers, statusText]
: [200, status, data];
};
}
@@ -1120,7 +1120,8 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
function handleResponse() {
var response = wrapped.response(method, url, data, headers);
xhr.$$respHeaders = response[2];
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders());
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
copy(response[3] || ''));
}
function handleTimeout() {
@@ -1187,16 +1188,17 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* request is handled.
*
* - respond
* `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
* The respond method takes a set of static data to be returned or a function that can return
* an array containing response status (number), response data (string) and response headers
* (Object).
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers)}`
* The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
* headers (Object), and the text for the status (string).
*/
$httpBackend.when = function(method, url, data, headers) {
var definition = new MockHttpExpectation(method, url, data, headers),
chain = {
respond: function(status, data, headers) {
definition.response = createResponse(status, data, headers);
respond: function(status, data, headers, statusText) {
definition.response = createResponse(status, data, headers, statusText);
}
};
@@ -1304,17 +1306,18 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* request is handled.
*
* - respond
* `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
* The respond method takes a set of static data to be returned or a function that can return
* an array containing response status (number), response data (string) and response headers
* (Object).
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers)}`
* The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
* headers (Object), and the text for the status (string).
*/
$httpBackend.expect = function(method, url, data, headers) {
var expectation = new MockHttpExpectation(method, url, data, headers);
expectations.push(expectation);
return {
respond: function(status, data, headers) {
expectation.response = createResponse(status, data, headers);
respond: function (status, data, headers, statusText) {
expectation.response = createResponse(status, data, headers, statusText);
}
};
};
@@ -1816,13 +1819,14 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* control how a matched request is handled.
*
* - respond
* `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers)}`
* The respond method takes a set of static data to be returned or a function that can return
* an array containing response status (number), response data (string) and response headers
* (Object).
* - passThrough `{function()}` Any request matching a backend definition with `passThrough`
* handler will be passed through to the real backend (an XHR request will be made to the
* server.)
* an array containing response status (number), response data (string), response headers
* (Object), and the text for the status (string).
* - passThrough `{function()}` Any request matching a backend definition with
* `passThrough` handler will be passed through to the real backend (an XHR request will be made
* to the server.)
*/
/**
+3
View File
@@ -199,6 +199,9 @@ function shallowClearAndCopy(src, dst) {
* On failure, the promise is resolved with the {@link ng.$http http response} object, without
* the `resource` property.
*
* If an interceptor object was provided, the promise will instead be resolved with the value
* returned by the interceptor.
*
* - `$resolved`: `true` after first server interaction is completed (either with success or
* rejection), `false` before that. Knowing if the Resource has been resolved is useful in
* data-binding.
+30
View File
@@ -95,6 +95,36 @@ describe('jqLite', function() {
expect(fragment.length).toBe(1);
expect(fragment[0].nodeType).toBe(11);
});
it('should allow construction of <option> elements', function() {
var nodes = jqLite('<option>');
expect(nodes.length).toBe(1);
expect(nodes[0].nodeName.toLowerCase()).toBe('option');
});
// Special tests for the construction of elements which are restricted (in the HTML5 spec) to
// being children of specific nodes.
forEach([
'caption',
'colgroup',
'col',
'optgroup',
'opt',
'tbody',
'td',
'tfoot',
'th',
'thead',
'tr'
], function(name) {
it('should allow construction of <$NAME$> elements'.replace('$NAME$', name), function() {
var nodes = jqLite('<$NAME$>'.replace('$NAME$', name));
expect(nodes.length).toBe(1);
expect(nodes[0].nodeName.toLowerCase()).toBe(name);
});
});
});
describe('_data', function() {
+48
View File
@@ -541,6 +541,14 @@ describe('$compile', function() {
replace: true,
template: '<tfoot><tr><td>TD</td></tr></tfoot>'
}));
directive('replaceWithOption', valueFn({
replace: true,
template: '<option>OPTION</option>'
}));
directive('replaceWithOptgroup', valueFn({
replace: true,
template: '<optgroup>OPTGROUP</optgroup>'
}));
}));
@@ -746,6 +754,20 @@ describe('$compile', function() {
}).not.toThrow();
expect(nodeName_(element)).toMatch(/tfoot/i);
}));
it('should support templates with root <option> tags', inject(function($compile, $rootScope) {
expect(function() {
element = $compile('<div replace-with-option></div>')($rootScope);
}).not.toThrow();
expect(nodeName_(element)).toMatch(/option/i);
}));
it('should support templates with root <optgroup> tags', inject(function($compile, $rootScope) {
expect(function() {
element = $compile('<div replace-with-optgroup></div>')($rootScope);
}).not.toThrow();
expect(nodeName_(element)).toMatch(/optgroup/i);
}));
});
@@ -867,6 +889,14 @@ describe('$compile', function() {
replace: true,
templateUrl: 'tfoot.html'
}));
directive('replaceWithOption', valueFn({
replace: true,
templateUrl: 'option.html'
}));
directive('replaceWithOptgroup', valueFn({
replace: true,
templateUrl: 'optgroup.html'
}));
}
));
@@ -1556,6 +1586,24 @@ describe('$compile', function() {
$rootScope.$digest();
expect(nodeName_(element)).toMatch(/tfoot/i);
}));
it('should support templates with root <option> tags', inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('option.html', '<option>OPTION</option>');
expect(function() {
element = $compile('<div replace-with-option></div>')($rootScope);
}).not.toThrow();
$rootScope.$digest();
expect(nodeName_(element)).toMatch(/option/i);
}));
it('should support templates with root <optgroup> tags', inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('optgroup.html', '<optgroup>OPTGROUP</optgroup>');
expect(function() {
element = $compile('<div replace-with-optgroup></div>')($rootScope);
}).not.toThrow();
$rootScope.$digest();
expect(nodeName_(element)).toMatch(/optgroup/i);
}));
});
+13
View File
@@ -192,6 +192,19 @@ describe('ngClass', function() {
}));
it("should allow ngClassOdd/Even on the same element with overlapping classes", inject(function($rootScope, $compile, $animate) {
var className;
element = $compile('<ul><li ng-repeat="i in [0,1,2]" ng-class-odd="\'same odd\'" ng-class-even="\'same even\'"></li><ul>')($rootScope);
$rootScope.$digest();
var e1 = jqLite(element[0].childNodes[1]);
var e2 = jqLite(element[0].childNodes[5]);
expect(e1.hasClass('same')).toBeTruthy();
expect(e1.hasClass('odd')).toBeTruthy();
expect(e2.hasClass('same')).toBeTruthy();
expect(e2.hasClass('odd')).toBeTruthy();
}));
it('should allow both ngClass and ngClassOdd/Even with multiple classes', inject(function($rootScope, $compile) {
element = $compile('<ul>' +
'<li ng-repeat="i in [0,1]" ng-class="[\'A\', \'B\']" ' +
+26
View File
@@ -75,6 +75,32 @@ describe('$httpBackend', function() {
expect(xhr.$$data).toBe(null);
});
it('should call completion function with xhr.statusText if present', function() {
callback.andCallFake(function(status, response, headers, statusText) {
expect(statusText).toBe('OK');
});
$backend('GET', '/some-url', null, callback);
xhr = MockXhr.$$lastInstance;
xhr.statusText = 'OK';
xhr.readyState = 4;
xhr.onreadystatechange();
expect(callback).toHaveBeenCalledOnce();
});
it('should call completion function with empty string if not present', function() {
callback.andCallFake(function(status, response, headers, statusText) {
expect(statusText).toBe('');
});
$backend('GET', '/some-url', null, callback);
xhr = MockXhr.$$lastInstance;
xhr.readyState = 4;
xhr.onreadystatechange();
expect(callback).toHaveBeenCalledOnce();
});
it('should normalize IE\'s 1223 status code into 204', function() {
callback.andCallFake(function(status) {
expect(status).toBe(204);
+24
View File
@@ -481,6 +481,30 @@ describe('$http', function() {
});
it('should pass statusText in response object when a request is successful', function() {
$httpBackend.expect('GET', '/url').respond(200, 'SUCCESS', {}, 'OK');
$http({url: '/url', method: 'GET'}).then(function(response) {
expect(response.statusText).toBe('OK');
callback();
});
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
});
it('should pass statusText in response object when a request fails', function() {
$httpBackend.expect('GET', '/url').respond(404, 'ERROR', {}, 'Not Found');
$http({url: '/url', method: 'GET'}).then(null, function(response) {
expect(response.statusText).toBe('Not Found');
callback();
});
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
});
it('should pass in the response object when a request failed', function() {
$httpBackend.expect('GET', '/url').respond(543, 'bad error', {'request-id': '123'});
$http({url: '/url', method: 'GET'}).then(null, function(response) {
+1
View File
@@ -1031,6 +1031,7 @@ describe('parser', function() {
it('should mark complex expressions involving constant values as constant', inject(function($parse) {
expect($parse('!true').constant).toBe(true);
expect($parse('-42').constant).toBe(true);
expect($parse('1 - 1').constant).toBe(true);
expect($parse('"foo" + "bar"').constant).toBe(true);
expect($parse('5 != null').constant).toBe(true);
+46 -3
View File
@@ -844,17 +844,19 @@ describe('Scope', function() {
expect(log).toBe('123');
first.$destroy();
// once a scope is destroyed apply should not do anything any more
first.$apply();
expect(log).toBe('12323');
expect(log).toBe('123');
first.$destroy();
first.$destroy();
first.$apply();
expect(log).toBe('1232323');
expect(log).toBe('123');
}));
it('should decrement anscestor $$listenerCount entries', inject(function($rootScope) {
it('should decrement ancestor $$listenerCount entries', inject(function($rootScope) {
var EVENT = 'fooEvent',
spy = jasmine.createSpy('listener'),
firstSecond = first.$new();
@@ -874,6 +876,47 @@ describe('Scope', function() {
$rootScope.$broadcast(EVENT);
expect(spy.callCount).toBe(1);
}));
it("should do nothing when a child event listener is registered after parent's destruction",
inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new();
parent.$destroy();
var fn = child.$on('someEvent', function() {});
expect(fn).toBe(noop);
}));
it("should do nothing when a child watch is registered after parent's destruction",
inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new();
parent.$destroy();
var fn = child.$watch('somePath', function() {});
expect(fn).toBe(noop);
}));
it("should preserve all (own and inherited) model properties on a destroyed scope",
inject(function($rootScope) {
// This test simulates an async task (xhr response) interacting with the scope after the scope
// was destroyed. Since we can't abort the request, we should ensure that the task doesn't
// throw NPEs because the scope was cleaned up during destruction.
var parent = $rootScope.$new(),
child = parent.$new();
parent.parentModel = 'parent';
child.childModel = 'child';
child.$destroy();
expect(child.parentModel).toBe('parent');
expect(child.childModel).toBe('child');
}));
});
+101 -8
View File
@@ -721,6 +721,37 @@ describe("ngAnimate", function() {
})
});
/* The CSS animation handler must always be rendered before the other JS animation
handlers. This is important since the CSS animation handler may place temporary
styling on the HTML element before the reflow commences which in turn may override
other transition or keyframe styles that any former JS animations may have placed
on the element: https://github.com/angular/angular.js/issues/6675 */
it("should always perform the CSS animation before the JS animation", function() {
var log = [];
module(function($animateProvider) {
//CSS animation handler
$animateProvider.register('', function() {
return {
leave : function() { log.push('css'); }
}
});
//custom JS animation handler
$animateProvider.register('.js-animation', function() {
return {
leave : function() { log.push('js'); }
}
});
});
inject(function($animate, $rootScope, $compile, $sniffer) {
if(!$sniffer.transitions) return;
element = $compile(html('<div class="js-animation"></div>'))($rootScope);
$animate.leave(element);
$rootScope.$digest();
expect(log).toEqual(['css','js']);
});
});
describe("Animations", function() {
@@ -1301,24 +1332,47 @@ describe("ngAnimate", function() {
}));
it("should intelligently cancel former timeouts and close off a series of elements a final timeout", function() {
var cancellations = 0;
var currentTimestamp, cancellations = 0;
module(function($provide) {
$provide.decorator('$timeout', function($delegate) {
var _cancel = $delegate.cancel;
$delegate.cancel = function() {
cancellations++;
return _cancel.apply($delegate, arguments);
$delegate.cancel = function(timer) {
if(timer) {
cancellations++;
return _cancel.apply($delegate, arguments);
}
};
return $delegate;
});
return function($sniffer) {
if($sniffer.transitions) {
currentTimestamp = Date.now();
spyOn(Date,'now').andCallFake(function() {
return currentTimestamp;
});
}
}
})
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
if (!$sniffer.transitions) return;
ss.addRule('.animate-me', '-webkit-transition:1s linear all;' +
ss.addRule('.animate-me div', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
element = $compile(html('<div><div class="animate-me" ng-repeat="item in items"></div></div>'))($rootScope);
ss.addRule('.animate-me-longer div', '-webkit-transition:1.5s linear all;' +
'transition:1.5s linear all;');
element = $compile(html('<div class="animate-me-longer">' +
' <div ng-repeat="item in items"></div>' +
'</div>'))($rootScope);
$rootScope.items = [0];
$rootScope.$digest();
$animate.triggerReflow();
currentTimestamp += 2250; //1.5 * 1500 = 2250
element[0].className = 'animate-me';
$rootScope.items = [1,2,3,4,5,6,7,8,9,10];
var totalOperations = $rootScope.items.length;
@@ -1327,9 +1381,11 @@ describe("ngAnimate", function() {
$rootScope.items = [0];
$animate.triggerReflow();
currentTimestamp += 1500; //1.5 * 1000 = 1500
$timeout.flush(1500);
expect(cancellations).toBeLessThan(totalOperations);
expect(cancellations).toBe(1);
expect(element.children().length).toBe(10);
cancellations = 0;
@@ -1339,7 +1395,7 @@ describe("ngAnimate", function() {
$animate.triggerReflow();
$timeout.flush(1500);
expect(element.children().length).toBe(1);
expect(cancellations).toBeLessThan(totalOperations);
expect(cancellations).toBe(1);
});
});
@@ -3596,5 +3652,42 @@ describe("ngAnimate", function() {
expect(element.children().length).toBe(0);
}));
describe('SVG', function() {
it('should properly apply transitions on an SVG element',
inject(function($animate, $rootScope, $compile, $rootElement, $sniffer) {
//jQuery doesn't handle SVG elements natively. Instead, an add-on library
//is required which is called jquery.svg.js. Therefore, when jQuery is
//active here there is no point to test this since it won't work by default.
if(!$sniffer.transitions || !_jqLiteMode) return;
ss.addRule('circle.ng-enter', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
var element = $compile('<svg width="500" height="500">' +
'<circle cx="15" cy="5" r="100" fill="orange" ng-if="on" />' +
'</svg>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$rootScope.$digest();
$rootScope.on = true;
$rootScope.$digest();
$animate.triggerReflow();
var child = element.find('circle');
expect(child.hasClass('ng-enter')).toBe(true);
expect(child.hasClass('ng-enter-active')).toBe(true);
browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
expect(child.hasClass('ng-enter')).toBe(false);
expect(child.hasClass('ng-enter-active')).toBe(false);
}));
});
});
});
+10 -10
View File
@@ -1068,29 +1068,29 @@ describe('ngMock', function() {
hb.flush();
expect(callback.callCount).toBe(2);
expect(callback.argsForCall[0]).toEqual([201, 'second', '']);
expect(callback.argsForCall[1]).toEqual([200, 'first', '']);
expect(callback.argsForCall[0]).toEqual([201, 'second', '', '']);
expect(callback.argsForCall[1]).toEqual([200, 'first', '', '']);
});
describe('respond()', function() {
it('should take values', function() {
hb.expect('GET', '/url1').respond(200, 'first', {'header': 'val'});
hb.expect('GET', '/url1').respond(200, 'first', {'header': 'val'}, 'OK');
hb('GET', '/url1', undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val');
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK');
});
it('should take function', function() {
hb.expect('GET', '/some').respond(function(m, u, d, h) {
return [301, m + u + ';' + d + ';a=' + h.a, {'Connection': 'keep-alive'}];
hb.expect('GET', '/some').respond(function (m, u, d, h) {
return [301, m + u + ';' + d + ';a=' + h.a, {'Connection': 'keep-alive'}, 'Moved Permanently'];
});
hb('GET', '/some', 'data', callback, {a: 'b'});
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some;data;a=b', 'Connection: keep-alive');
expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some;data;a=b', 'Connection: keep-alive', 'Moved Permanently');
});
it('should default status code to 200', function() {
@@ -1119,8 +1119,8 @@ describe('ngMock', function() {
hb.flush();
expect(callback.callCount).toBe(2);
expect(callback.argsForCall[0]).toEqual([200, 'first', '']);
expect(callback.argsForCall[1]).toEqual([200, 'second', '']);
expect(callback.argsForCall[0]).toEqual([200, 'first', '', '']);
expect(callback.argsForCall[1]).toEqual([200, 'second', '', '']);
});
});
@@ -1415,7 +1415,7 @@ describe('ngMock', function() {
hb[shortcut]('/foo').respond('bar');
hb(method, '/foo', undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'bar', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'bar', '', '');
});
});
});