Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f79662d61 | |||
| e4830b9fd2 | |||
| 5ad4f5562c | |||
| 43bb414e23 | |||
| 64c7c53190 | |||
| 5075c870fd | |||
| b9edb415b2 | |||
| 7ca6543244 | |||
| e58b4ceed8 | |||
| c97237bcb8 | |||
| 8822a4ff69 | |||
| 772440cdaf | |||
| 1fbddf950f | |||
| 369ca3e13e | |||
| 00d38e3358 | |||
| a0ed1b73ff | |||
| e44c3ac327 | |||
| c6551231b2 | |||
| bf073be69d | |||
| aed34c75be | |||
| 78e17f5729 | |||
| ba6f477ac8 | |||
| 6482297185 | |||
| 41deaf1d21 | |||
| 23e4138c07 | |||
| 0ffb30f585 | |||
| 6f52013668 | |||
| 07e1ba29f0 | |||
| 04c64b3d10 | |||
| 4d9a95ffdb | |||
| 27486bd15e | |||
| cf919a6fb7 | |||
| d4d1031bcd | |||
| 798114075f | |||
| 838dd12ee1 | |||
| 6926223963 | |||
| 5f8372ee2d | |||
| 289374a43c | |||
| 3b333f3b9f | |||
| 7cbb1044fc | |||
| a8bfeff5d8 | |||
| fc0e100c45 | |||
| eb49f6b755 | |||
| f9e651d113 | |||
| 2f72a69ded | |||
| 8a67ac57fc | |||
| 90a41d415c | |||
| eefaa76a90 | |||
| bc3432365c | |||
| f4ac5c8487 | |||
| 9f2f567167 | |||
| 3e6f49a227 | |||
| 795398fab0 | |||
| f1a34bad20 | |||
| 84a6ea9eb4 | |||
| ff0ef2aeaf | |||
| c81e3556e8 | |||
| 8cc69b9edb | |||
| ae8ce3ec60 | |||
| df0d5d245e | |||
| 67ec5e8ceb | |||
| 4df2188e96 | |||
| 816a587578 | |||
| 0e1bd7822e | |||
| b27080d525 | |||
| 1a509873bf | |||
| fead62e958 | |||
| e2011bd0b3 | |||
| 3cdffcecba | |||
| f87d6648d7 | |||
| b1d0a83d01 | |||
| d6098eeb1c | |||
| c903b76f6c | |||
| f3a565872d | |||
| 582b03b983 | |||
| be240b1176 | |||
| 61b33543ff | |||
| 1dcba9cd88 | |||
| 8cd54d7794 | |||
| 3105b2c26a | |||
| bc5a48d4a4 | |||
| 2bbc7c464f | |||
| e85f91d582 | |||
| 862a78dfd2 | |||
| bd772abf34 | |||
| b074d719ae | |||
| a43a40b778 | |||
| 2ceeb739f3 | |||
| fa715abf45 | |||
| f943e377e8 | |||
| 30084c1369 | |||
| 668a33da34 | |||
| 19e2347759 | |||
| f17292e1b5 | |||
| 77b4330011 | |||
| 7717de4c51 | |||
| e68697e2e3 | |||
| a02ed88279 | |||
| a5914c94a8 | |||
| 63c9c9e8d7 | |||
| 4adbf82a84 | |||
| 1144b1eccb | |||
| 8970087e58 | |||
| 131e62a819 | |||
| 2fad638237 | |||
| a0940895a2 | |||
| 0a1db2ad5f | |||
| 4bd4246906 | |||
| 05ac702bc7 | |||
| 6882113bc1 | |||
| 535ee32a0b | |||
| 7cf4a2933c | |||
| 7dd6c87eec | |||
| 17f963c5d8 | |||
| ba09ba5344 | |||
| 8c36a43e91 | |||
| af14d67b84 | |||
| c8acff1cdc | |||
| 876e9842a2 | |||
| 5cb9465093 | |||
| 58f9413ad3 | |||
| 6f7674a7d0 | |||
| 8dc153db75 | |||
| 4a6f0996f6 | |||
| 522d581fc9 | |||
| 17b139f107 | |||
| 10973c3366 | |||
| fc64e68076 | |||
| ac5e92de9b | |||
| 0936353e9a | |||
| ed22d2fe7b | |||
| a5cfa88630 | |||
| bbf74f9994 | |||
| 62ad450d60 | |||
| faa4b17c86 | |||
| 4d980a8771 | |||
| 369469b4f3 | |||
| be417f2854 | |||
| 3a517c25f6 | |||
| 29b8dcf387 | |||
| c9d1e690aa | |||
| a47247b5e0 | |||
| b682213d72 | |||
| 223cf2b5bb | |||
| c387e0d79d | |||
| cbf74d5d64 | |||
| a812327acd | |||
| 35fce310e9 | |||
| 93a754a490 | |||
| 7d9d387195 | |||
| feac52d840 | |||
| f4f571efdf | |||
| a8c263c194 | |||
| 3d6c45d76e | |||
| bf841d3512 | |||
| a1d88457de | |||
| b011ae9544 | |||
| 63fdee6b9c | |||
| 257ebbb514 | |||
| 2b6c986736 | |||
| 2da4950406 | |||
| 789db83a8a | |||
| ce443792c4 | |||
| dd47867bfb | |||
| f2ebb82ba5 | |||
| 12698755be | |||
| f7d7954904 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
# http://editorconfig.org
|
||||
# https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ https://plnkr.co or similar (you can use this template as a starting point: http
|
||||
<!-- Check whether this is still an issue in the most recent stable or in the snapshot AngularJS
|
||||
version (https://code.angularjs.org/snapshot/) -->
|
||||
|
||||
**Browser:** [all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
|
||||
**Browser:** [all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView | Opera XX ]
|
||||
<!-- All browsers where this could be reproduced (and Operating System if relevant) -->
|
||||
|
||||
**Anything else:**
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@ env:
|
||||
- JOB=ci-checks
|
||||
- JOB=unit-core BROWSER_PROVIDER=saucelabs
|
||||
- JOB=unit-jquery BROWSER_PROVIDER=saucelabs
|
||||
- JOB=unit-modules BROWSER_PROVIDER=saucelabs
|
||||
- JOB=docs-app BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
|
||||
@@ -26,7 +27,7 @@ env:
|
||||
- secure: oTBjhnOKhs0qDSKTf7fE4f6DYiNDPycvB7qfSF5QRIbJK/LK/J4UtFwetXuXj79HhUZG9qnoT+5e7lPaiaMlpsIKn9ann7ffqFWN1E8TMtpJF+AGigx3djYElwfgf5nEnFUFhwjFzvbfpZNnxVGgX5YbIZpe/WUbHkP4ffU0Wks=
|
||||
|
||||
before_install:
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.10.1
|
||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
||||
|
||||
before_script:
|
||||
|
||||
+301
-4
@@ -1,3 +1,271 @@
|
||||
<a name="1.7.7"></a>
|
||||
# 1.7.7 kingly-exiting (2019-02-04)
|
||||
|
||||
## Bug Fixes
|
||||
- **ngRequired:** set error correctly when inside ngRepeat and false by default
|
||||
([5ad4f5](https://github.com/angular/angular.js/commit/5ad4f5562c37b1cb575e3e5fddd96e9dd10408e2),
|
||||
[#16814](https://github.com/angular/angular.js/issues/16814),
|
||||
[#16820](https://github.com/angular/angular.js/issues/16820))
|
||||
|
||||
|
||||
<a name="1.7.6"></a>
|
||||
# 1.7.6 gravity-manipulation (2019-01-17)
|
||||
|
||||
## Bug Fixes
|
||||
- **$compile:** fix ng-prop-* with undefined values
|
||||
([772440](https://github.com/angular/angular.js/commit/772440cdaf9a9bfa40de1675e20a5f0e356089ed),
|
||||
[#16797](https://github.com/angular/angular.js/issues/16797),
|
||||
[#16798](https://github.com/angular/angular.js/issues/16798))
|
||||
- **compile:** properly handle false value for boolean attrs with jQuery
|
||||
([27486b](https://github.com/angular/angular.js/commit/27486bd15e70946ece2ba713e4e8654b7f9bddad),
|
||||
[#16778](https://github.com/angular/angular.js/issues/16778),
|
||||
[#16779](https://github.com/angular/angular.js/issues/16779))
|
||||
- **ngRepeat:**
|
||||
- fix reference to last collection value remaining across linkages
|
||||
([cf919a](https://github.com/angular/angular.js/commit/cf919a6fb7fc655f3fa37a74899a797ea5b8073e))
|
||||
- fix trackBy function being invoked with incorrect scope
|
||||
([d4d103](https://github.com/angular/angular.js/commit/d4d1031bcd9b30ae6a58bd60a79bcc9d20f0f2b7),
|
||||
[#16776](https://github.com/angular/angular.js/issues/16776),
|
||||
[#16777](https://github.com/angular/angular.js/issues/16777))
|
||||
- **aria/ngClick:** check if element is `contenteditable` before blocking spacebar
|
||||
([289374](https://github.com/angular/angular.js/commit/289374a43c1b2fd715ddf7455db225b17afebbaf),
|
||||
[#16762](https://github.com/angular/angular.js/issues/16762))
|
||||
- **input:** prevent browsers from autofilling hidden inputs
|
||||
([7cbb10](https://github.com/angular/angular.js/commit/7cbb1044fcb3576cdad791bd22ebea3dfd533ff8))
|
||||
- **Angular:** add workaround for Safari / Webdriver problem
|
||||
([eb49f6](https://github.com/angular/angular.js/commit/eb49f6b7555cfd7ab03fd35581adb6b4bd49044e))
|
||||
- **$browser:** normalize inputted URLs
|
||||
([2f72a6](https://github.com/angular/angular.js/commit/2f72a69ded53a122afad3ec28d91f9bd2f41eb4f),
|
||||
[#16606](https://github.com/angular/angular.js/issues/16606))
|
||||
- **interpolate:** do not create directives for constant media URL attributes
|
||||
([90a41d](https://github.com/angular/angular.js/commit/90a41d415c83abdbf28317f49df0fd0a7e07db86),
|
||||
[#16734](https://github.com/angular/angular.js/issues/16734))
|
||||
- **$q:** allow third-party promise libraries
|
||||
([eefaa7](https://github.com/angular/angular.js/commit/eefaa76a90dbef08fdc7d734a205cc2de50d9f91),
|
||||
[#16164](https://github.com/angular/angular.js/issues/16164),
|
||||
[#16471](https://github.com/angular/angular.js/issues/16471))
|
||||
- **urlUtils:** make IPv6 URL's hostname wrapped in square brackets in IE/Edge
|
||||
([0e1bd7](https://github.com/angular/angular.js/commit/0e1bd7822e61822a48b8fd7ba5913a8702e6dabf),
|
||||
[#16692](https://github.com/angular/angular.js/issues/16692),
|
||||
[#16715](https://github.com/angular/angular.js/issues/16715))
|
||||
- **ngAnimateSwap:** make it compatible with `ngIf` on the same element
|
||||
([b27080](https://github.com/angular/angular.js/commit/b27080d52546409fb4e483f212f03616e2ca8037),
|
||||
[#16616](https://github.com/angular/angular.js/issues/16616),
|
||||
[#16729](https://github.com/angular/angular.js/issues/16729))
|
||||
- **ngMock:** make matchLatestDefinitionEnabled work
|
||||
([3cdffc](https://github.com/angular/angular.js/commit/3cdffcecbae71189b4db69b57fadda6608a23b61),
|
||||
[#16702](https://github.com/angular/angular.js/issues/16702))
|
||||
- **ngStyle:** skip setting empty value when new style has the property
|
||||
([d6098e](https://github.com/angular/angular.js/commit/d6098eeb1c9510d599e9bd3cfdba7dd21e7a55a5),
|
||||
[#16709](https://github.com/angular/angular.js/issues/16709))
|
||||
|
||||
## Performance Improvements
|
||||
- **input:** prevent multiple validations on initialization
|
||||
([692622](https://github.com/angular/angular.js/commit/69262239632027b373258e75c670b89132ad9edb),
|
||||
[#14691](https://github.com/angular/angular.js/issues/14691),
|
||||
[#16760](https://github.com/angular/angular.js/issues/16760))
|
||||
|
||||
|
||||
|
||||
<a name="1.7.5"></a>
|
||||
# 1.7.5 anti-prettification (2018-10-04)
|
||||
|
||||
## Bug Fixes
|
||||
- **ngClass:** do not break on invalid values
|
||||
([f3a565](https://github.com/angular/angular.js/commit/f3a565872d802c94bb213944791b11b483d52f73),
|
||||
[#16697](https://github.com/angular/angular.js/issues/16697),
|
||||
[#16699](https://github.com/angular/angular.js/issues/16699))
|
||||
|
||||
|
||||
<a name="1.7.4"></a>
|
||||
# 1.7.4 interstellar-exploration (2018-09-07)
|
||||
|
||||
## Bug Fixes
|
||||
- **ngAria.ngClick:** prevent default event on space/enter only for non-interactive elements
|
||||
([61b335](https://github.com/angular/angular.js/commit/61b33543ff8e7f32464dec98a46bf0a35e9b03a4),
|
||||
[#16664](https://github.com/angular/angular.js/issues/16664),
|
||||
[#16680](https://github.com/angular/angular.js/issues/16680))
|
||||
- **ngAnimate:** remove the "prepare" classes with multiple structural animations
|
||||
([3105b2](https://github.com/angular/angular.js/commit/3105b2c26a71594c4e7904efc18f4b2e9da25b1b),
|
||||
[#16681](https://github.com/angular/angular.js/issues/16681),
|
||||
[#16677](https://github.com/angular/angular.js/issues/16677))
|
||||
- **$route:** correctly extract path params if the path contains a question mark or a hash
|
||||
([2ceeb7](https://github.com/angular/angular.js/commit/2ceeb739f35e01fcebcabac4beeeb7684ae9f86d))
|
||||
- **ngHref:** allow numbers and other objects in interpolation
|
||||
([30084c](https://github.com/angular/angular.js/commit/30084c13699c814ff6703d7aa2d3947a9b2f7067),
|
||||
[#16652](https://github.com/angular/angular.js/issues/16652),
|
||||
[#16626](https://github.com/angular/angular.js/issues/16626))
|
||||
- **select:** allow to select first option with value `undefined`
|
||||
([668a33](https://github.com/angular/angular.js/commit/668a33da3439f17e61dfa8f6d9b114ebde8c9d87),
|
||||
[#16653](https://github.com/angular/angular.js/issues/16653),
|
||||
[#16656](https://github.com/angular/angular.js/issues/16656))
|
||||
|
||||
|
||||
<a name="1.7.3"></a>
|
||||
# 1.7.3 eventful-proposal (2018-08-03)
|
||||
|
||||
## Bug Fixes
|
||||
- **$location:**
|
||||
- fix infinite recursion/digest on URLs with special characters
|
||||
([e68697](https://github.com/angular/angular.js/commit/e68697e2e30695f509e6c2c1e43c2c02b7af41f0),
|
||||
[#16592](https://github.com/angular/angular.js/issues/16592),
|
||||
[#16611](https://github.com/angular/angular.js/issues/16611))
|
||||
- avoid unnecessary `$locationChange*` events due to empty hash
|
||||
([1144b1](https://github.com/angular/angular.js/commit/1144b1eccb886ea0e4a80bcb07d38a305c3263b4),
|
||||
[#16632](https://github.com/angular/angular.js/issues/16632),
|
||||
[#16636](https://github.com/angular/angular.js/issues/16636))
|
||||
- **ngMock.$httpBackend:**
|
||||
- pass failed HTTP expectations to `$exceptionHandler`
|
||||
([4adbf8](https://github.com/angular/angular.js/commit/4adbf82a84a564a8d3f0982c17a64c6163200bcd),
|
||||
[#16644](https://github.com/angular/angular.js/issues/16644))
|
||||
- correctly ignore query params in {expect,when}Route
|
||||
([be417f](https://github.com/angular/angular.js/commit/be417f28549e184fbc3c7f74251ac21fca965ae8),
|
||||
[#14173](https://github.com/angular/angular.js/issues/14173),
|
||||
[#16589](https://github.com/angular/angular.js/issues/16589))
|
||||
- **Angular:** add workaround for Safari / Webdriver problem
|
||||
([0a1db2](https://github.com/angular/angular.js/commit/0a1db2ad5f8da6902b1711a738ae4177ce9685fa),
|
||||
[#16645](https://github.com/angular/angular.js/issues/16645))
|
||||
- **$animate:** avoid memory leak with `$animate.enabled(element, enabled)`
|
||||
([4bd424](https://github.com/angular/angular.js/commit/4bd424690612885ca06028e9b27de585edc3d3c3),
|
||||
[#16649](https://github.com/angular/angular.js/issues/16649))
|
||||
- **$compile:**
|
||||
- use correct parent element when requiring on html element
|
||||
([05ac70](https://github.com/angular/angular.js/commit/05ac702bc7edae5f89c363ea661774910735ea8b),
|
||||
[#16535](https://github.com/angular/angular.js/issues/16535),
|
||||
[#16647](https://github.com/angular/angular.js/issues/16647))
|
||||
- work around Firefox `DocumentFragment` bug
|
||||
([10973c](https://github.com/angular/angular.js/commit/10973c3366676ac8e5b2728b1e006cdef4ea197e),
|
||||
[#16607](https://github.com/angular/angular.js/issues/16607),
|
||||
[#16615](https://github.com/angular/angular.js/issues/16615))
|
||||
- **ngEventDirs:**
|
||||
- pass error in handler to $exceptionHandler when event was triggered in a digest
|
||||
([688211](https://github.com/angular/angular.js/commit/6882113bc194fb10081db9bab3dd7d69dd59f311))
|
||||
- don't wrap the event handler in $apply if already in $digest
|
||||
([535ee3](https://github.com/angular/angular.js/commit/535ee32a0b4881c9fd526fb5e0ffc10919ba1800),
|
||||
[#14673](https://github.com/angular/angular.js/issues/14673),
|
||||
[#14674](https://github.com/angular/angular.js/issues/14674))
|
||||
- **angular.element:** do not break on `cleanData()` if `_data()` returns undefined
|
||||
([7cf4a2](https://github.com/angular/angular.js/commit/7cf4a2933cb017e45b0c97b0a836cbbd905ee31a),
|
||||
[#16641](https://github.com/angular/angular.js/issues/16641),
|
||||
[#16642](https://github.com/angular/angular.js/issues/16642))
|
||||
- **ngAria:** do not scroll when pressing spacebar on custom buttons
|
||||
([3a517c](https://github.com/angular/angular.js/commit/3a517c25f677294a7a9eca1660654a3edcc9e103),
|
||||
[#14665](https://github.com/angular/angular.js/issues/14665),
|
||||
[#16604](https://github.com/angular/angular.js/issues/16604))
|
||||
|
||||
|
||||
## New Features
|
||||
- **$compile:** add support for arbitrary DOM property and event bindings
|
||||
([a5914c](https://github.com/angular/angular.js/commit/a5914c94a8fa5b1eceeab9e4e6849cbf467bc26d),
|
||||
[#16428](https://github.com/angular/angular.js/issues/16428),
|
||||
[#16235](https://github.com/angular/angular.js/issues/16235),
|
||||
[#16614](https://github.com/angular/angular.js/issues/16614))
|
||||
- **ngMock:** add `$flushPendingTasks()` and `$verifyNoPendingTasks()`
|
||||
([6f7674](https://github.com/angular/angular.js/commit/6f7674a7d063d434205f75f5b861f167e8125999),
|
||||
[#14336](https://github.com/angular/angular.js/issues/14336))
|
||||
- **core:** implement more granular pending task tracking
|
||||
([17b139](https://github.com/angular/angular.js/commit/17b139f107e5471a9351af638093a8e13a69e42a))
|
||||
- **$animate:** add option data to event callbacks
|
||||
([fc64e6](https://github.com/angular/angular.js/commit/fc64e6807642512b567deb52b497bd2bff570a1f),
|
||||
[#12697](https://github.com/angular/angular.js/issues/12697),
|
||||
[#13059](https://github.com/angular/angular.js/issues/13059))
|
||||
- **form.FormController:** add $getControls()
|
||||
([c9d1e6](https://github.com/angular/angular.js/commit/c9d1e690aa597283373b78e646676fa8f1ba1b4d),
|
||||
[#16601](https://github.com/angular/angular.js/issues/16601),
|
||||
[#14749](https://github.com/angular/angular.js/issues/14749),
|
||||
[#14517](https://github.com/angular/angular.js/issues/14517),
|
||||
[#13202](https://github.com/angular/angular.js/issues/13202))
|
||||
- **ngModelOptions:** add `timeStripZeroSeconds` and `timeSecondsFormat`
|
||||
([b68221](https://github.com/angular/angular.js/commit/b682213d72d65c996a6a31ea57b79d4c4f4e3c98),
|
||||
[#10721](https://github.com/angular/angular.js/issues/10721),
|
||||
[#16510](https://github.com/angular/angular.js/issues/16510),
|
||||
[#16584](https://github.com/angular/angular.js/issues/16584))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
- **ngAnimate:** avoid repeated calls to addClass/removeClass when animation has no duration
|
||||
([093635](https://github.com/angular/angular.js/commit/0936353e9a03f072bc3c4056888fd154a96530ef),
|
||||
[#14165](https://github.com/angular/angular.js/issues/14165),
|
||||
[#14166](https://github.com/angular/angular.js/issues/14166),
|
||||
[#16613](https://github.com/angular/angular.js/issues/16613))
|
||||
|
||||
|
||||
<a name="1.7.2"></a>
|
||||
# 1.7.2 extreme-compatiplication (2018-06-12)
|
||||
|
||||
In the previous release, we removed a private, undocumented API that was no longer used by
|
||||
AngularJS. It turned out that several popular UI libraries (such as
|
||||
[AngularJS Material](https://material.angularjs.org/),
|
||||
[UI Bootstrap](https://angular-ui.github.io/bootstrap/),
|
||||
[ngDialog](http://likeastore.github.io/ngDialog/) and probably others) relied on that API.
|
||||
|
||||
In order to avoid unnecessary pain for developers, this release reverts the removal of the private
|
||||
API and restores compatibility of the aforementioned libraries with the latest AngularJS.
|
||||
|
||||
## Reverts
|
||||
- **$compile:** remove `preAssignBindingsEnabled` leftovers
|
||||
([2da495](https://github.com/angular/angular.js/commit/2da49504065e9e2b71a7a5622e45118d8abbe87e),
|
||||
[#16580](https://github.com/angular/angular.js/pull/16580),
|
||||
[a81232](https://github.com/angular/angular.js/commit/a812327acda8bc890a4c4e809f0debb761c29625),
|
||||
[#16595](https://github.com/angular/angular.js/pull/16595))
|
||||
|
||||
|
||||
<a name="1.7.1"></a>
|
||||
# 1.7.1 momentum-defiance (2018-06-08)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- **$compile:** support transcluding multi-element directives
|
||||
([789db8](https://github.com/angular/angular.js/commit/789db83a8ae0e2db5db13289b2c29e56093d967a),
|
||||
[#15554](https://github.com/angular/angular.js/issues/15554),
|
||||
[#15555](https://github.com/angular/angular.js/issues/15555))
|
||||
- **ngModel:** do not throw if view value changes on destroyed scope
|
||||
([2b6c98](https://github.com/angular/angular.js/commit/2b6c9867369fd3ef1ddb687af1153478ab62ee1b),
|
||||
[#16583](https://github.com/angular/angular.js/issues/16583),
|
||||
[#16585](https://github.com/angular/angular.js/issues/16585))
|
||||
|
||||
|
||||
## New Features
|
||||
- **$compile:** add one-way collection bindings
|
||||
([f9d1ca](https://github.com/angular/angular.js/commit/f9d1ca20c38f065f15769fbe23aee5314cb58bd4),
|
||||
[#14039](https://github.com/angular/angular.js/issues/14039),
|
||||
[#16553](https://github.com/angular/angular.js/issues/16553),
|
||||
[#15874](https://github.com/angular/angular.js/issues/15874))
|
||||
- **ngRef:** add directive to publish controller, or element into scope
|
||||
([bf841d](https://github.com/angular/angular.js/commit/bf841d35120bf3c4655fde46af4105c85a0f1cdc),
|
||||
[#16511](https://github.com/angular/angular.js/issues/16511))
|
||||
- **errorHandlingConfig:** add option to exclude error params from url
|
||||
([3d6c45](https://github.com/angular/angular.js/commit/3d6c45d76e30b1b3c4eb9672cf4a93e5251c06b3),
|
||||
[#14744](https://github.com/angular/angular.js/issues/14744),
|
||||
[#15707](https://github.com/angular/angular.js/issues/15707),
|
||||
[#16283](https://github.com/angular/angular.js/issues/16283),
|
||||
[#16299](https://github.com/angular/angular.js/issues/16299),
|
||||
[#16591](https://github.com/angular/angular.js/issues/16591))
|
||||
- **ngAria:** add support for ignoring a specific element
|
||||
([7d9d38](https://github.com/angular/angular.js/commit/7d9d387195292cb5e04984602b752d31853cfea6),
|
||||
[#14602](https://github.com/angular/angular.js/issues/14602),
|
||||
[#14672](https://github.com/angular/angular.js/issues/14672),
|
||||
[#14833](https://github.com/angular/angular.js/issues/14833))
|
||||
- **ngCookies:** support samesite option
|
||||
([10a229](https://github.com/angular/angular.js/commit/10a229ce1befdeaf6295d1635dc11391c252a91a),
|
||||
[#16543](https://github.com/angular/angular.js/issues/16543),
|
||||
[#16544](https://github.com/angular/angular.js/issues/16544))
|
||||
- **ngMessages:** add support for default message
|
||||
([a8c263](https://github.com/angular/angular.js/commit/a8c263c1947cc85ee60b4732f7e4bcdc7ba463e8),
|
||||
[#12008](https://github.com/angular/angular.js/issues/12008),
|
||||
[#12213](https://github.com/angular/angular.js/issues/12213),
|
||||
[#16587](https://github.com/angular/angular.js/issues/16587))
|
||||
- **ngMock, ngMockE2E:** add option to match latest definition for `$httpBackend` request
|
||||
([773f39](https://github.com/angular/angular.js/commit/773f39c9345479f5f8b6321236ce6ad96f77aa92),
|
||||
[#16251](https://github.com/angular/angular.js/issues/16251),
|
||||
[#11637](https://github.com/angular/angular.js/issues/11637),
|
||||
[#16560](https://github.com/angular/angular.js/issues/16560))
|
||||
- **$route:** add support for the `reloadOnUrl` configuration option
|
||||
([f4f571](https://github.com/angular/angular.js/commit/f4f571efdf86d6acbcd5c6b1de66b4b33a259125),
|
||||
[#7925](https://github.com/angular/angular.js/issues/7925),
|
||||
[#15002](https://github.com/angular/angular.js/issues/15002))
|
||||
|
||||
|
||||
<a name="1.7.0"></a>
|
||||
# 1.7.0 nonexistent-physiology (2018-05-11)
|
||||
|
||||
@@ -372,8 +640,8 @@ This in turn affects how dirty checking treats objects that prototypally
|
||||
inherit from `Array` (e.g. MobX observable arrays). AngularJS will now
|
||||
be able to handle these objects better when copying or watching.
|
||||
|
||||
### **$sce** due to:
|
||||
- **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**: handle URL sanitization through the `$sce` service
|
||||
### **$sce** :
|
||||
- due to **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**: handle URL sanitization through the `$sce` service
|
||||
|
||||
If you use `attrs.$set` for URL attributes (a[href] and img[src]) there will no
|
||||
longer be any automated sanitization of the value. This is in line with other
|
||||
@@ -387,6 +655,35 @@ Note that values that have been passed through the `$interpolate` service within
|
||||
`URL` or `MEDIA_URL` will have already been sanitized, so you would not need to sanitize
|
||||
these values again.
|
||||
|
||||
- due to **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**: handle URL sanitization through the `$sce` service
|
||||
|
||||
binding `trustAs()` and the short versions (`trustAsResourceUrl()` et al.) to
|
||||
`ngSrc`, `ngSrcset`, and `ngHref` will now raise an infinite digest error:
|
||||
|
||||
```js
|
||||
$scope.imgThumbFn = function(id) {
|
||||
return $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
};
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{imgThumbFn(imgId)}}">
|
||||
```
|
||||
This is because the `$interpolate` service is now responsible for sanitizing
|
||||
the attribute value, and its watcher receives a new object from `trustAs()`
|
||||
on every digest.
|
||||
To migrate, compute the trusted value only when the input value changes:
|
||||
|
||||
```js
|
||||
$scope.$watch('imgId', function(id) {
|
||||
$scope.imgThumb = $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{imgThumb}}">
|
||||
```
|
||||
|
||||
### **orderBy** due to:
|
||||
- **[1d8046](https://github.com/angular/angular.js/commit/1d804645f7656d592c90216a0355b4948807f6b8)**: consider `null` and `undefined` greater than other values
|
||||
|
||||
@@ -518,7 +815,7 @@ If you rely on the $modelValue validation, you can overwrite the `min`/`max` val
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
var maxValidator = ctrl.$validators.max;
|
||||
|
||||
ctrk.$validators.max = function(modelValue, viewValue) {
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return maxValidator(modelValue, modelValue);
|
||||
};
|
||||
}
|
||||
@@ -1351,7 +1648,7 @@ If you rely on the $modelValue validation, you can overwrite the `min`/`max` val
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
var maxValidator = ctrl.$validators.max;
|
||||
|
||||
ctrk.$validators.max = function(modelValue, viewValue) {
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return maxValidator(modelValue, modelValue);
|
||||
};
|
||||
}
|
||||
|
||||
+56
-3
@@ -4,6 +4,7 @@ var serveFavicon = require('serve-favicon');
|
||||
var serveStatic = require('serve-static');
|
||||
var serveIndex = require('serve-index');
|
||||
var files = require('./angularFiles').files;
|
||||
var mergeFilesFor = require('./angularFiles').mergeFilesFor;
|
||||
var util = require('./lib/grunt/utils.js');
|
||||
var versionInfo = require('./lib/versions/version-info');
|
||||
var path = require('path');
|
||||
@@ -30,7 +31,7 @@ if (!semver.satisfies(currentYarnVersion, expectedYarnVersion)) {
|
||||
}
|
||||
|
||||
// Grunt CLI version checks
|
||||
var expectedGruntVersion = pkg.engines.grunt;
|
||||
var expectedGruntVersion = pkg.engines['grunt-cli'];
|
||||
var currentGruntVersions = exec('grunt --version', {silent: true}).stdout;
|
||||
var match = /^grunt-cli v(.+)$/m.exec(currentGruntVersions);
|
||||
if (!match) {
|
||||
@@ -141,7 +142,9 @@ module.exports = function(grunt) {
|
||||
'jquery-2.2': 'karma-jquery-2.2.conf.js',
|
||||
'jquery-2.1': 'karma-jquery-2.1.conf.js',
|
||||
docs: 'karma-docs.conf.js',
|
||||
modules: 'karma-modules.conf.js'
|
||||
modules: 'karma-modules.conf.js',
|
||||
'modules-ngAnimate': 'karma-modules-ngAnimate.conf.js',
|
||||
'modules-ngMock': 'karma-modules-ngMock.conf.js'
|
||||
},
|
||||
|
||||
|
||||
@@ -211,6 +214,12 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-touch.js',
|
||||
src: util.wrap(files['angularModules']['ngTouch'], 'module')
|
||||
},
|
||||
touchModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-touch.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngTouch'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
mocks: {
|
||||
dest: 'build/angular-mocks.js',
|
||||
src: util.wrap(files['angularModules']['ngMock'], 'module'),
|
||||
@@ -220,18 +229,42 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-sanitize.js',
|
||||
src: util.wrap(files['angularModules']['ngSanitize'], 'module')
|
||||
},
|
||||
sanitizeModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-sanitize.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngSanitize'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
resource: {
|
||||
dest: 'build/angular-resource.js',
|
||||
src: util.wrap(files['angularModules']['ngResource'], 'module')
|
||||
},
|
||||
resourceModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-resource.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngResource'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
messageformat: {
|
||||
dest: 'build/angular-message-format.js',
|
||||
src: util.wrap(files['angularModules']['ngMessageFormat'], 'module')
|
||||
},
|
||||
messageformatModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-message-format.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngMessageFormat'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
messages: {
|
||||
dest: 'build/angular-messages.js',
|
||||
src: util.wrap(files['angularModules']['ngMessages'], 'module')
|
||||
},
|
||||
messagesModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-messages.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngMessages'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
animate: {
|
||||
dest: 'build/angular-animate.js',
|
||||
src: util.wrap(files['angularModules']['ngAnimate'], 'module')
|
||||
@@ -240,14 +273,32 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-route.js',
|
||||
src: util.wrap(files['angularModules']['ngRoute'], 'module')
|
||||
},
|
||||
routeModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-route.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngRoute'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
cookies: {
|
||||
dest: 'build/angular-cookies.js',
|
||||
src: util.wrap(files['angularModules']['ngCookies'], 'module')
|
||||
},
|
||||
cookiesModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-cookies.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngCookies'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
aria: {
|
||||
dest: 'build/angular-aria.js',
|
||||
src: util.wrap(files['angularModules']['ngAria'], 'module')
|
||||
},
|
||||
ariaModuleTestBundle: {
|
||||
dest: 'build/test-bundles/angular-aria.js',
|
||||
prefix: 'src/module.prefix',
|
||||
src: mergeFilesFor('karmaModules-ngAria'),
|
||||
suffix: 'src/module.suffix'
|
||||
},
|
||||
parseext: {
|
||||
dest: 'build/angular-parse-ext.js',
|
||||
src: util.wrap(files['angularModules']['ngParseExt'], 'module')
|
||||
@@ -430,7 +481,9 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('test:jquery-2.1', 'Run the jQuery 2.1 unit tests with Karma', ['tests:jquery-2.1']);
|
||||
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', [
|
||||
'build',
|
||||
'tests:modules'
|
||||
'tests:modules',
|
||||
'tests:modules-ngAnimate',
|
||||
'tests:modules-ngMock'
|
||||
]);
|
||||
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
|
||||
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', [
|
||||
|
||||
@@ -14,9 +14,9 @@ piece of cake. Best of all? It makes development fun!
|
||||
|
||||
--------------------
|
||||
|
||||
##### AngularJS will be moving to Long Term Support (LTS) mode on July 1st 2018: [Find out more](https://docs.angularjs.org/misc/version-support-status)
|
||||
**On July 1, 2018 AngularJS entered a 3 year Long Term Support period:** [Find out more](https://docs.angularjs.org/misc/version-support-status)
|
||||
|
||||
##### Looking for the new Angular? Go here: https://github.com/angular/angular
|
||||
**Looking for the new Angular? Go here:** https://github.com/angular/angular
|
||||
|
||||
--------------------
|
||||
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
# AngularJS Release instructions
|
||||
|
||||
|
||||
## Compare the list of commits between stable and unstable
|
||||
|
||||
There is a script - compare-master-to-stable.js - that helps with this.
|
||||
We just want to make sure that good commits (low risk fixes + docs fixes) got cherry-picked into stable branch and nothing interesting got merged only into stable branch.
|
||||
|
||||
|
||||
## Pick a release name (for this version)
|
||||
|
||||
A super-heroic power (adverb-verb phrase).
|
||||
|
||||
|
||||
## Generate release notes
|
||||
|
||||
Example Commit: https://github.com/angular/angular.js/commit/7ab5098c14ee4f195dbfe2681e402fe2dfeacd78
|
||||
|
||||
1) Run
|
||||
|
||||
```bash
|
||||
node_modules/.bin/changez -o changes.md -v <new version> <base branch>
|
||||
```
|
||||
|
||||
2) Review the generated file and manually fix typos, group and reorder stuff if needed.
|
||||
3) Move the content into CHANGELOG.md add release code-names to headers.
|
||||
4) Push the changes to your private github repo and review.
|
||||
5) cherry-pick the release notes commit to the appropriate branches.
|
||||
|
||||
|
||||
## Pick a commit to release (for this version)
|
||||
|
||||
Usually this will be the commit containing the release notes, but it may also be in the past.
|
||||
|
||||
|
||||
## Run "release" script
|
||||
|
||||
```bash
|
||||
scripts/jenkins/release.sh --git-push-dryrun=false --commit-sha=8822a4f --version-number=1.7.6 --version-name=gravity-manipulation
|
||||
```
|
||||
|
||||
1) The SHA is of the commit to release (could be in the past).
|
||||
|
||||
2) The version number and code-name that should be released, not the next version number (e.g. to release 1.2.12 you enter 1.2.12 as release version and the code-name that was picked for 1.2.12, cauliflower-eradication).
|
||||
|
||||
3) You will need to have write access to all the AngularJS github dist repositories and publish rights for the AngularJS packages on npm.
|
||||
|
||||
|
||||
## Update GitHub milestones
|
||||
|
||||
1) Create the next milestone if it doesn't exist yet-giving ita due date.
|
||||
2) Move all open issues and PRs for the current milestone to the next milestone<br>
|
||||
You can do this by filtering the current milestone, selecting via checklist, and moving to the next milestone within the GH issues page.
|
||||
|
||||
3) Close the current milestone click the milestones tab and close from there.
|
||||
4) Create a new holding milestone for the release after next-but don't give it a due date otherwise that will mess up the dashboard.
|
||||
|
||||
|
||||
## Push build artifacts to CDN
|
||||
|
||||
Google CDNs are fed with data from google3 every day at 11:15am PT it takes only few minutes for the import to propagate).
|
||||
If we want to make our files available, we need submit our CLs before this time on the day of the release.
|
||||
|
||||
|
||||
## Don't update the package.json (branchVersion) until the CDN has updated
|
||||
|
||||
This is the version used to compute what version to link to in the CDN. If you update this too early then the CDN lookup fails and you end up with 'null, for the version, which breaks the docs.
|
||||
|
||||
|
||||
## Verify angularjs.org download modal has latest version (updates via Travis job)
|
||||
|
||||
The versions in the modal are updated (based on the versions available on CDN) as part of the Travis deploy stage: https://github.com/angular/angularjs.org/blob/a4d25c5abcd39e8ce19d31cb1c78073d13c4c974/.travis.yml#L26
|
||||
(You may need to explicitly trigger the Travis job. e.g. re-running the last job.)
|
||||
|
||||
|
||||
## Announce the release (via official Google accounts)
|
||||
|
||||
Double check that angularjs.org is up to date with the new release version before sharing.
|
||||
|
||||
1) Collect a list of contributors
|
||||
|
||||
use: `git log --format='%aN' v1.2.12..v1.2.13 | sort -u`
|
||||
|
||||
2) Write a blog post (for minor releases, not patch releases) and publish it with the "release" tag
|
||||
3) Post on twitter as yourself (tweet from your heart; there is no template for this), retweet as @AngularJS
|
||||
|
||||
|
||||
## Party!
|
||||
|
||||
|
||||
## Major Release Tasks
|
||||
|
||||
1) Update angularjs.org to use the latest branch.
|
||||
2) Write up a migration document.
|
||||
3) Create a new git branch for the version that has been released (e.g. 1.8.x).
|
||||
4) Check that the build and release scripts still work.
|
||||
5) Update the dist-tag of the old branch, see https://github.com/angular/angular.js/pull/12722.
|
||||
6) Write a blog post.
|
||||
Vendored
+80
-13
@@ -28,6 +28,7 @@ var angularFiles = {
|
||||
'src/ng/httpBackend.js',
|
||||
'src/ng/interpolate.js',
|
||||
'src/ng/interval.js',
|
||||
'src/ng/intervalFactory.js',
|
||||
'src/ng/jsonpCallbacks.js',
|
||||
'src/ng/locale.js',
|
||||
'src/ng/location.js',
|
||||
@@ -40,6 +41,7 @@ var angularFiles = {
|
||||
'src/ng/sanitizeUri.js',
|
||||
'src/ng/sce.js',
|
||||
'src/ng/sniffer.js',
|
||||
'src/ng/taskTrackerFactory.js',
|
||||
'src/ng/templateRequest.js',
|
||||
'src/ng/testability.js',
|
||||
'src/ng/timeout.js',
|
||||
@@ -74,6 +76,7 @@ var angularFiles = {
|
||||
'src/ng/directive/ngNonBindable.js',
|
||||
'src/ng/directive/ngOptions.js',
|
||||
'src/ng/directive/ngPluralize.js',
|
||||
'src/ng/directive/ngRef.js',
|
||||
'src/ng/directive/ngRepeat.js',
|
||||
'src/ng/directive/ngShowHide.js',
|
||||
'src/ng/directive/ngStyle.js',
|
||||
@@ -103,6 +106,7 @@ var angularFiles = {
|
||||
'src/ngAnimate/animateJs.js',
|
||||
'src/ngAnimate/animateJsDriver.js',
|
||||
'src/ngAnimate/animateQueue.js',
|
||||
'src/ngAnimate/animateCache.js',
|
||||
'src/ngAnimate/animation.js',
|
||||
'src/ngAnimate/ngAnimateSwap.js',
|
||||
'src/ngAnimate/module.js'
|
||||
@@ -130,6 +134,7 @@ var angularFiles = {
|
||||
],
|
||||
'ngRoute': [
|
||||
'src/shallowCopy.js',
|
||||
'src/routeToRegExp.js',
|
||||
'src/ngRoute/route.js',
|
||||
'src/ngRoute/routeParams.js',
|
||||
'src/ngRoute/directive/ngView.js'
|
||||
@@ -139,6 +144,7 @@ var angularFiles = {
|
||||
'src/ngSanitize/filter/linky.js'
|
||||
],
|
||||
'ngMock': [
|
||||
'src/routeToRegExp.js',
|
||||
'src/ngMock/angular-mocks.js',
|
||||
'src/ngMock/browserTrigger.js'
|
||||
],
|
||||
@@ -183,21 +189,72 @@ var angularFiles = {
|
||||
'src/angular.bind.js'
|
||||
],
|
||||
|
||||
'karmaModules': [
|
||||
'karmaModules-ngAnimate': [
|
||||
'build/angular.js',
|
||||
'@angularSrcModules',
|
||||
'build/angular-mocks.js',
|
||||
'test/modules/no_bootstrap.js',
|
||||
'test/helpers/*.js',
|
||||
'test/ngAnimate/*.js',
|
||||
'test/ngMessageFormat/*.js',
|
||||
'test/ngMessages/*.js',
|
||||
'test/ngMock/*.js',
|
||||
'test/ngCookies/*.js',
|
||||
'test/ngRoute/**/*.js',
|
||||
'test/ngResource/*.js',
|
||||
'test/ngSanitize/**/*.js',
|
||||
'test/ngTouch/**/*.js',
|
||||
'test/ngAria/*.js'
|
||||
'test/helpers/matchers.js',
|
||||
'test/helpers/privateMocks.js',
|
||||
'test/helpers/support.js',
|
||||
'test/helpers/testabilityPatch.js',
|
||||
'@angularSrcModuleNgAnimate',
|
||||
'test/ngAnimate/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngAria': [
|
||||
'@angularSrcModuleNgAria',
|
||||
'test/ngAria/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngCookies': [
|
||||
'@angularSrcModuleNgCookies',
|
||||
'test/ngCookies/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngMessageFormat': [
|
||||
'@angularSrcModuleNgMessageFormat',
|
||||
'test/ngMessageFormat/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngMessages': [
|
||||
'build/angular-animate.js',
|
||||
'@angularSrcModuleNgMessages',
|
||||
'test/ngMessages/**/*.js'
|
||||
],
|
||||
|
||||
// ngMock doesn't include the base because it must use the ngMock src files
|
||||
'karmaModules-ngMock': [
|
||||
'build/angular.js',
|
||||
'src/ngMock/*.js',
|
||||
'test/modules/no_bootstrap.js',
|
||||
'test/helpers/matchers.js',
|
||||
'test/helpers/privateMocks.js',
|
||||
'test/helpers/support.js',
|
||||
'test/helpers/testabilityPatch.js',
|
||||
'src/routeToRegExp.js',
|
||||
'build/angular-animate.js',
|
||||
'test/ngMock/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngResource': [
|
||||
'@angularSrcModuleNgResource',
|
||||
'test/ngResource/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngRoute': [
|
||||
'build/angular-animate.js',
|
||||
'@angularSrcModuleNgRoute',
|
||||
'test/ngRoute/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngSanitize': [
|
||||
'@angularSrcModuleNgSanitize',
|
||||
'test/ngSanitize/**/*.js'
|
||||
],
|
||||
|
||||
'karmaModules-ngTouch': [
|
||||
'@angularSrcModuleNgTouch',
|
||||
'test/ngTouch/**/*.js'
|
||||
],
|
||||
|
||||
'karmaJquery': [
|
||||
@@ -226,6 +283,16 @@ var angularFiles = {
|
||||
});
|
||||
});
|
||||
|
||||
angularFiles['angularSrcModuleNgAnimate'] = angularFiles['angularModules']['ngAnimate'];
|
||||
angularFiles['angularSrcModuleNgAria'] = angularFiles['angularModules']['ngAria'];
|
||||
angularFiles['angularSrcModuleNgCookies'] = angularFiles['angularModules']['ngCookies'];
|
||||
angularFiles['angularSrcModuleNgMessageFormat'] = angularFiles['angularModules']['ngMessageFormat'];
|
||||
angularFiles['angularSrcModuleNgMessages'] = angularFiles['angularModules']['ngMessages'];
|
||||
angularFiles['angularSrcModuleNgResource'] = angularFiles['angularModules']['ngResource'];
|
||||
angularFiles['angularSrcModuleNgRoute'] = angularFiles['angularModules']['ngRoute'];
|
||||
angularFiles['angularSrcModuleNgSanitize'] = angularFiles['angularModules']['ngSanitize'];
|
||||
angularFiles['angularSrcModuleNgTouch'] = angularFiles['angularModules']['ngTouch'];
|
||||
|
||||
angularFiles['angularSrcModules'] = [].concat(
|
||||
angularFiles['angularModules']['ngAnimate'],
|
||||
angularFiles['angularModules']['ngMessageFormat'],
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', ['ngAnimate'])
|
||||
.config(function($animateProvider) {
|
||||
$animateProvider.classNameFilter(/animate-/);
|
||||
})
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'classfilter';
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', [])
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'noanimate';
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('repeatAnimateBenchmark', ['ngAnimate'])
|
||||
.run(function($rootScope) {
|
||||
$rootScope.fileType = 'default';
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/* eslint-env node */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
scripts: [
|
||||
{
|
||||
id: 'angular',
|
||||
src: '/build/angular.js'
|
||||
},
|
||||
{
|
||||
id: 'angular-animate',
|
||||
src: '/build/angular-animate.js'
|
||||
},
|
||||
{
|
||||
id: 'app',
|
||||
src: 'app.js'
|
||||
},
|
||||
{
|
||||
src: 'common.js'
|
||||
}]
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
var app = angular.module('repeatAnimateBenchmark');
|
||||
|
||||
app.config(function($compileProvider, $animateProvider) {
|
||||
if ($compileProvider.debugInfoEnabled) {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.run(function($animate) {
|
||||
if ($animate.enabled) {
|
||||
$animate.enabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
app.controller('DataController', function($scope, $rootScope, $animate) {
|
||||
var totalRows = 500;
|
||||
var totalColumns = 20;
|
||||
|
||||
var data = $scope.data = [];
|
||||
|
||||
function fillData() {
|
||||
if ($animate.enabled) {
|
||||
$animate.enabled($scope.benchmarkType !== 'globallyDisabled');
|
||||
}
|
||||
|
||||
for (var i = 0; i < totalRows; i++) {
|
||||
data[i] = [];
|
||||
for (var j = 0; j < totalColumns; j++) {
|
||||
data[i][j] = {
|
||||
i: i
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'enter',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
fillData();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'leave',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
data = $scope.data = [];
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.directive('disableAnimations', function($animate) {
|
||||
return {
|
||||
link: {
|
||||
pre: function(s, e) {
|
||||
$animate.enabled(e, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('noop', function($animate) {
|
||||
return {
|
||||
link: {
|
||||
pre: angular.noop
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('baseline', function($document) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, $element) {
|
||||
var document = $document[0];
|
||||
|
||||
var i, j, row, cell, comment;
|
||||
var template = document.createElement('span');
|
||||
template.setAttribute('ng-repeat', 'foo in foos');
|
||||
template.classList.add('ng-scope');
|
||||
template.appendChild(document.createElement('span'));
|
||||
template.appendChild(document.createTextNode(':'));
|
||||
|
||||
function createList() {
|
||||
for (i = 0; i < $scope.data.length; i++) {
|
||||
row = document.createElement('div');
|
||||
$element[0].appendChild(row);
|
||||
for (j = 0; j < $scope.data[i].length; j++) {
|
||||
cell = template.cloneNode(true);
|
||||
row.appendChild(cell);
|
||||
cell.childNodes[0].textContent = i;
|
||||
cell.ng339 = 'xxx';
|
||||
comment = document.createComment('ngRepeat end: bar in foo');
|
||||
row.appendChild(comment);
|
||||
}
|
||||
|
||||
comment = document.createComment('ngRepeat end: foo in foos');
|
||||
$element[0].appendChild(comment);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch('data.length', function(newVal) {
|
||||
if (newVal === 0) {
|
||||
while ($element[0].firstChild) {
|
||||
$element[0].removeChild($element[0].firstChild);
|
||||
}
|
||||
} else {
|
||||
createList();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,70 @@
|
||||
<div ng-app="repeatAnimateBenchmark" ng-cloak>
|
||||
<div ng-controller="DataController">
|
||||
<div class="container-fluid">
|
||||
<p>
|
||||
Tests rendering of an ngRepeat with 500 elements.<br>
|
||||
Animations can be enabled / disabled in different ways.<br>
|
||||
Two tests require reloading the app with different module / app configurations.
|
||||
</p>
|
||||
|
||||
<div><label><input type="radio" ng-model="benchmarkType" value="none">none: </label></div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" value="baseline">baseline (vanilla Javascript): </label></div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'default'" value="enabled">enabled : </label> (requires <a href="./">app.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'default' && fileType !== 'classfilter'" value="globallyDisabled">globally disabled:</label> (requires <a href="./">app.js</a> or <a href="?app=app-classfilter.js">app-classfilter.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'default'" value="disabledParentElement">disabled by $animate.enabled() on parent element: </label> (requires <a href="./">app.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'noanimate'" value="noanimate">Without ngAnimate:</label> (requires <a href="?app=app-noanimate.js">app-noanimate.js</a>)</div>
|
||||
<div><label><input type="radio" ng-model="benchmarkType" ng-disabled="fileType !== 'classfilter'" value="disabledClassFilter">disabled by classNameFilter on element:</label> (requires <a href="?app=app-classfilter.js">app-classfilter.js</a>)</div>
|
||||
|
||||
<ng-switch on="benchmarkType">
|
||||
<baseline ng-switch-when="baseline">
|
||||
</baseline>
|
||||
<div ng-switch-when="noanimate">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="enabled">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="globallyDisabled">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="disabledClassFilter">
|
||||
<div noop>
|
||||
<div ng-repeat="row in data">
|
||||
<span class="disable-animations" ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="disabledParentElement">
|
||||
<div disable-animations>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span>{{column.i}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-switch>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,13 +91,16 @@ directivesModule
|
||||
.component('tocTree', {
|
||||
template: '<ul>' +
|
||||
'<li ng-repeat="item in $ctrl.items">' +
|
||||
'<a ng-href="#{{item.fragment}}">{{item.title}}</a>' +
|
||||
'<a ng-href="{{ $ctrl.path }}#{{item.fragment}}">{{item.title}}</a>' +
|
||||
'<toc-tree ng-if="::item.children.length > 0" items="item.children"></toc-tree>' +
|
||||
'</li>' +
|
||||
'</ul>',
|
||||
bindings: {
|
||||
items: '<'
|
||||
}
|
||||
},
|
||||
controller: ['$location', /** @this */ function($location) {
|
||||
this.path = $location.path().replace(/^\/?(.+?)(\/index)?\/?$/, '$1');
|
||||
}]
|
||||
})
|
||||
.directive('tocContainer', function() {
|
||||
return {
|
||||
|
||||
@@ -148,6 +148,7 @@ module.exports = new Package('angularjs', [
|
||||
|
||||
.config(function(checkAnchorLinksProcessor) {
|
||||
checkAnchorLinksProcessor.base = '/';
|
||||
checkAnchorLinksProcessor.errorOnUnmatchedLinks = true;
|
||||
// We are only interested in docs that have an area (i.e. they are pages)
|
||||
checkAnchorLinksProcessor.checkDoc = function(doc) { return doc.area; };
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@ var cdnUrl = googleCdnUrl + versionInfo.cdnVersion;
|
||||
// docs.angularjs.org and code.angularjs.org need them.
|
||||
var versionPath = versionInfo.currentVersion.isSnapshot ?
|
||||
'snapshot' :
|
||||
(versionInfo.currentVersion.version || versionInfo.currentVersion.version);
|
||||
versionInfo.currentVersion.version;
|
||||
var examplesDependencyPath = angularCodeUrl + versionPath + '/';
|
||||
|
||||
module.exports = function productionDeployment(getVersion) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if method.this %}
|
||||
<h4>Method's {% code %}this{% endcode %}</h4>
|
||||
<h4>Method's `this`</h4>
|
||||
{$ method.this | marked $}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% if doc.this %}
|
||||
<h3>Method's {% code %}this{% endcode %}</h3>
|
||||
<h3>Method's `this`</h3>
|
||||
{$ doc.this | marked $}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# AngularJS API Docs
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**AngularJS will be moving to Long Term Support (LTS) mode on July 1st 2018.**: [Find out more](misc/version-support-status).
|
||||
**On July 1, 2018 AngularJS entered a 3 year Long Term Support period:** [Find out more](misc/version-support-status).
|
||||
</div>
|
||||
|
||||
## Welcome to the AngularJS API docs page.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
@ngdoc error
|
||||
@name $compile:ctxoverride
|
||||
@fullName DOM Property Security Context Override
|
||||
@description
|
||||
|
||||
This error occurs when the security context for a property is defined via {@link ng.$compileProvider#addPropertySecurityContext addPropertySecurityContext()} multiple times under different security contexts.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
$compileProvider.addPropertySecurityContext("my-element", "src", $sce.MEDIA_URL);
|
||||
$compileProvider.addPropertySecurityContext("my-element", "src", $sce.RESOURCE_URL); //throws
|
||||
```
|
||||
@@ -1,12 +1,12 @@
|
||||
@ngdoc error
|
||||
@name $compile:nodomevents
|
||||
@fullName Interpolated Event Attributes
|
||||
@fullName Event Attribute/Property Binding
|
||||
@description
|
||||
|
||||
This error occurs when one tries to create a binding for event handler attributes like `onclick`, `onload`, `onsubmit`, etc.
|
||||
This error occurs when one tries to create a binding for event handler attributes or properties like `onclick`, `onload`, `onsubmit`, etc.
|
||||
|
||||
There is no practical value in binding to these attributes and doing so only exposes your application to security vulnerabilities like XSS.
|
||||
For these reasons binding to event handler attributes (all attributes that start with `on` and `formaction` attribute) is not supported.
|
||||
There is no practical value in binding to these attributes/properties and doing so only exposes your application to security vulnerabilities like XSS.
|
||||
For these reasons binding to event handler attributes and properties (`formaction` and all starting with `on`) is not supported.
|
||||
|
||||
|
||||
An example code that would allow XSS vulnerability by evaluating user input in the window context could look like this:
|
||||
@@ -17,4 +17,4 @@ An example code that would allow XSS vulnerability by evaluating user input in t
|
||||
|
||||
Since the `onclick` evaluates the value as JavaScript code in the window context, setting the `username` model to a value like `javascript:alert('PWND')` would result in script injection when the `div` is clicked.
|
||||
|
||||
|
||||
Please use the `ng-*` or `ng-on-*` versions instead (such as `ng-click` or `ng-on-click` rather than `onclick`).
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
@ngdoc error
|
||||
@name ngRef:noctrl
|
||||
@fullName A controller for the value of `ngRefRead` could not be found on the element.
|
||||
@description
|
||||
|
||||
This error occurs when the {@link ng.ngRef ngRef directive} specifies
|
||||
a value in `ngRefRead` that cannot be resolved to a directive / component controller.
|
||||
|
||||
Causes for this error can be:
|
||||
|
||||
1. Your `ngRefRead` value has a typo.
|
||||
2. You have a typo in the *registered* directive / component name.
|
||||
3. The directive / component does not have a controller.
|
||||
|
||||
Note that `ngRefRead` takes the name of the component / directive, not the name of controller, and
|
||||
also not the combination of directive and 'Controller'. For example, for a directive called 'myDirective',
|
||||
the correct declaration is `<div ng-ref="$ctrl.ref" ng-ref-read="myDirective">`.
|
||||
@@ -0,0 +1,27 @@
|
||||
@ngdoc error
|
||||
@name ngRef:nonassign
|
||||
@fullName Non-Assignable Expression
|
||||
@description
|
||||
|
||||
This error occurs when ngRef defines an expression that is not-assignable.
|
||||
|
||||
In order for ngRef to work, it must be possible to write the reference into the path defined with the expression.
|
||||
|
||||
For example, the following expressions are non-assignable:
|
||||
|
||||
```
|
||||
<my-directive ng-ref="{}"></my-directive>
|
||||
|
||||
<my-directive ng-ref="myFn()"></my-directive>
|
||||
|
||||
<!-- missing attribute value is also invalid -->
|
||||
<my-directive ng-ref></my-directive>
|
||||
|
||||
```
|
||||
|
||||
To resolve this error, use a path expression that is assignable:
|
||||
|
||||
```
|
||||
<my-directive ng-ref="$ctrl.reference"></my-directive>
|
||||
|
||||
```
|
||||
@@ -3,7 +3,7 @@
|
||||
@sortOrder 500
|
||||
@description
|
||||
|
||||
# What does it do?
|
||||
# Using the `$location` service
|
||||
|
||||
The `$location` service parses the URL in the browser address bar (based on [`window.location`](https://developer.mozilla.org/en/window.location)) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into the `$location` service and
|
||||
@@ -76,7 +76,7 @@ the current URL in the browser.
|
||||
It does not cause a full page reload when the browser URL is changed. To reload the page after
|
||||
changing the URL, use the lower-level API, `$window.location.href`.
|
||||
|
||||
# General overview of the API
|
||||
## General overview of the API
|
||||
|
||||
The `$location` service can behave differently, depending on the configuration that was provided to
|
||||
it when it was instantiated. The default configuration is suitable for many applications, for
|
||||
@@ -85,7 +85,7 @@ others customizing the configuration can enable new features.
|
||||
Once the `$location` service is instantiated, you can interact with it via jQuery-style getter and
|
||||
setter methods that allow you to get or change the current URL in the browser.
|
||||
|
||||
## `$location` service configuration
|
||||
### `$location` service configuration
|
||||
|
||||
To configure the `$location` service, retrieve the
|
||||
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
|
||||
@@ -113,12 +113,12 @@ To configure the `$location` service, retrieve the
|
||||
Prefix used for Hashbang URLs (used in Hashbang mode or in legacy browsers in HTML5 mode).<br />
|
||||
Default: `'!'`
|
||||
|
||||
### Example configuration
|
||||
#### Example configuration
|
||||
```js
|
||||
$locationProvider.html5Mode(true).hashPrefix('*');
|
||||
```
|
||||
|
||||
## Getter and setter methods
|
||||
### Getter and setter methods
|
||||
|
||||
`$location` service provides getter methods for read-only parts of the URL (absUrl, protocol, host,
|
||||
port) and getter / setter methods for url, path, search, hash:
|
||||
@@ -137,7 +137,7 @@ change multiple segments in one go, chain setters like this:
|
||||
$location.path('/newValue').search({key: value});
|
||||
```
|
||||
|
||||
## Replace method
|
||||
### Replace method
|
||||
|
||||
There is a special `replace` method which can be used to tell the $location service that the next
|
||||
time the $location service is synced with the browser, the last history record should be replaced
|
||||
@@ -173,7 +173,7 @@ encoded.
|
||||
`/path?search=a&b=c#hash`. The segments are encoded as well.
|
||||
|
||||
|
||||
# Hashbang and HTML5 Modes
|
||||
## Hashbang and HTML5 Modes
|
||||
|
||||
`$location` service has two configuration modes which control the format of the URL in the browser
|
||||
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
|
||||
@@ -221,7 +221,7 @@ facilitate the browser URL change and history management.
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Hashbang mode (default mode)
|
||||
### Hashbang mode (default mode)
|
||||
|
||||
In this mode, `$location` uses Hashbang URLs in all browsers.
|
||||
AngularJS also does not intercept and rewrite links in this mode. I.e. links work
|
||||
@@ -229,7 +229,7 @@ as expected and also perform full page reloads when other parts of the url
|
||||
than the hash fragment was changed.
|
||||
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
```js
|
||||
it('should show example', function() {
|
||||
@@ -255,7 +255,7 @@ it('should show example', function() {
|
||||
});
|
||||
```
|
||||
|
||||
## HTML5 mode
|
||||
### HTML5 mode
|
||||
|
||||
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
|
||||
through the HTML5 history API. This allows for use of regular URL path and search segments,
|
||||
@@ -271,7 +271,7 @@ Note that in this mode, AngularJS intercepts all links (subject to the "Html lin
|
||||
and updates the url in a way that never performs a full page reload.
|
||||
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
```js
|
||||
it('should show example', function() {
|
||||
@@ -320,14 +320,14 @@ it('should show example (when browser doesn\'t support HTML5 mode', function() {
|
||||
});
|
||||
```
|
||||
|
||||
### Fallback for legacy browsers
|
||||
#### Fallback for legacy browsers
|
||||
|
||||
For browsers that support the HTML5 history API, `$location` uses the HTML5 history API to write
|
||||
path and search. If the history API is not supported by a browser, `$location` supplies a Hashbang
|
||||
URL. This frees you from having to worry about whether the browser viewing your app supports the
|
||||
history API or not; the `$location` service makes this transparent to you.
|
||||
|
||||
### HTML link rewriting
|
||||
#### HTML link rewriting
|
||||
|
||||
When you use HTML5 history API mode, you will not need special hashbang links. All you have to do
|
||||
is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
@@ -361,7 +361,7 @@ Note that [attribute name normalization](guide/directive#normalization) does not
|
||||
`'internalLink'` will **not** match `'internal-link'`.
|
||||
|
||||
|
||||
### Relative links
|
||||
#### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. AngularJS requires you to specify the url
|
||||
base in the head of your main html file (`<base href="/my-base/index.html">`) unless `html5Mode.requireBase`
|
||||
@@ -374,14 +374,14 @@ will only change `$location.hash()` and not modify the url otherwise. This is us
|
||||
to anchors on the same page without needing to know on which page the user currently is.
|
||||
|
||||
|
||||
### Server side
|
||||
#### Server side
|
||||
|
||||
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
|
||||
to entry point of your application (e.g. index.html). Requiring a `<base>` tag is also important for
|
||||
this case, as it allows AngularJS to differentiate between the part of the url that is the application
|
||||
base and the path that should be handled by the application.
|
||||
|
||||
### Base href constraints
|
||||
#### Base href constraints
|
||||
|
||||
The `$location` service is not able to function properly if the current URL is outside the URL given
|
||||
as the base href. This can have subtle confusing consequences...
|
||||
@@ -403,7 +403,7 @@ legacy browsers and hashbang links in modern browser:
|
||||
- Modern browser will rewrite hashbang URLs to regular URLs.
|
||||
- Older browsers will redirect regular URLs to hashbang URLs.
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
Here you can see two `$location` instances that show the difference between **Html5 mode** and **Html5 Fallback mode**.
|
||||
Note that to simulate different levels of browser support, the `$location` instances are connected to
|
||||
@@ -415,7 +415,7 @@ redirect to regular / hashbang url, as this conversion happens only during parsi
|
||||
|
||||
In these examples we use `<base href="/base/index.html" />`. The inputs represent the address bar of the browser.
|
||||
|
||||
#### Browser in HTML5 mode
|
||||
##### Browser in HTML5 mode
|
||||
<example module="html5-mode" name="location-html5-mode">
|
||||
<file name="index.html">
|
||||
<div ng-controller="LocationController">
|
||||
@@ -565,7 +565,7 @@ In these examples we use `<base href="/base/index.html" />`. The inputs represen
|
||||
|
||||
</example>
|
||||
|
||||
#### Browser in HTML5 Fallback mode (Hashbang mode)
|
||||
##### Browser in HTML5 Fallback mode (Hashbang mode)
|
||||
<example module="hashbang-mode" name="location-hashbang-mode">
|
||||
<file name="index.html">
|
||||
<div ng-controller="LocationController">
|
||||
@@ -718,15 +718,15 @@ In these examples we use `<base href="/base/index.html" />`. The inputs represen
|
||||
|
||||
</example>
|
||||
|
||||
# Caveats
|
||||
## Caveats
|
||||
|
||||
## Page reload navigation
|
||||
### Page reload navigation
|
||||
|
||||
The `$location` service allows you to change only the URL; it does not allow you to reload the
|
||||
page. When you need to change the URL and reload the page or navigate to a different page, please
|
||||
use a lower level API, {@link ng.$window $window.location.href}.
|
||||
|
||||
## Using $location outside of the scope life-cycle
|
||||
### Using $location outside of the scope life-cycle
|
||||
|
||||
`$location` knows about AngularJS's {@link ng.$rootScope.Scope scope} life-cycle. When a URL changes in
|
||||
the browser it updates the `$location` and calls `$apply` so that all
|
||||
@@ -738,7 +738,7 @@ propagate this change into browser and will notify all the {@link ng.$rootScope.
|
||||
When you want to change the `$location` from outside AngularJS (for example, through a DOM Event or
|
||||
during testing) - you must call `$apply` to propagate the changes.
|
||||
|
||||
## $location.path() and ! or / prefixes
|
||||
### $location.path() and ! or / prefixes
|
||||
|
||||
A path should always begin with forward slash (`/`); the `$location.path()` setter will add the
|
||||
forward slash if it is missing.
|
||||
@@ -746,22 +746,17 @@ forward slash if it is missing.
|
||||
Note that the `!` prefix in the hashbang mode is not part of `$location.path()`; it is actually
|
||||
`hashPrefix`.
|
||||
|
||||
## Crawling your app
|
||||
### Crawling your app
|
||||
|
||||
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
|
||||
your document:
|
||||
Most modern search engines are able to crawl AJAX applications with dynamic content, provided all
|
||||
included resources are available to the crawler bots.
|
||||
|
||||
```html
|
||||
<meta name="fragment" content="!" />
|
||||
```
|
||||
There also exists a special
|
||||
[AJAX crawling scheme](http://code.google.com/web/ajaxcrawling/docs/specification.html) developed by
|
||||
Google that allows bots to crawl the static equivalent of a dynamically generated page,
|
||||
but this schema has been deprecated, and support for it may vary by search engine.
|
||||
|
||||
This will cause crawler bot to request links with `_escaped_fragment_` param so that your server
|
||||
can recognize the crawler and serve a HTML snapshots. For more information about this technique,
|
||||
see [Making AJAX Applications
|
||||
Crawlable](http://code.google.com/web/ajaxcrawling/docs/specification.html).
|
||||
|
||||
|
||||
# Testing with the $location service
|
||||
## Testing with the $location service
|
||||
|
||||
When using `$location` service during testing, you are outside of the angular's {@link
|
||||
ng.$rootScope.Scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`.
|
||||
@@ -784,85 +779,6 @@ describe('serviceUnderTest', function() {
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
# Migrating from earlier AngularJS releases
|
||||
|
||||
In earlier releases of AngularJS, `$location` used `hashPath` or `hashSearch` to process path and
|
||||
search methods. With this release, the `$location` service processes path and search methods and
|
||||
then uses the information it obtains to compose hashbang URLs (such as
|
||||
`http://server.com/#!/path?search=a`), when necessary.
|
||||
|
||||
## Changes to your code
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr class="head">
|
||||
<th>Navigation inside the app</th>
|
||||
<th>Change to</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.hash = value<br />$location.update(value)<br
|
||||
/>$location.updateHash(value)</td>
|
||||
<td>$location.path(path).search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath = path</td>
|
||||
<td>$location.path(path)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch = search</td>
|
||||
<td>$location.search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<th>Navigation outside the app</td>
|
||||
<th>Use lower level API</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.update(value)</td>
|
||||
<td>$window.location.href = value</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location[protocol | host | port | path | search]</td>
|
||||
<td>$window.location[protocol | host | port | path | search]</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<th>Read access</td>
|
||||
<th>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath</td>
|
||||
<td>$location.path()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch</td>
|
||||
<td>$location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href<br />$location.protocol<br />$location.host<br />$location.port<br
|
||||
/>$location.hash</td>
|
||||
<td>$location.absUrl()<br />$location.protocol()<br />$location.host()<br />$location.port()<br
|
||||
/>$location.path() + $location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.path<br />$location.search</td>
|
||||
<td>$window.location.path<br />$window.location.search</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Two-way binding to $location
|
||||
|
||||
Because `$location` uses getters/setters, you can use `ng-model-options="{ getterSetter: true }"`
|
||||
@@ -884,6 +800,6 @@ angular.module('locationExample', [])
|
||||
</file>
|
||||
</example>
|
||||
|
||||
# Related API
|
||||
## Related API
|
||||
|
||||
* {@link ng.$location `$location` API}
|
||||
|
||||
@@ -222,23 +222,26 @@ triggered:
|
||||
|
||||
| Directive | Supported Animations |
|
||||
|-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
||||
| {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
| {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
| {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
| {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
| {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
| {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
| {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
| {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave |
|
||||
| {@link ng.directive:ngClass#animations ngClass / {{class}​}} | add and remove |
|
||||
| {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove |
|
||||
| {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove |
|
||||
| {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) |
|
||||
| {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
| {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
| {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
| {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
| {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
| {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
| {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
|
||||
| {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
| {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
| {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
| {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
| {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
|
||||
(More information can be found by visiting the documentation associated with each directive.)
|
||||
|
||||
For a full breakdown of the steps involved during each animation event, refer to the
|
||||
{@link ng.$animate API docs}.
|
||||
{@link ng.$animate `$animate` API docs}.
|
||||
|
||||
## How do I use animations in my own directives?
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ components should follow a few simple conventions:
|
||||
}
|
||||
```
|
||||
|
||||
- **Components have a well-defined lifecycle**
|
||||
- **Components have a well-defined lifecycle:**
|
||||
Each component can implement "lifecycle hooks". These are methods that will be called at certain points in the life
|
||||
of the component. The following hook methods can be implemented:
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ Right now, the `InvoiceController` contains all logic of our example. When the a
|
||||
is a good practice to move view-independent logic from the controller into a
|
||||
<a name="service">{@link services service}</a>, so it can be reused by other parts
|
||||
of the application as well. Later on, we could also change that service to load the exchange rates
|
||||
from the web, e.g. by calling the [Fixer.io](http://fixer.io) exchange rate API, without changing the controller.
|
||||
from the web, e.g. by calling the [exchangeratesapi.io](https://exchangeratesapi.io) exchange rate API, without changing the controller.
|
||||
|
||||
Let's refactor our example and move the currency conversion into a service in another file:
|
||||
|
||||
@@ -300,7 +300,7 @@ to something shorter like `a`.
|
||||
|
||||
## Accessing the backend
|
||||
|
||||
Let's finish our example by fetching the exchange rates from the [Fixer.io](http://fixer.io) exchange rate API.
|
||||
Let's finish our example by fetching the exchange rates from the [exchangeratesapi.io](https://exchangeratesapi.io) exchange rate API.
|
||||
The following example shows how this is done with AngularJS:
|
||||
|
||||
<example name="guide-concepts-3" ng-app-included="true">
|
||||
@@ -331,7 +331,7 @@ The following example shows how this is done with AngularJS:
|
||||
};
|
||||
|
||||
var refresh = function() {
|
||||
var url = 'https://api.fixer.io/latest?base=USD&symbols=' + currencies.join(",");
|
||||
var url = 'https://api.exchangeratesapi.io/latest?base=USD&symbols=' + currencies.join(",");
|
||||
return $http.get(url).then(function(response) {
|
||||
usdToForeignRates = response.data.rates;
|
||||
usdToForeignRates['USD'] = 1;
|
||||
|
||||
@@ -43,7 +43,7 @@ In AngularJS applications, you move the job of filling page templates with data
|
||||
|
||||
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}
|
||||
* **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Internationalization and Localization:** {@link guide/i18n AngularJS Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
|
||||
* **Internationalization and Localization:** {@link guide/i18n AngularJS Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](https://blog.novanet.no/creating-multilingual-support-using-angularjs/)
|
||||
* **Touch events:** {@link ngTouch Touch events}
|
||||
* **Accessibility:** {@link guide/accessibility ngAria}
|
||||
|
||||
|
||||
+147
-101
@@ -25,7 +25,7 @@ Additionally, we have removed some long-deprecated modules and APIs.
|
||||
|
||||
The most notable changes are:
|
||||
|
||||
- $resource has now support for request and requestError interceptors
|
||||
- `$resource` has now support for request and requestError interceptors
|
||||
|
||||
- Several deprecated features have been removed:
|
||||
- the `$controllerProvider.allowGlobals()` flag
|
||||
@@ -36,8 +36,8 @@ The most notable changes are:
|
||||
- the complete `ngScenario` module
|
||||
|
||||
Please note that feature development (without breaking changes) has happened in parallel on the
|
||||
1.6.x branch, so 1.7 doesn't contain many new features, but you may still benefit from those features
|
||||
that were added (with possible BCs), bugfixes, and a few smaller performance improvements.
|
||||
1.6.x branch, so 1.7 doesn't contain many new features, but you may still benefit from those
|
||||
features that were added (with possible BCs), bugfixes, and a few smaller performance improvements.
|
||||
|
||||
|
||||
<br />
|
||||
@@ -48,11 +48,11 @@ Below is the full list of breaking changes:
|
||||
<a name="migrate1.6to1.7-ng-directives"></a>
|
||||
### Core: _Directives_
|
||||
|
||||
<a name="migrate1.6to1.7-ng-directives-form"></a>
|
||||
|
||||
#### **form**
|
||||
|
||||
**Due to [223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77)**,
|
||||
forms will now set $submitted on child forms when they are submitted.
|
||||
forms will now set `$submitted` on child forms when they are submitted.
|
||||
For example:
|
||||
```
|
||||
<form name="parentform" ng-submit="$ctrl.submit()">
|
||||
@@ -63,15 +63,16 @@ For example:
|
||||
</form>
|
||||
```
|
||||
|
||||
Submitting this form will set $submitted on "parentform" and "childform".
|
||||
Submitting this form will set `$submitted` on "parentform" and "childform".
|
||||
Previously, it was only set on "parentform".
|
||||
|
||||
This change was introduced because mixing form and ngForm does not create
|
||||
This change was introduced because mixing `form` and `ngForm` does not create
|
||||
logically separate forms, but rather something like input groups.
|
||||
Therefore, child forms should inherit the submission state from their parent form.
|
||||
|
||||
|
||||
#### **input[radio]** and **input[checkbox]**
|
||||
|
||||
**Due to [656c8f](https://github.com/angular/angular.js/commit/656c8fa8f23b1277cc5c214c4d0237f3393afa1e)**,
|
||||
`input[radio]` and `input[checkbox]` now listen to the "change" event instead of the "click" event.
|
||||
Most apps should not be affected, as "change" is automatically fired by browsers after "click"
|
||||
@@ -84,10 +85,10 @@ Two scenarios might need migration:
|
||||
Before this change, custom click event listeners on radio / checkbox would be called after the
|
||||
input element and `ngModel` had been updated, unless they were specifically registered before
|
||||
the built-in click handlers.
|
||||
After this change, they are called before the input is updated, and can call event.preventDefault()
|
||||
to prevent the input from updating.
|
||||
After this change, they are called before the input is updated, and can call
|
||||
`event.preventDefault()` to prevent the input from updating.
|
||||
|
||||
If an app uses a click event listener that expects ngModel to be updated when it is called, it now
|
||||
If an app uses a click event listener that expects `ngModel` to be updated when it is called, it now
|
||||
needs to register a change event listener instead.
|
||||
|
||||
- Triggering click events:
|
||||
@@ -95,57 +96,59 @@ needs to register a change event listener instead.
|
||||
Conventional trigger functions:
|
||||
|
||||
The change event might not be fired when the input element is not attached to the document. This
|
||||
can happen in **tests** that compile input elements and
|
||||
trigger click events on them. Depending on the browser (Chrome and Safari) and the trigger method,
|
||||
the change event will not be fired when the input isn't attached to the document.
|
||||
can happen in **tests** that compile input elements and trigger click events on them. Depending on
|
||||
the browser (Chrome and Safari) and the trigger method, the change event will not be fired when the
|
||||
input isn't attached to the document.
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
it('should update the model', inject(function($compile, $rootScope) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
it('should update the model', inject(function($compile, $rootScope) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
With this patch, `$rootScope.checkbox` might not be true, because the click event
|
||||
hasn't triggered the change event. To make the test, work append the inputElm to the app's
|
||||
`$rootElement`, and the `$rootElement` to the `$document`.
|
||||
With this patch, `$rootScope.checkbox` might not be true, because the click event hasn't triggered
|
||||
the change event. To make the test, work append `inputElm` to the app's `$rootElement`, and the
|
||||
`$rootElement` to the `$document`.
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
it('should update the model', inject(function($compile, $rootScope, $rootElement, $document) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
it('should update the model', inject(function($compile, $rootScope, $rootElement, $document) {
|
||||
var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope);
|
||||
|
||||
$rootElement.append(inputElm);
|
||||
$document.append($rootElement);
|
||||
$rootElement.append(inputElm);
|
||||
$document.append($rootElement);
|
||||
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger()
|
||||
expect($rootScope.checkbox).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
#### **input\[number\]**
|
||||
|
||||
**Due to [aa3f95](https://github.com/angular/angular.js/commit/aa3f951330ec7b10b43ea884d9b5754e296770ec)**,
|
||||
`input[type=number]` with `ngModel` now validates the input for the `max`/`min` restriction against
|
||||
the `ngModelController.$viewValue` instead of against the `ngModelController.$modelValue`.
|
||||
|
||||
This affects apps that use `$parsers` or `$formatters` to transform the input / model value.
|
||||
|
||||
If you rely on the $modelValue validation, you can overwrite the `min`/`max` validator from a custom directive, as seen in the following example directive definition object:
|
||||
If you rely on the `$modelValue` validation, you can overwrite the `min`/`max` validator from a
|
||||
custom directive, as seen in the following example directive definition object:
|
||||
|
||||
```
|
||||
```js
|
||||
{
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
var maxValidator = ctrl.$validators.max;
|
||||
|
||||
ctrk.$validators.max = function(modelValue, viewValue) {
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return maxValidator(modelValue, modelValue);
|
||||
};
|
||||
}
|
||||
@@ -154,11 +157,11 @@ If you rely on the $modelValue validation, you can overwrite the `min`/`max` val
|
||||
|
||||
|
||||
#### **ngModel, input**
|
||||
|
||||
**Due to [74b04c](https://github.com/angular/angular.js/commit/74b04c9403af4fc7df5b6420f22c9f45a3e84140)**,
|
||||
*Custom* parsers that fail to parse on input types "email", "url", "number", "date", "month",
|
||||
"time", "datetime-local", "week", no longer set `ngModelController.$error[inputType]`, and
|
||||
the `ng-invalid-[inputType]` class. Also, custom parsers on input type "range" do no
|
||||
longer set `ngModelController.$error.number` and the `ng-invalid-number` class.
|
||||
the `ng-invalid-[inputType]` class. Also, custom parsers on input type "range" no longer set `ngModelController.$error.number` and the `ng-invalid-number` class.
|
||||
|
||||
Instead, any custom parsers on these inputs set `ngModelController.$error.parse` and
|
||||
`ng-invalid-parse`. This change was made to make distinguishing errors from built-in parsers
|
||||
@@ -166,6 +169,7 @@ and custom parsers easier.
|
||||
|
||||
|
||||
#### **ngModelOptions**
|
||||
|
||||
**Due to [55ba44](https://github.com/angular/angular.js/commit/55ba44913e02650b56410aa9ab5eeea5d3492b68)**,
|
||||
the 'default' key in 'debounce' now only debounces the default event, i.e. the event that is added
|
||||
as an update trigger by the different input directives automatically.
|
||||
@@ -179,24 +183,24 @@ See the following example:
|
||||
|
||||
Pre-1.7:
|
||||
'mouseup' is also debounced by 500 milliseconds because 'default' is applied:
|
||||
```
|
||||
```html
|
||||
ng-model-options="{
|
||||
updateOn: 'default blur mouseup',
|
||||
debounce: { 'default': 500, 'blur': 0 }
|
||||
}
|
||||
}"
|
||||
```
|
||||
|
||||
1.7:
|
||||
The pre-1.7 behavior can be re-created by setting '*' as a catch-all debounce value:
|
||||
```
|
||||
```html
|
||||
ng-model-options="{
|
||||
updateOn: 'default blur mouseup',
|
||||
debounce: { '*': 500, 'blur': 0 }
|
||||
}
|
||||
}"
|
||||
```
|
||||
|
||||
In contrast, when only 'default' is used, 'blur' and 'mouseup' are not debounced:
|
||||
```
|
||||
```html
|
||||
ng-model-options="{
|
||||
updateOn: 'default blur mouseup',
|
||||
debounce: { 'default': 500 }
|
||||
@@ -207,14 +211,15 @@ ng-model-options="{
|
||||
#### **ngStyle**
|
||||
|
||||
**Due to [15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0)**,
|
||||
previously the use of deep watch by ng-style would trigger styles to be
|
||||
re-applied when nested state changed. Now only changes to direct
|
||||
properties of the watched object will trigger changes.
|
||||
the use of deep-watching in `ngStyle` has changed. Previously, `ngStyle` would trigger styles to be
|
||||
re-applied whenever nested state changed. Now, only changes to direct properties of the watched
|
||||
object will trigger changes.
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ng-services"></a>
|
||||
### Core: _Services_
|
||||
|
||||
|
||||
#### **$compile**
|
||||
|
||||
**Due to [38f8c9](https://github.com/angular/angular.js/commit/38f8c97af74649ce224b6dd45f433cc665acfbfb)**,
|
||||
@@ -238,16 +243,16 @@ migrating to AngularJS 1.7.0 shouldn't require any further action.
|
||||
3. If you specified `$compileProvider.preAssignBindingsEnabled(true)` you need
|
||||
to first migrate your code so that the flag can be flipped to `false`. The
|
||||
instructions on how to do that are available in the "Migrating from 1.5 to 1.6"
|
||||
guide:
|
||||
https://docs.angularjs.org/guide/migration#migrating-from-1-5-to-1-6
|
||||
guide: https://docs.angularjs.org/guide/migration#migrating-from-1-5-to-1-6
|
||||
Afterwards, remove the `$compileProvider.preAssignBindingsEnabled(true)`
|
||||
statement.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [6ccbfa](https://github.com/angular/angular.js/commit/6ccbfa65d60a3dc396d0cf6da21b993ad74653fd)**,
|
||||
the `xlink:href` security context for SVG's `a` and `image` elements has been lowered.
|
||||
|
||||
In the unlikely case that an app relied on RESOURCE_URL whitelisting for the
|
||||
In the unlikely case that an app relied on `RESOURCE_URL` whitelisting for the
|
||||
purpose of binding to the `xlink:href` property of SVG's `<a>` or `<image>`
|
||||
elements and if the values do not pass the regular URL sanitization, they will
|
||||
break.
|
||||
@@ -258,37 +263,39 @@ To fix this you need to ensure that the values used for binding to the affected
|
||||
`imgSrcSanitizationWhitelist` (for `<image>` elements).
|
||||
|
||||
<hr />
|
||||
**Due to [fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727)**,
|
||||
deepWatch is no longer used in in literal one-way bindings.
|
||||
|
||||
Previously when a literal value was passed into a directive/component via
|
||||
**Due to [fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727)**,
|
||||
deep-watching is no longer used in literal one-way bindings.
|
||||
|
||||
Previously, when a literal value was passed into a directive/component via
|
||||
one-way binding it would be watched with a deep watcher.
|
||||
|
||||
For example, for `<my-component input="[a]">`, a new instance of the array
|
||||
would be passed into the directive/component (and trigger $onChanges) not
|
||||
would be passed into the directive/component (and trigger `$onChanges`) not
|
||||
only if `a` changed but also if any sub property of `a` changed such as
|
||||
`a.b` or `a.b.c.d.e` etc.
|
||||
|
||||
This also means a new but equal value for `a` would NOT trigger such a
|
||||
change.
|
||||
|
||||
Now literal values use an input-based watch similar to other directive/component
|
||||
Now, literal values use an input-based watch similar to other directive/component
|
||||
one-way bindings. In this context inputs are the non-constant parts of the
|
||||
literal. In the example above the input would be `a`. Changes are only
|
||||
triggered when the inputs to the literal change.
|
||||
literal. In the example above, the input would be `a`. Changes are only
|
||||
triggered, when the inputs to the literal change.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [1cf728](https://github.com/angular/angular.js/commit/1cf728e209a9e0016068fac2769827e8f747760e)**,
|
||||
`base[href]` was added to the list of RESOURCE_URL context attributes.
|
||||
`base[href]` was added to the list of `RESOURCE_URL` context attributes.
|
||||
|
||||
Previously, `<base href="{{ $ctrl.baseUrl }}" />` would not require `baseUrl` to
|
||||
be trusted as a RESOURCE_URL. Now, `baseUrl` will be sent to `$sce`'s
|
||||
RESOURCE_URL checks. By default, it will break unless `baseUrl` is of the same
|
||||
be trusted as a `RESOURCE_URL`. Now, `baseUrl` will be sent to `$sce`'s
|
||||
`RESOURCE_URL` checks. By default, it will break unless `baseUrl` is of the same
|
||||
origin as the application document.
|
||||
|
||||
Refer to the
|
||||
[`$sce` API docs](https://code.angularjs.org/snapshot/docs/api/ng/service/$sce)
|
||||
for more info on how to trust a value in a RESOURCE_URL context.
|
||||
for more info on how to trust a value in a `RESOURCE_URL` context.
|
||||
|
||||
Also, concatenation in trusted contexts is not allowed, which means that the
|
||||
following won't work: `<base href="/something/{{ $ctrl.partialPath }}" />`.
|
||||
@@ -315,10 +322,10 @@ except for the simplest of cases):
|
||||
**Due to ([c2b8fa](https://github.com/angular/angular.js/commit/c2b8fab0a480204374d561d6b9b3d47347ac5570))**,
|
||||
the arguments of `$watchGroup` callbacks have changed.
|
||||
|
||||
Previously when using `$watchGroup` the entries in `newValues` and
|
||||
Previously, when using `$watchGroup`, the entries in `newValues` and
|
||||
`oldValues` represented the *most recent change of each entry*.
|
||||
|
||||
Now the entries in `oldValues` will always equal the `newValues` of the previous
|
||||
Now, the entries in `oldValues` will always equal the `newValues` of the previous
|
||||
call of the listener. This means comparing the entries in `newValues` and
|
||||
`oldValues` can be used to determine which individual expressions changed.
|
||||
|
||||
@@ -343,7 +350,7 @@ Now the `oldValue` will always equal the previous `newValue`:
|
||||
|
||||
Note the last call now shows `a === 2` in the `oldValues` array.
|
||||
|
||||
This also makes the `oldValue` of one-time watchers more clear. Previously
|
||||
This also makes the `oldValue` of one-time watchers more clear. Previously,
|
||||
the `oldValue` of a one-time watcher would remain `undefined` forever. For
|
||||
example `$scope.$watchGroup(['a', '::b'], fn)` would previously:
|
||||
|
||||
@@ -367,7 +374,7 @@ Where now the `oldValue` will always equal the previous `newValue`:
|
||||
#### **$interval**
|
||||
|
||||
**Due to [a8bef9](https://github.com/angular/angular.js/commit/a8bef95127775d83d80daa4617c33227c4b443d4)**,
|
||||
`$interval.cancel() will throw an error if called with a promise that was not generated by
|
||||
`$interval.cancel()` will throw an error if called with a promise that was not generated by
|
||||
`$interval()`. Previously, it would silently do nothing.
|
||||
|
||||
Before:
|
||||
@@ -393,7 +400,7 @@ $interval.cancel(promise); // Interval canceled.
|
||||
#### **$timeout**
|
||||
|
||||
**Due to [336525](https://github.com/angular/angular.js/commit/3365256502344970f86355d3ace1cb4251ae9828)**,
|
||||
`$timeout.cancel() will throw an error if called with a promise that was not generated by
|
||||
`$timeout.cancel()` will throw an error if called with a promise that was not generated by
|
||||
`$timeout()`. Previously, it would silently do nothing.
|
||||
|
||||
Before:
|
||||
@@ -417,10 +424,11 @@ $timeout.cancel(promise); // Timeout canceled.
|
||||
|
||||
|
||||
#### **$cookies**
|
||||
|
||||
**Due to [73c646](https://github.com/angular/angular.js/commit/73c6467f1468353215dc689c019ed83aa4993c77)**,
|
||||
the `$cookieStore`service has been removed. Migrate to the $cookies service. Note that
|
||||
for object values you need to use the `putObject` & `getObject` methods as
|
||||
`get`/`put` will not correctly save/retrieve them.
|
||||
the `$cookieStore`service has been removed. Migrate to the `$cookies` service. Note that
|
||||
for object values you need to use the `putObject` & `getObject` methods, as
|
||||
`get`/`put` will not correctly save/retrieve the object values.
|
||||
|
||||
Before:
|
||||
```js
|
||||
@@ -433,29 +441,31 @@ $cookieStore.remove('name');
|
||||
#### **$templateRequest**
|
||||
|
||||
**Due to [c617d6](https://github.com/angular/angular.js/commit/c617d6dceee5b000bfceda44ced22fc16b48b18b)**,
|
||||
give tpload error namespace has changed. Previously the `tpload` error was namespaced to `$compile`.
|
||||
If you have code that matches errors of the form `[$compile:tpload]` it will no
|
||||
longer run. You should change the code to match
|
||||
`[$templateRequest:tpload]`.
|
||||
the `tpload` error namespace has changed. Previously, the `tpload` error was namespaced to
|
||||
`$compile`. If you have code that matches errors of the form `[$compile:tpload]` it will no longer
|
||||
run. You should change the code to match `[$templateRequest:tpload]`.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to ([fb0099](https://github.com/angular/angular.js/commit/fb00991460cf69ae8bc7f1f826363d09c73c0d5e)**,
|
||||
the service now returns the result of `$templateCache.put()` when making a server request to the
|
||||
template. Previously it would return the content of the response directly.
|
||||
This now means if you are decorating `$templateCache.put()` to manipulate the template, you will
|
||||
now get this manipulated result also on the first `$templateRequest` rather than only on subsequent
|
||||
calls (when the template is retrived from the cache).
|
||||
In practice this should not affect any apps, as it is unlikely that they rely on the template being
|
||||
`$templateRequest()` now returns the result of `$templateCache.put()` when making a server request
|
||||
for a template. Previously, it would return the content of the response directly.
|
||||
|
||||
This means that if you are decorating `$templateCache.put()` to manipulate the template, you will
|
||||
now get this manipulated result also on the first `$templateRequest()` call rather than only on
|
||||
subsequent calls (when the template is retrieved from the cache).
|
||||
|
||||
In practice, this should not affect any apps, as it is unlikely that they rely on the template being
|
||||
different in the first and subsequent calls.
|
||||
|
||||
|
||||
#### **$animate**
|
||||
|
||||
**Due to [16b82c](https://github.com/angular/angular.js/commit/16b82c6afe0ab916fef1d6ca78053b00bf5ada83)**,
|
||||
$animate.cancel(runner) now rejects the underlying
|
||||
promise and calls the catch() handler on the runner
|
||||
returned by $animate functions (enter, leave, move,
|
||||
addClass, removeClass, setClass, animate).
|
||||
Previously it would resolve the promise as if the animation
|
||||
had ended successfully.
|
||||
`$animate.cancel(runner)` now rejects the underlying promise and calls the `catch()` handler on the
|
||||
runner returned by `$animate` functions (`enter`, `leave`, `move`, `addClass`, `removeClass`,
|
||||
`setClass`, `animate`).
|
||||
Previously, it would resolve the promise as if the animation had ended successfully.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -468,7 +478,7 @@ runner.cancel();
|
||||
```
|
||||
|
||||
Pre-1.7.0, this logs 'success', 1.7.0 and later it logs 'cancelled'.
|
||||
To migrate, add a catch() handler to your animation runners.
|
||||
To migrate, add a `catch()` handler to your animation runners.
|
||||
|
||||
|
||||
#### **$controller**
|
||||
@@ -479,38 +489,74 @@ has been removed. Likewise, the deprecated `$controllerProvider.allowGlobals()`
|
||||
method that could enable this behavior, has been removed.
|
||||
|
||||
This behavior had been deprecated since AngularJS v1.3.0, because polluting the global scope
|
||||
is bad. To migrate, remove the call to $controllerProvider.allowGlobals() in the config, and
|
||||
register your controller via the Module API or the $controllerProvider, e.g.
|
||||
is considered bad practice. To migrate, remove the call to `$controllerProvider.allowGlobals()` in
|
||||
the config, and register your controller via the Module API or the `$controllerProvider`, e.g.:
|
||||
|
||||
```
|
||||
```js
|
||||
angular.module('myModule', []).controller('myController', function() {...});
|
||||
|
||||
// or
|
||||
|
||||
angular.module('myModule', []).config(function($controllerProvider) {
|
||||
$controllerProvider.register('myController', function() {...});
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### **$sce**
|
||||
|
||||
**Due to [1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**,
|
||||
if you use `attrs.$set` for URL attributes (a[href] and img[src]) there will no
|
||||
if you use `attrs.$set` for URL attributes (`a[href]` and `img[src]`) there will no
|
||||
longer be any automated sanitization of the value. This is in line with other
|
||||
programmatic operations, such as writing to the innerHTML of an element.
|
||||
programmatic operations, such as writing to the `innerHTML` of an element.
|
||||
|
||||
If you are programmatically writing URL values to attributes from untrusted
|
||||
input then you must sanitize it yourself. You could write your own sanitizer or copy
|
||||
input, then you must sanitize it yourself. You could write your own sanitizer or copy
|
||||
the private `$$sanitizeUri` service.
|
||||
|
||||
Note that values that have been passed through the `$interpolate` service within the
|
||||
`URL` or `MEDIA_URL` will have already been sanitized, so you would not need to sanitize
|
||||
these values again.
|
||||
|
||||
<hr/>
|
||||
|
||||
**Due to [1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**,
|
||||
binding {@link ng.$sce#trustAs trustAs()} and the short versions
|
||||
({@link ng.$sce#trustAsResourceUrl trustAsResourceUrl()} et al.) to {@link ng.ngSrc},
|
||||
{@link ng.ngSrcset}, and {@link ng.ngHref} will now raise an infinite digest error:
|
||||
|
||||
```js
|
||||
$scope.imgThumbFn = function(id) {
|
||||
return $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
};
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{ imgThumbFn(imgId) }}" />
|
||||
```
|
||||
|
||||
This is because {@link ng.$interpolate} is now responsible for sanitizing
|
||||
the attribute value, and its watcher receives a new object from `trustAs()`
|
||||
on every digest.
|
||||
To migrate, compute the trusted value only when the input value changes:
|
||||
|
||||
```js
|
||||
$scope.$watch('imgId', function(id) {
|
||||
$scope.imgThumb = $sce.trustAsResourceUrl(someService.someUrl(id));
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<img ng-src="{{ imgThumb }}" />
|
||||
```
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ng-filters"></a>
|
||||
### Core: _Filters_
|
||||
|
||||
|
||||
#### **orderBy**
|
||||
|
||||
**Due to [1d8046](https://github.com/angular/angular.js/commit/1d804645f7656d592c90216a0355b4948807f6b8)**,
|
||||
when using `orderBy` to sort arrays containing `null` values, the `null` values
|
||||
will be considered "greater than" all other values, except for `undefined`.
|
||||
@@ -535,8 +581,9 @@ orderByFilter(['a', undefined, 'o', null, 'z']);
|
||||
|
||||
|
||||
#### **jqLite**
|
||||
|
||||
**Due to [b7d396](https://github.com/angular/angular.js/commit/b7d396b8b6e8f27a1f4556d58fc903321e8d532a)**,
|
||||
removeData() no longer removes event handlers.
|
||||
`removeData()` no longer removes event handlers.
|
||||
|
||||
Before this commit `removeData()` invoked on an element removed its event
|
||||
handlers as well. If you want to trigger a full cleanup of an element, change:
|
||||
@@ -561,22 +608,20 @@ elem.remove();
|
||||
will remove event handlers as well.
|
||||
|
||||
|
||||
|
||||
#### **Angular**
|
||||
#### **Helpers**
|
||||
|
||||
**Due to [1daa4f](https://github.com/angular/angular.js/commit/1daa4f2231a89ee88345689f001805ffffa9e7de)**,
|
||||
the helper functions `angular.lowercase` `and angular.uppercase` have been removed.
|
||||
the helper functions `angular.lowercase` and `angular.uppercase` have been removed.
|
||||
|
||||
These functions have been deprecated since 1.5.0. They are internally
|
||||
used, but should not be exposed as they contain special locale handling
|
||||
(for Turkish) to maintain internal consistency regardless of user-set locale.
|
||||
|
||||
Developers should generally use the built-ins `toLowerCase` and `toUpperCase`
|
||||
Developers should generally use the built-in methods `toLowerCase` and `toUpperCase`
|
||||
or `toLocaleLowerCase` and `toLocaleUpperCase` for special cases.
|
||||
|
||||
Further, we generally discourage using the angular.x helpers in application code.
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948)**,
|
||||
`angular.isArray()` now supports Array subclasses.
|
||||
|
||||
@@ -597,18 +642,19 @@ be able to handle these objects better when copying or watching.
|
||||
### ngAria
|
||||
|
||||
**Due to [6d5ef3](https://github.com/angular/angular.js/commit/6d5ef34fc6a974cde73157ba94f9706723dd8f5b)**,
|
||||
the ngAria directive no longer sets aria-* attributes on input[type="hidden"] with ngModel.
|
||||
This can affect apps that test for the presence of aria attributes on hidden inputs.
|
||||
`ngAria` no longer sets `aria-*` attributes on `input[type="hidden"]` with `ngModel`.
|
||||
This can affect apps that test for the presence of ARIA attributes on hidden inputs.
|
||||
To migrate, remove these assertions.
|
||||
In actual apps, this should not have a user-facing effect, as the previous behavior
|
||||
was incorrect, and the new behavior is correct for accessibility.
|
||||
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ngResource"></a>
|
||||
### ngResource
|
||||
|
||||
|
||||
#### **$resource**
|
||||
|
||||
**Due to [ea0585](https://github.com/angular/angular.js/commit/ea0585773bb93fd891576e2271254a17e15f1ddd)**,
|
||||
the behavior of interceptors and success/error callbacks has changed.
|
||||
|
||||
@@ -690,6 +736,7 @@ User.get({id: 2}, onSuccess, onError);
|
||||
```
|
||||
|
||||
<hr />
|
||||
|
||||
**Due to [240a3d](https://github.com/angular/angular.js/commit/240a3ddbf12a9bb79754031be95dae4b6bd2dded)**,
|
||||
`$http` will be called asynchronously from `$resource` methods
|
||||
(regardless if a `request`/`requestError` interceptor has been defined).
|
||||
@@ -728,7 +775,6 @@ it('...', function() {
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="migrate1.6to1.7-ngScenario"></a>
|
||||
### ngScenario
|
||||
|
||||
@@ -736,7 +782,7 @@ it('...', function() {
|
||||
the angular scenario runner end-to-end test framework has been
|
||||
removed from the project and will no longer be available on npm
|
||||
or bower starting with 1.7.0.
|
||||
It was deprecated and removed from the documentation in 2014.
|
||||
It has been deprecated and removed from the documentation since 2014.
|
||||
Applications that still use it should migrate to
|
||||
[Protractor](http://www.protractortest.org).
|
||||
Technically, it should also be possible to continue using an
|
||||
@@ -748,10 +794,10 @@ not changed. However, we do not guarantee future compatibility.
|
||||
### ngTouch
|
||||
|
||||
**Due to [11d9ad](https://github.com/angular/angular.js/commit/11d9ad1eb25eaf5967195e424108207427835d50)**,
|
||||
the `ngClick` directive from the ngTouch module has been removed, and with it the
|
||||
the `ngClick` directive of the `ngTouch` module has been removed, and with it the
|
||||
corresponding `$touchProvider` and `$touch` service.
|
||||
|
||||
If you have included ngTouch v1.5.0 or higher in your application, and have not
|
||||
If you have included `ngTouch` v1.5.0 or higher in your application, and have not
|
||||
changed the value of `$touchProvider.ngClickOverrideEnabled()`, or injected and used the `$touch`
|
||||
service, then there are no migration steps for your code. Otherwise you must remove references to
|
||||
the provider and service.
|
||||
|
||||
@@ -102,7 +102,7 @@ For more information please visit {@link $http#json-vulnerability-protection JSO
|
||||
|
||||
Bear in mind that calling `$http.jsonp` gives the remote server (and, if the request is not secured, any Man-in-the-Middle attackers)
|
||||
instant remote code execution in your application: the result of these requests is handed off
|
||||
to the browser as regular `<script>` tag.
|
||||
to the browser as a regular `<script>` tag.
|
||||
|
||||
## Strict Contextual Escaping
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
@description
|
||||
|
||||
# Including AngularJS scripts from the Google CDN
|
||||
The quickest way to get started is to point your html `<script>` tag to a
|
||||
[Google CDN](https://developers.google.com/speed/libraries/#angularjs) URL.
|
||||
The quickest way to get started is to point your html `<script>` tag to a Google CDN URL.
|
||||
This way, you don't have to download anything or maintain a local copy.
|
||||
|
||||
There are two types of AngularJS script URLs you can point to, one for development and one for
|
||||
|
||||
@@ -7,48 +7,41 @@
|
||||
This page describes the support status of the significant versions of AngularJS.
|
||||
|
||||
<div class="alert alert-info">
|
||||
AngularJS is planning one more significant release, version 1.7, and on July 1, 2018 it will enter a 3 year Long Term Support period.
|
||||
On July 1, 2018 AngularJS entered a 3 year Long Term Support period.
|
||||
</div>
|
||||
|
||||
### Until July 1st 2018
|
||||
|
||||
Any version branch not shown in the following table (e.g. 1.5.x) is no longer being developed.
|
||||
|
||||
<table class="dev-status table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="security"><td><span>1.2.x</span></td><td>Security patches only</td><td>Last version to provide IE 8 support</td></tr>
|
||||
<tr class="stable"><td><span>1.6.x</span></td><td>Patch Releases</td><td>Minor features, bug fixes, security patches - no breaking changes</td></tr>
|
||||
<tr class="current"><td><span>1.7.x</span></td><td>Active Development</td><td>1.7.0 (not yet released) will be the last release of AngularJS to contain breaking changes</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### After July 1st 2018
|
||||
|
||||
Any version branch not shown in the following table (e.g. 1.6.x) is no longer being developed.
|
||||
|
||||
<table class="dev-status table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="security"><td><span>1.2.x</span></td><td>Long Term Support</td><td>Last version to provide IE 8 support</td></tr>
|
||||
<tr class="stable"><td><span>1.7.x</span></td><td>Long Term Support</td><td>See [Long Term Support](#long-term-support) section below.</td></tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="security">
|
||||
<td><span>1.2.x</span></td>
|
||||
<td>Security patches only</td>
|
||||
<td>Last version to provide IE 8 support</td>
|
||||
</tr>
|
||||
<tr class="stable">
|
||||
<td><span>1.7.x</span></td>
|
||||
<td>Long Term Support</td>
|
||||
<td>See {@link version-support-status#long-term-support Long Term Support} section below.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Long Term Support
|
||||
|
||||
On July 1st 2018, we will enter a Long Term Support period for AngularJS.
|
||||
On July 1st 2018, AngularJS entered a Long Term Support period for AngularJS.
|
||||
|
||||
At this time we will focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
|
||||
We now focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
|
||||
|
||||
* A security flaw is detected in the 1.7.x branch of the framework
|
||||
* One of the major browsers releases a version that will cause current production applications using AngularJS 1.7.x to stop working
|
||||
* The jQuery library releases a version that will cause current production applications using AngularJS 1.7.x to stop working.
|
||||
|
||||
AngularJS 1.2.x will get a new version if and only if a new severe security issue is discovered.
|
||||
|
||||
### Blog Post
|
||||
|
||||
You can read more about these plans in our [blog post announcement](https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c).
|
||||
You can read more about these plans in our [blog post announcement](https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c).
|
||||
|
||||
@@ -64,7 +64,7 @@ a few git commands.
|
||||
|
||||
### Install Git
|
||||
|
||||
You can download and install Git from http://git-scm.com/download. Once installed, you should have
|
||||
You can download and install Git from https://git-scm.com/download. Once installed, you should have
|
||||
access to the `git` command line tool. The main commands that you will need to use are:
|
||||
|
||||
* `git clone ...`: Clone a remote repository onto your local machine.
|
||||
@@ -99,8 +99,8 @@ The tutorial instructions, from now on, assume you are running all commands from
|
||||
|
||||
### Install Node.js
|
||||
|
||||
If you want to run the preconfigured local web server and the test tools then you will also need
|
||||
[Node.js v4+][node].
|
||||
In order to install dependencies (such as the test tools and AngularJS itself) and run the
|
||||
preconfigured local web server, you will also need [Node.js v6+][node].
|
||||
|
||||
You can download a Node.js installer for your operating system from https://nodejs.org/en/download/.
|
||||
|
||||
@@ -125,22 +125,25 @@ npm --version
|
||||
[Node Version Manager (nvm)][nvm] or [Node Version Manager (nvm) for Windows][nvm-windows].
|
||||
</div>
|
||||
|
||||
Once you have Node.js installed on your machine, you can download the tool dependencies by running:
|
||||
By installing Node.js, you also get [npm][npm], which is a command line executable for downloading
|
||||
and managing Node.js packages. We use it to download the AngularJS framework as well as development
|
||||
and testing tools.
|
||||
|
||||
Once you have Node.js installed on your machine, you can download these dependencies by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
This command reads angular-phonecat's `package.json` file and downloads the following tools into the
|
||||
`node_modules` directory:
|
||||
This command reads angular-phonecat's `package.json` file and downloads the following dependencies
|
||||
into the `node_modules` directory:
|
||||
|
||||
* [Bower][bower] - client-side code package manager
|
||||
* [Http-Server][http-server] - simple local static web server
|
||||
* [Karma][karma] - unit test runner
|
||||
* [Protractor][protractor] - end-to-end (E2E) test runner
|
||||
|
||||
Running `npm install` will also automatically use bower to download the AngularJS framework into the
|
||||
`app/bower_components` directory.
|
||||
Running `npm install` will also automatically copy the AngularJS framework and other dependencies
|
||||
necessary for our app to work into the `app/lib/` directory.
|
||||
|
||||
<div class="alert alert-info">
|
||||
Note the angular-phonecat project is setup to install and run these utilities via npm scripts.
|
||||
@@ -160,23 +163,23 @@ tasks that you will need while developing:
|
||||
|
||||
### Install Helper Tools (optional)
|
||||
|
||||
The Bower, Http-Server, Karma and Protractor modules are also executables, which can be installed
|
||||
globally and run directly from a terminal/command prompt. You don't need to do this to follow the
|
||||
tutorial, but if you decide you do want to run them directly, you can install these modules globally
|
||||
using, `sudo npm install -g ...`.
|
||||
The Http-Server, Karma and Protractor modules are also executables, which can be installed globally
|
||||
and run directly from a terminal/command prompt. You don't need to do this to follow the tutorial,
|
||||
but if you decide you do want to run them directly, you can install these modules globally using,
|
||||
`sudo npm install --global ...`.
|
||||
|
||||
For instance, to install the Bower command line executable you would do:
|
||||
For instance, to install the `http-server` command line executable you would do:
|
||||
|
||||
```
|
||||
sudo npm install -g bower
|
||||
sudo npm install --global http-server
|
||||
```
|
||||
|
||||
_(Omit the sudo if running on Windows)_
|
||||
_(Omit the sudo if running on Windows.)_
|
||||
|
||||
Then you can run the bower tool directly, such as:
|
||||
Then you can run the `http-server` tool directly, such as:
|
||||
|
||||
```
|
||||
bower install
|
||||
http-server ./app
|
||||
```
|
||||
|
||||
|
||||
@@ -278,6 +281,45 @@ It is good to run the E2E tests whenever you make changes to the HTML views or w
|
||||
the application as a whole is executing correctly. It is very common to run E2E tests before pushing
|
||||
a new commit of changes to a remote repository.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<p>
|
||||
Each version of Protractor is compatible with specific browser versions. If you are reading this
|
||||
some time in the future, it is possible that the specified Protractor version is no longer
|
||||
compatible with the latest version of Chrome that you are using.
|
||||
</p>
|
||||
<p>
|
||||
If that is the case, you can try upgrading Protractor to newer version. For instructions on how
|
||||
to upgrade dependencies see [Updating dependencies](tutorial/#updating-dependencies).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
### Updating dependencies
|
||||
|
||||
In order to avoid surprises, all dependencies listed in `package.json` are pinned to specific
|
||||
versions (this is what the [package-lock.json][package-lock] file is about). This ensures that the
|
||||
same version of a dependency is installed every time.
|
||||
|
||||
Since all dependencies are acquired via npm, you can use the same tool to easily update them as
|
||||
well (although you probably don't need to for the purpose of this tutorial). Simply run the
|
||||
preconfigured script:
|
||||
|
||||
```
|
||||
npm run update-deps
|
||||
```
|
||||
|
||||
This will update all packages to the latest version that satisfy their version ranges (as specified
|
||||
in `package.json`) and also copy the necessary files into `app/lib/`. For example, if `package.json`
|
||||
contains `"some-package": "1.2.x"`, it will be updated to the latest 1.2.x version (e.g. 1.2.99),
|
||||
but not to 1.3.x (e.g. 1.3.0).
|
||||
|
||||
If you want to update a dependency to a version newer than what the specificed range would permit,
|
||||
you can change the version range in `package.json` and then run `npm run update-deps` as usual.
|
||||
|
||||
<div class="alert alert-info">
|
||||
See [here][semver-ranges] for more info on the various version range formats.
|
||||
</div>
|
||||
|
||||
|
||||
### Common Issues
|
||||
|
||||
@@ -324,14 +366,16 @@ Now that you have set up your local machine, let's get started with the tutorial
|
||||
|
||||
|
||||
[angular-phonecat]: https://github.com/angular/angular-phonecat
|
||||
[bower]: http://bower.io/
|
||||
[git]: http://git-scm.com/
|
||||
[git]: https://git-scm.com/
|
||||
[http-server]: https://github.com/nodeapps/http-server
|
||||
[jdk]: https://en.wikipedia.org/wiki/Java_Development_Kit
|
||||
[jdk-download]: http://www.oracle.com/technetwork/java/javase/downloads/index.html
|
||||
[jdk-download]: https://www.oracle.com/technetwork/java/javase/downloads/index.html
|
||||
[karma]: https://karma-runner.github.io/
|
||||
[node]: http://nodejs.org/
|
||||
[node]: https://nodejs.org/
|
||||
[npm]: https://www.npmjs.com/
|
||||
[nvm]: https://github.com/creationix/nvm
|
||||
[nvm-windows]: https://github.com/coreybutler/nvm-windows
|
||||
[package-lock]: https://docs.npmjs.com/files/package-lock.json
|
||||
[protractor]: https://github.com/angular/protractor
|
||||
[selenium]: http://docs.seleniumhq.org/
|
||||
[selenium]: https://docs.seleniumhq.org/
|
||||
[semver-ranges]: https://docs.npmjs.com/misc/semver#ranges
|
||||
|
||||
@@ -51,8 +51,8 @@ The code contains some key AngularJS elements that we will need as we progress.
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>My HTML File</title>
|
||||
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css" />
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -84,7 +84,7 @@ For more info on `ngApp`, check out the {@link ngApp API Reference}.
|
||||
**`angular.js` script tag:**
|
||||
|
||||
```html
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
```
|
||||
|
||||
This code downloads the `angular.js` script which registers a callback that will be executed by the
|
||||
@@ -154,8 +154,8 @@ and one static binding, and our model is empty. That will soon change!
|
||||
|
||||
Most of the files in your working directory come from the [angular-seed project][angular-seed],
|
||||
which is typically used to bootstrap new AngularJS projects. The seed project is pre-configured to
|
||||
install the AngularJS framework (via `bower` into the `app/bower_components/` directory) and tools
|
||||
for developing and testing a typical web application (via `npm`).
|
||||
install the AngularJS framework (via `npm` into the `app/lib/` directory) and tools for developing
|
||||
and testing a typical web application (via `npm`).
|
||||
|
||||
For the purposes of this tutorial, we modified the angular-seed with the following changes:
|
||||
|
||||
@@ -163,7 +163,7 @@ For the purposes of this tutorial, we modified the angular-seed with the followi
|
||||
* Removed unused dependencies.
|
||||
* Added phone images to `app/img/phones/`.
|
||||
* Added phone data files (JSON) to `app/phones/`.
|
||||
* Added a dependency on [Bootstrap](http://getbootstrap.com) in the `bower.json` file.
|
||||
* Added a dependency on [Bootstrap][bootstrap-3.3] in the `package.json` file.
|
||||
|
||||
|
||||
## Experiments
|
||||
@@ -186,3 +186,4 @@ Now let's go to {@link step_01 step 1} and add some content to the web app.
|
||||
|
||||
|
||||
[angular-seed]: https://github.com/angular/angular-seed
|
||||
[bootstrap-3.3]: https://getbootstrap.com/docs/3.3
|
||||
|
||||
@@ -33,7 +33,7 @@ The view is constructed by AngularJS from this template.
|
||||
<html ng-app="phonecatApp">
|
||||
<head>
|
||||
...
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
<body ng-controller="PhoneListController">
|
||||
@@ -317,7 +317,7 @@ by utilizing components.
|
||||
<ul doc-tutorial-nav="2"></ul>
|
||||
|
||||
|
||||
[jasmine-docs]: http://jasmine.github.io/2.4/introduction.html
|
||||
[jasmine-home]: http://jasmine.github.io/
|
||||
[jasmine-docs]: https://jasmine.github.io/api/3.3/global
|
||||
[jasmine-home]: https://jasmine.github.io/
|
||||
[karma]: https://karma-runner.github.io/
|
||||
[mvc-pattern]: http://en.wikipedia.org/wiki/Model–View–Controller
|
||||
[mvc-pattern]: https://en.wikipedia.org/wiki/Model–View–Controller
|
||||
|
||||
@@ -88,6 +88,13 @@ Let's see an example:
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<body>
|
||||
<!-- The following line is how to use the `greetUser` component above in your html doc. -->
|
||||
<greet-user></greet-user>
|
||||
</body>
|
||||
```
|
||||
|
||||
Now, every time we include `<greet-user></greet-user>` in our view, AngularJS will expand it into a
|
||||
DOM sub-tree constructed using the provided `template` and managed by an instance of the specified
|
||||
controller.
|
||||
@@ -120,14 +127,14 @@ acquired skill.
|
||||
<html ng-app="phonecatApp">
|
||||
<head>
|
||||
...
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="app.js"></script>
|
||||
<script src="phone-list.component.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Use a custom component to render a list of phones -->
|
||||
<phone-list></phone-list>
|
||||
<phone-list></phone-list> <!-- This tells AngularJS to instantiate a `phoneList` component here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -148,7 +155,7 @@ angular.module('phonecatApp', []);
|
||||
// Register `phoneList` component, along with its associated controller and template
|
||||
angular.
|
||||
module('phonecatApp').
|
||||
component('phoneList', {
|
||||
component('phoneList', { // This name is what AngularJS uses to match to the `<phone-list>` element.
|
||||
template:
|
||||
'<ul>' +
|
||||
'<li ng-repeat="phone in $ctrl.phones">' +
|
||||
@@ -277,7 +284,7 @@ files, so it remains easy to locate as our application grows.
|
||||
|
||||
|
||||
[case-styles]: https://en.wikipedia.org/wiki/Letter_case#Special_case_styles
|
||||
[jasmine-docs]: http://jasmine.github.io/2.4/introduction.html
|
||||
[jasmine-home]: http://jasmine.github.io/
|
||||
[jasmine-docs]: https://jasmine.github.io/api/3.3/global
|
||||
[jasmine-home]: https://jasmine.github.io/
|
||||
[karma]: https://karma-runner.github.io/
|
||||
[mvc-pattern]: http://en.wikipedia.org/wiki/Model–View–Controller
|
||||
[mvc-pattern]: https://en.wikipedia.org/wiki/Model–View–Controller
|
||||
|
||||
@@ -230,6 +230,8 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
* Reverse the sort order by adding a `-` symbol before the sorting value:
|
||||
`<option value="-age">Oldest</option>`
|
||||
After making this change, you'll notice that the drop-down list has a blank option selected and does not default to age anymore.
|
||||
Fix this by updating the `orderProp` value in `phone-list.component.js` to match the new value on the `<option>` element.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
|
||||
from our server using one of AngularJS's built-in {@link guide/services services} called
|
||||
{@link ng.$http $http}. We will use AngularJS's {@link guide/di dependency injection (DI)} to provide
|
||||
the service to the `phoneList` component's controller.
|
||||
{@link ng.$http $http}. We will use AngularJS's {@link guide/di dependency injection (DI)} to
|
||||
provide the service to the `phoneList` component's controller.
|
||||
|
||||
* There is now a list of 20 phones, loaded from the server.
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@ URLs point to the `app/img/phones/` directory.
|
||||
...
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp" class="thumbnail">
|
||||
<a href="#/phones/{{phone.id}}" class="thumb">
|
||||
<a href="#!/phones/{{phone.id}}" class="thumb">
|
||||
<img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}" />
|
||||
</a>
|
||||
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<a href="#!/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -83,7 +83,7 @@ HTTP request to an invalid location.
|
||||
query.sendKeys('nexus');
|
||||
|
||||
element.all(by.css('.phones li a')).first().click();
|
||||
expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s');
|
||||
expect(browser.getCurrentUrl()).toContain('index.html#!/phones/nexus-s');
|
||||
});
|
||||
|
||||
...
|
||||
@@ -110,8 +110,9 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
## Summary
|
||||
|
||||
Now that you have added phone images and links, go to {@link step_09 step 9} to learn about AngularJS
|
||||
layout templates and how AngularJS makes it easy to create applications that have multiple views.
|
||||
Now that you have added phone images and links, go to {@link step_09 step 9} to learn about
|
||||
AngularJS layout templates and how AngularJS makes it easy to create applications that have
|
||||
multiple views.
|
||||
|
||||
|
||||
<ul doc-tutorial-nav="8"></ul>
|
||||
|
||||
@@ -23,49 +23,33 @@ has multiple views by adding routing, using an AngularJS module called {@link ng
|
||||
The routing functionality added in this step is provided by AngularJS in the `ngRoute` module, which
|
||||
is distributed separately from the core AngularJS framework.
|
||||
|
||||
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
|
||||
`bower.json` configuration file to include the new dependency:
|
||||
Since we are using [npm][npm] to install client-side dependencies, this step updates the
|
||||
`package.json` configuration file to include the new dependency:
|
||||
|
||||
<br />
|
||||
**`bower.json`:**
|
||||
**`package.json`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "angular-phonecat",
|
||||
"description": "A starter project for AngularJS",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/angular/angular-phonecat",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
...
|
||||
"dependencies": {
|
||||
"angular": "1.5.x",
|
||||
"angular-mocks": "1.5.x",
|
||||
"angular-route": "1.5.x",
|
||||
"angular": "1.7.x",
|
||||
"angular-route": "1.7.x",
|
||||
"bootstrap": "3.3.x"
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-route": "1.5.x"` tells bower to install a version of the angular-route
|
||||
module that is compatible with version 1.5.x of AngularJS. We must tell bower to download and install
|
||||
The new dependency `"angular-route": "1.7.x"` tells npm to install a version of the angular-route
|
||||
module that is compatible with version 1.7.x of AngularJS. We must tell npm to download and install
|
||||
this dependency.
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
|
||||
we have preconfigured `npm install` to run bower for us.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Warning:** If a new version of AngularJS has been released since you last ran `npm install`, then
|
||||
you may have a problem with the `bower install` due to a conflict between the versions of
|
||||
angular.js that need to be installed. If you run into this issue, simply delete your
|
||||
`app/bower_components` directory and then run `npm install`.
|
||||
</div>
|
||||
|
||||
|
||||
## Multiple Views, Routing and Layout Templates
|
||||
|
||||
@@ -127,8 +111,8 @@ service, the `$routeProvider` exposes APIs that allow you to define routes for y
|
||||
</div>
|
||||
|
||||
AngularJS modules solve the problem of removing global variables from the application and provide a
|
||||
way of configuring the injector. As opposed to AMD or require.js modules, AngularJS modules don't try
|
||||
to solve the problem of script load ordering or lazy script fetching. These goals are totally
|
||||
way of configuring the injector. As opposed to AMD or require.js modules, AngularJS modules don't
|
||||
try to solve the problem of script load ordering or lazy script fetching. These goals are totally
|
||||
independent and both module systems can live side-by-side and fulfill their goals.
|
||||
|
||||
To deepen your understanding on AngularJS's DI, see [Understanding Dependency Injection][wiki-di].
|
||||
@@ -146,8 +130,8 @@ into the layout template. This makes it a perfect fit for our `index.html` templ
|
||||
```html
|
||||
<head>
|
||||
...
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="bower_components/angular-route/angular-route.js"></script>
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="lib/angular-route/angular-route.js"></script>
|
||||
<script src="app.module.js"></script>
|
||||
<script src="app.config.js"></script>
|
||||
...
|
||||
@@ -203,10 +187,8 @@ code, we put it into a separate file and used the `.config` suffix.
|
||||
```js
|
||||
angular.
|
||||
module('phonecatApp').
|
||||
config(['$locationProvider', '$routeProvider',
|
||||
function config($locationProvider, $routeProvider) {
|
||||
$locationProvider.hashPrefix('!');
|
||||
|
||||
config(['$routeProvider',
|
||||
function config($routeProvider) {
|
||||
$routeProvider.
|
||||
when('/phones', {
|
||||
template: '<phone-list></phone-list>'
|
||||
@@ -226,18 +208,6 @@ the corresponding services. Here, we use the
|
||||
{@link ngRoute.$routeProvider#otherwise $routeProvider.otherwise()} methods to define our
|
||||
application routes.
|
||||
|
||||
<div class="alert alert-success">
|
||||
<p>
|
||||
We also used {@link $locationProvider#hashPrefix $locationProvider.hashPrefix()} to set the
|
||||
hash-prefix to `!`. This prefix will appear in the links to our client-side routes, right after
|
||||
the hash (`#`) symbol and before the actual path (e.g. `index.html#!/some/path`).
|
||||
</p>
|
||||
<p>
|
||||
Setting a prefix is not necessary, but it is considered a good practice (for reasons that are
|
||||
outside the scope of this tutorial). `!` is the most commonly used prefix.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Our routes are defined as follows:
|
||||
|
||||
* `when('/phones')`: Determines the view that will be shown, when the URL hash fragment is
|
||||
@@ -261,6 +231,25 @@ the route declaration — `'/phones/:phoneId'` — as a template that is matched
|
||||
URL. All variables defined with the `:` prefix are extracted into the (injectable)
|
||||
{@link ngRoute.$routeParams $routeParams} object.
|
||||
|
||||
<div class="alert alert-info">
|
||||
<p>
|
||||
You may have noticed, that — while the configured route paths start with `/` (e.g.
|
||||
`/phones`) — the URLs used in templates start with `#!/` (e.g. `#!/phones`).
|
||||
</p>
|
||||
<p>
|
||||
Without getting into much detail, AngularJS (by default) uses the hash part of the URL (i.e.
|
||||
what comes after the hash (`#`) symbol) to determine the current route. In addition to that, you
|
||||
can also specify a {@link $locationProvider#hashPrefix hash-prefix} (`!` by default) that needs
|
||||
to appear after the hash symbol in order for AngularJS to consider the value an "AngularJS path"
|
||||
and process it (for example, try to match it to a route).
|
||||
</p>
|
||||
<p>
|
||||
You can find out more about how all this works in the [Using $location](guide/$location) section
|
||||
of the Developer Guide. But all you need to know for now, is that the URLs to our various routes
|
||||
should be prefixed with `#!`.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
## The `phoneDetail` Component
|
||||
|
||||
@@ -345,8 +334,8 @@ any modification.
|
||||
|
||||
```js
|
||||
files: [
|
||||
'bower_components/angular/angular.js',
|
||||
'bower_components/angular-route/angular-route.js',
|
||||
'lib/angular/angular.js',
|
||||
'lib/angular-route/angular-route.js',
|
||||
...
|
||||
],
|
||||
```
|
||||
@@ -363,7 +352,7 @@ various URLs and verifying that the correct view was rendered.
|
||||
|
||||
it('should redirect `index.html` to `index.html#!/phones', function() {
|
||||
browser.get('index.html');
|
||||
expect(browser.getLocationAbsUrl()).toBe('/phones');
|
||||
expect(browser.getCurrentUrl()).toContain('index.html#!/phones');
|
||||
});
|
||||
|
||||
...
|
||||
@@ -424,6 +413,6 @@ With the routing set up and the phone list view implemented, we are ready to go
|
||||
<ul doc-tutorial-nav="9"></ul>
|
||||
|
||||
|
||||
[bower]: http://bower.io
|
||||
[deep-linking]: https://en.wikipedia.org/wiki/Deep_linking
|
||||
[npm]: https://www.npmjs.com/
|
||||
[wiki-di]: https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
|
||||
|
||||
@@ -21,50 +21,34 @@ In this step, we will change the way our application fetches data.
|
||||
The RESTful functionality is provided by AngularJS in the {@link ngResource ngResource} module, which
|
||||
is distributed separately from the core AngularJS framework.
|
||||
|
||||
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
|
||||
`bower.json` configuration file to include the new dependency:
|
||||
Since we are using [npm][npm] to install client-side dependencies, this step updates the
|
||||
`package.json` configuration file to include the new dependency:
|
||||
|
||||
<br />
|
||||
**`bower.json`:**
|
||||
**`package.json`:**
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "angular-phonecat",
|
||||
"description": "A starter project for AngularJS",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/angular/angular-phonecat",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
...
|
||||
"dependencies": {
|
||||
"angular": "1.5.x",
|
||||
"angular-mocks": "1.5.x",
|
||||
"angular-resource": "1.5.x",
|
||||
"angular-route": "1.5.x",
|
||||
"angular": "1.7.x",
|
||||
"angular-resource": "1.7.x",
|
||||
"angular-route": "1.7.x",
|
||||
"bootstrap": "3.3.x"
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-resource": "1.5.x"` tells bower to install a version of the
|
||||
angular-resource module that is compatible with version 1.5.x of AngularJS. We must tell bower to
|
||||
The new dependency `"angular-resource": "1.7.x"` tells npm to install a version of the
|
||||
angular-resource module that is compatible with version 1.7.x of AngularJS. We must tell npm to
|
||||
download and install this dependency.
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
|
||||
we have preconfigured `npm install` to run bower for us.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Warning:** If a new version of AngularJS has been released since you last ran `npm install`, then
|
||||
you may have a problem with the `bower install` due to a conflict between the versions of
|
||||
angular.js that need to be installed. If you run into this issue, simply delete your
|
||||
`app/bower_components` directory and then run `npm install`.
|
||||
</div>
|
||||
|
||||
|
||||
## Service
|
||||
|
||||
@@ -129,7 +113,7 @@ need to load the `angular-resource.js` file, which contains the `ngResource` mod
|
||||
```html
|
||||
<head>
|
||||
...
|
||||
<script src="bower_components/angular-resource/angular-resource.js"></script>
|
||||
<script src="lib/angular-resource/angular-resource.js"></script>
|
||||
...
|
||||
<script src="core/phone/phone.module.js"></script>
|
||||
<script src="core/phone/phone.service.js"></script>
|
||||
@@ -141,9 +125,10 @@ need to load the `angular-resource.js` file, which contains the `ngResource` mod
|
||||
## Component Controllers
|
||||
|
||||
We can now simplify our component controllers (`PhoneListController` and `PhoneDetailController`) by
|
||||
factoring out the lower-level `$http` service, replacing it with the new `Phone` service. AngularJS's
|
||||
`$resource` service is easier to use than `$http` for interacting with data sources exposed as
|
||||
RESTful resources. It is also easier now to understand what the code in our controllers is doing.
|
||||
factoring out the lower-level `$http` service, replacing it with the new `Phone` service.
|
||||
AngularJS's `$resource` service is easier to use than `$http` for interacting with data sources
|
||||
exposed as RESTful resources. It is also easier now to understand what the code in our controllers
|
||||
is doing.
|
||||
|
||||
<br />
|
||||
**`app/phone-list/phone-list.module.js`:**
|
||||
@@ -240,8 +225,8 @@ Karma configuration file with angular-resource.
|
||||
|
||||
```js
|
||||
files: [
|
||||
'bower_components/angular/angular.js',
|
||||
'bower_components/angular-resource/angular-resource.js',
|
||||
'lib/angular/angular.js',
|
||||
'lib/angular-resource/angular-resource.js',
|
||||
...
|
||||
],
|
||||
```
|
||||
@@ -319,6 +304,6 @@ Now that we have seen how to build a custom service as a RESTful client, we are
|
||||
<ul doc-tutorial-nav="13"></ul>
|
||||
|
||||
|
||||
[bower]: http://bower.io/
|
||||
[jasmine-equality]: https://jasmine.github.io/2.4/custom_equality.html
|
||||
[npm]: https://www.npmjs.com/
|
||||
[restful]: https://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||
|
||||
@@ -22,59 +22,43 @@ the template code we created earlier.
|
||||
## Dependencies
|
||||
|
||||
The animation functionality is provided by AngularJS in the `ngAnimate` module, which is distributed
|
||||
separately from the core AngularJS framework. In addition we will use [jQuery][jquery] in this project
|
||||
to do extra JavaScript animations.
|
||||
separately from the core AngularJS framework. In addition we will use [jQuery][jquery] in this
|
||||
project to do extra JavaScript animations.
|
||||
|
||||
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
|
||||
`bower.json` configuration file to include the new dependencies:
|
||||
Since we are using [npm][npm] to install client-side dependencies, this step updates the
|
||||
`package.json` configuration file to include the new dependencies:
|
||||
|
||||
<br />
|
||||
**`bower.json`:**
|
||||
**`package.json`:**
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "angular-phonecat",
|
||||
"description": "A starter project for AngularJS",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/angular/angular-phonecat",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
...
|
||||
"dependencies": {
|
||||
"angular": "1.5.x",
|
||||
"angular-animate": "1.5.x",
|
||||
"angular-mocks": "1.5.x",
|
||||
"angular-resource": "1.5.x",
|
||||
"angular-route": "1.5.x",
|
||||
"angular": "1.7.x",
|
||||
"angular-animate": "1.7.x",
|
||||
"angular-resource": "1.7.x",
|
||||
"angular-route": "1.7.x",
|
||||
"bootstrap": "3.3.x",
|
||||
"jquery": "3.2.x"
|
||||
}
|
||||
"jquery": "3.3.x"
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
* `"angular-animate": "1.5.x"` tells bower to install a version of the angular-animate module that
|
||||
is compatible with version 1.5.x of AngularJS.
|
||||
* `"jquery": "3.2.x"` tells bower to install the latest patch release of the 3.2 version of jQuery.
|
||||
Note that this is not an AngularJS library; it is the standard jQuery library. We can use bower to
|
||||
* `"angular-animate": "1.7.x"` tells npm to install a version of the angular-animate module that
|
||||
is compatible with version 1.7.x of AngularJS.
|
||||
* `"jquery": "3.3.x"` tells npm to install the latest patch release of the 3.3 version of jQuery.
|
||||
Note that this is not an AngularJS library; it is the standard jQuery library. We can use npm to
|
||||
install a wide range of 3rd party libraries.
|
||||
|
||||
Now, we must tell bower to download and install these dependencies.
|
||||
Now, we must tell npm to download and install these dependencies.
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
|
||||
we have preconfigured `npm install` to run bower for us.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Warning:** If a new version of AngularJS has been released since you last ran `npm install`, then
|
||||
you may have a problem with the `bower install` due to a conflict between the versions of
|
||||
angular.js that need to be installed. If you run into this issue, simply delete your
|
||||
`app/bower_components` directory and then run `npm install`.
|
||||
</div>
|
||||
|
||||
|
||||
## How Animations work with `ngAnimate`
|
||||
|
||||
@@ -101,12 +85,12 @@ code necessary to make your application "animation aware".
|
||||
...
|
||||
|
||||
<!-- Used for JavaScript animations (include this before angular.js) -->
|
||||
<script src="bower_components/jquery/dist/jquery.js"></script>
|
||||
<script src="lib/jquery/dist/jquery.js"></script>
|
||||
|
||||
...
|
||||
|
||||
<!-- Adds animation support in AngularJS -->
|
||||
<script src="bower_components/angular-animate/angular-animate.js"></script>
|
||||
<script src="lib/angular-animate/angular-animate.js"></script>
|
||||
|
||||
<!-- Defines JavaScript animations -->
|
||||
<script src="app.animations.js"></script>
|
||||
@@ -115,8 +99,8 @@ code necessary to make your application "animation aware".
|
||||
```
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer, when using AngularJS 1.5 or newer; jQuery 1.x is
|
||||
not officially supported.
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer, when using AngularJS 1.5 or newer;
|
||||
jQuery 1.x is not officially supported.
|
||||
In order for AngularJS to detect jQuery and take advantage of it, make sure to include `jquery.js`
|
||||
before `angular.js`.
|
||||
</div>
|
||||
@@ -556,9 +540,9 @@ There you have it! We have created a web application in a relatively short amoun
|
||||
<ul doc-tutorial-nav="14"></ul>
|
||||
|
||||
|
||||
[bower]: http://bower.io/
|
||||
[caniuse-css-animation]: http://caniuse.com/#feat=css-animation
|
||||
[caniuse-css-transitions]: http://caniuse.com/#feat=css-transitions
|
||||
[caniuse-css-animation]: https://caniuse.com/#feat=css-animation
|
||||
[caniuse-css-transitions]: https://caniuse.com/#feat=css-transitions
|
||||
[jquery]: https://jquery.com/
|
||||
[jquery-animate]: https://api.jquery.com/animate/
|
||||
[jquery-animate]: https://api.jquery.com/animate
|
||||
[mdn-animations]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations
|
||||
[npm]: https://www.npmjs.com/
|
||||
|
||||
@@ -22,5 +22,5 @@ If you have questions or feedback or just want to say "hi", please post a messag
|
||||
|
||||
[angular-seed]: https://github.com/angular/angular-seed
|
||||
[gitter]: https://gitter.im/angular/angular.js
|
||||
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
|
||||
[irc]: https://webchat.freenode.net/?channels=angularjs&uio=d4
|
||||
[mailing-list]: https://groups.google.com/forum/#!forum/angular
|
||||
|
||||
@@ -20,7 +20,7 @@ function main() {
|
||||
} catch (e) {
|
||||
fs.mkdirSync(__dirname + '/../../../src/ngParseExt');
|
||||
}
|
||||
fs.writeFile(__dirname + '/../../../src/ngParseExt/ucd.js', code);
|
||||
fs.writeFileSync(__dirname + '/../../../src/ngParseExt/ucd.js', code);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var angularFiles = require('./angularFiles');
|
||||
var sharedConfig = require('./karma-shared.conf');
|
||||
|
||||
module.exports = function(config) {
|
||||
sharedConfig(config, {testName: 'AngularJS: isolated module tests (ngAnimate)', logFile: 'karma-ngAnimate-isolated.log'});
|
||||
|
||||
config.set({
|
||||
files: angularFiles.mergeFilesFor('karmaModules-ngAnimate')
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var angularFiles = require('./angularFiles');
|
||||
var sharedConfig = require('./karma-shared.conf');
|
||||
|
||||
module.exports = function(config) {
|
||||
sharedConfig(config, {testName: 'AngularJS: isolated module tests (ngMock)', logFile: 'karma-ngMock-isolated.log'});
|
||||
|
||||
config.set({
|
||||
files: angularFiles.mergeFilesFor('karmaModules-ngMock')
|
||||
});
|
||||
};
|
||||
+11
-8
@@ -1,17 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
var angularFiles = require('./angularFiles');
|
||||
var sharedConfig = require('./karma-shared.conf');
|
||||
|
||||
module.exports = function(config) {
|
||||
sharedConfig(config, {testName: 'AngularJS: modules', logFile: 'karma-modules.log'});
|
||||
sharedConfig(config, {testName: 'AngularJS: isolated module tests', logFile: 'karma-modules-isolated.log'});
|
||||
|
||||
config.set({
|
||||
files: angularFiles.mergeFilesFor('karmaModules'),
|
||||
|
||||
junitReporter: {
|
||||
outputFile: 'test_out/modules.xml',
|
||||
suite: 'modules'
|
||||
}
|
||||
files: [
|
||||
'build/angular.js',
|
||||
'build/angular-mocks.js',
|
||||
'test/modules/no_bootstrap.js',
|
||||
'test/helpers/matchers.js',
|
||||
'test/helpers/privateMocks.js',
|
||||
'test/helpers/support.js',
|
||||
'test/helpers/testabilityPatch.js',
|
||||
'build/test-bundles/angular-*.js'
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
+8
-12
@@ -23,12 +23,7 @@ module.exports = function(config, specificOptions) {
|
||||
// SauceLabs config for local development.
|
||||
sauceLabs: {
|
||||
testName: specificOptions.testName || 'AngularJS',
|
||||
startConnect: true,
|
||||
options: {
|
||||
// We need selenium version +2.46 for Firefox 39 and the last selenium version for OS X is 2.45.
|
||||
// TODO: Uncomment when there is a selenium 2.46 available for OS X.
|
||||
// 'selenium-version': '2.46.0'
|
||||
}
|
||||
startConnect: true
|
||||
},
|
||||
|
||||
// BrowserStack config for local development.
|
||||
@@ -65,13 +60,11 @@ module.exports = function(config, specificOptions) {
|
||||
'SL_Safari-1': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.12',
|
||||
version: 'latest-1'
|
||||
},
|
||||
'SL_Safari': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.12',
|
||||
version: 'latest'
|
||||
},
|
||||
'SL_IE_9': {
|
||||
@@ -104,15 +97,15 @@ module.exports = function(config, specificOptions) {
|
||||
platform: 'Windows 10',
|
||||
version: 'latest-1'
|
||||
},
|
||||
'SL_iOS_10': {
|
||||
'SL_iOS': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
version: '10.3'
|
||||
version: 'latest'
|
||||
},
|
||||
'SL_iOS_11': {
|
||||
'SL_iOS-1': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
version: '11'
|
||||
version: 'latest-1'
|
||||
},
|
||||
|
||||
'BS_Chrome': {
|
||||
@@ -191,6 +184,9 @@ module.exports = function(config, specificOptions) {
|
||||
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
config.sauceLabs.recordScreenshots = true;
|
||||
|
||||
// Try 'websocket' for a faster transmission first. Fallback to 'polling' if necessary.
|
||||
config.transports = ['websocket', 'polling'];
|
||||
|
||||
// Debug logging into a file, that we print out at the end of the build.
|
||||
config.loggers.push({
|
||||
type: 'file',
|
||||
|
||||
+13
-5
@@ -75,10 +75,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
|
||||
wrap: function(src, name) {
|
||||
src.unshift('src/' + name + '.prefix');
|
||||
src.push('src/' + name + '.suffix');
|
||||
return src;
|
||||
wrap(src, name) {
|
||||
return [`src/${name}.prefix`, ...src, `src/${name}.suffix`];
|
||||
},
|
||||
|
||||
|
||||
@@ -135,6 +133,16 @@ module.exports = {
|
||||
|
||||
build: function(config, fn) {
|
||||
var files = grunt.file.expand(config.src);
|
||||
// grunt.file.expand might reorder the list of files
|
||||
// when it is expanding globs, so we use prefix and suffix
|
||||
// fields to ensure that files are at the start of end of
|
||||
// the list (primarily for wrapping in an IIFE).
|
||||
if (config.prefix) {
|
||||
files = grunt.file.expand(config.prefix).concat(files);
|
||||
}
|
||||
if (config.suffix) {
|
||||
files = files.concat(grunt.file.expand(config.suffix));
|
||||
}
|
||||
var styles = config.styles;
|
||||
var processedStyles;
|
||||
//concat
|
||||
@@ -221,7 +229,7 @@ module.exports = {
|
||||
//returns the 32-bit mode force flags for java compiler if supported, this makes the build much faster
|
||||
java32flags: function() {
|
||||
if (process.platform === 'win32') return '';
|
||||
if (shell.exec('java -version -d32 2>&1', {silent: true}).code !== 0) return '';
|
||||
if (shell.exec('java -d32 -version 2>&1', {silent: true}).code !== 0) return '';
|
||||
return ' -d32 -client';
|
||||
},
|
||||
|
||||
|
||||
@@ -11,14 +11,14 @@ set -e
|
||||
# Curl and run this script as part of your .travis.yml before_script section:
|
||||
# before_script:
|
||||
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
|
||||
SC_VERSION="4.4.1"
|
||||
SC_VERSION="4.5.2"
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-$SC_VERSION-linux.tar.gz"
|
||||
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
|
||||
CONNECT_DOWNLOAD="sc-$SC_VERSION-linux.tar.gz"
|
||||
|
||||
CONNECT_LOG="$LOGS_DIR/sauce-connect"
|
||||
CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout"
|
||||
CONNECT_STDERR="$LOGS_DIR/sauce-connect.stderr"
|
||||
# We don't want to create a log file because sauceconnect always logs in verbose mode. This seems
|
||||
# to be overwhelming Travis and causing flakes when we are cat-ing the log in "print_logs.sh"
|
||||
CONNECT_LOG="/dev/null"
|
||||
|
||||
# Get Connect and start it
|
||||
mkdir -p $CONNECT_DIR
|
||||
@@ -42,9 +42,6 @@ if [ ! -z "$BROWSER_PROVIDER_READY_FILE" ]; then
|
||||
fi
|
||||
|
||||
|
||||
echo "Starting Sauce Connect in the background, logging into:"
|
||||
echo " $CONNECT_LOG"
|
||||
echo " $CONNECT_STDOUT"
|
||||
echo " $CONNECT_STDERR"
|
||||
echo "Starting Sauce Connect in the background"
|
||||
sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS \
|
||||
--logfile $CONNECT_LOG 2> $CONNECT_STDERR 1> $CONNECT_STDOUT &
|
||||
--logfile $CONNECT_LOG &
|
||||
|
||||
+21
-17
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"name": "angular",
|
||||
"license": "MIT",
|
||||
"branchVersion": "^1.7.0",
|
||||
"branchPattern": "1.7.*",
|
||||
@@ -9,15 +9,14 @@
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.9.1",
|
||||
"yarn": ">=1.3.2",
|
||||
"grunt": "^1.2.0"
|
||||
"node": ">=8.12.0",
|
||||
"yarn": ">=1.10.1",
|
||||
"grunt-cli": "^1.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"commit": "git-cz",
|
||||
"test-i18n": "jasmine-node i18n/spec",
|
||||
"test-i18n-ucd": "jasmine-node i18n/ucd/spec",
|
||||
"grunt": "grunt"
|
||||
"test-i18n-ucd": "jasmine-node i18n/ucd/spec"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
@@ -32,8 +31,8 @@
|
||||
"commitplease": "^2.7.10",
|
||||
"cross-spawn": "^4.0.0",
|
||||
"cz-conventional-changelog": "1.1.4",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.16.4",
|
||||
"dgeni": "^0.4.9",
|
||||
"dgeni-packages": "^0.26.5",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"glob": "^6.0.1",
|
||||
@@ -63,18 +62,18 @@
|
||||
"jquery": "3.2.1",
|
||||
"jquery-2.1": "npm:jquery@2.1.4",
|
||||
"jquery-2.2": "npm:jquery@2.2.4",
|
||||
"karma": "^2.0.0",
|
||||
"karma-browserstack-launcher": "^1.2.0",
|
||||
"karma-chrome-launcher": "^2.1.1",
|
||||
"karma": "^3.1.4",
|
||||
"karma-browserstack-launcher": "^1.3.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-edge-launcher": "^0.4.2",
|
||||
"karma-firefox-launcher": "^1.0.1",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"karma-jasmine": "^1.1.2",
|
||||
"karma-junit-reporter": "^1.2.0",
|
||||
"karma-safari-launcher": "^1.0.0",
|
||||
"karma-sauce-launcher": "^1.2.0",
|
||||
"karma-sauce-launcher": "^2.0.2",
|
||||
"karma-script-launcher": "^1.0.0",
|
||||
"karma-spec-reporter": "^0.0.31",
|
||||
"karma-spec-reporter": "^0.0.32",
|
||||
"load-grunt-tasks": "^3.5.0",
|
||||
"lodash": "~2.4.1",
|
||||
"log4js": "^0.6.27",
|
||||
@@ -84,13 +83,13 @@
|
||||
"npm-run": "^4.1.0",
|
||||
"open-sans-fontface": "^1.4.0",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "^5.1.2",
|
||||
"protractor": "^5.4.1",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
"rewire": "~2.1.0",
|
||||
"sax": "^1.1.1",
|
||||
"selenium-webdriver": "^2.53.1",
|
||||
"selenium-webdriver": "^4.0.0-alpha.1",
|
||||
"semver": "^5.4.1",
|
||||
"serve-favicon": "^2.3.0",
|
||||
"serve-index": "^1.8.0",
|
||||
@@ -100,6 +99,11 @@
|
||||
"stringmap": "^0.2.2"
|
||||
},
|
||||
"dependencies": {},
|
||||
"resolutions": {
|
||||
"//1": "`natives@1.1.0` does not work with Node.js 10.x on Windows 10",
|
||||
"//2": "(E.g. see https://github.com/gulpjs/gulp/issues/2162.)",
|
||||
"natives": "1.1.3"
|
||||
},
|
||||
"commitplease": {
|
||||
"style": "angular",
|
||||
"nohook": true
|
||||
|
||||
@@ -79,6 +79,8 @@ function capabilitiesForSauceLabs(capabilities) {
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version,
|
||||
'elementScrollBehavior': 1
|
||||
'elementScrollBehavior': 1,
|
||||
// Allow e2e test sessions to run for a maximum of 35 minutes, instead of the default 30 minutes.
|
||||
'maxDuration': 2100
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ const BROWSER_CACHE_DURATION = 60 * 10;
|
||||
const CDN_CACHE_DURATION = 60 * 60 * 12;
|
||||
|
||||
function sendStoredFile(request, response) {
|
||||
let filePathSegments = request.path.split('/').filter((segment) => {
|
||||
const requestPath = request.path || '/';
|
||||
let filePathSegments = requestPath.split('/').filter((segment) => {
|
||||
// Remove empty leading or trailing path parts
|
||||
return segment !== '';
|
||||
});
|
||||
@@ -159,7 +160,11 @@ function sendStoredFile(request, response) {
|
||||
const nextQuery = data[1];
|
||||
const apiResponse = data[2];
|
||||
|
||||
if (!files.length && (!apiResponse || !apiResponse.prefixes)) {
|
||||
if (
|
||||
// we got no files or directories from previous query pages
|
||||
!fileList.length && !directoryList.length &&
|
||||
// this query page has no file or directories
|
||||
!files.length && (!apiResponse || !apiResponse.prefixes)) {
|
||||
return Promise.reject({
|
||||
code: 404
|
||||
});
|
||||
@@ -190,22 +195,16 @@ const snapshotRegex = /^snapshot(-stable)?\//;
|
||||
* When a new zip file is uploaded into snapshot or snapshot-stable,
|
||||
* delete the previous zip file.
|
||||
*/
|
||||
function deleteOldSnapshotZip(event) {
|
||||
const object = event.data;
|
||||
|
||||
function deleteOldSnapshotZip(object, context) {
|
||||
const bucketId = object.bucket;
|
||||
const filePath = object.name;
|
||||
const contentType = object.contentType;
|
||||
const resourceState = object.resourceState;
|
||||
|
||||
const bucket = gcs.bucket(bucketId);
|
||||
|
||||
const snapshotFolderMatch = filePath.match(snapshotRegex);
|
||||
|
||||
if (!snapshotFolderMatch ||
|
||||
contentType !== 'application/zip' ||
|
||||
resourceState === 'not_exists' // Deletion event
|
||||
) {
|
||||
if (!snapshotFolderMatch || contentType !== 'application/zip') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -230,4 +229,4 @@ function deleteOldSnapshotZip(event) {
|
||||
}
|
||||
|
||||
exports.sendStoredFile = functions.https.onRequest(sendStoredFile);
|
||||
exports.deleteOldSnapshotZip = functions.storage.object().onChange(deleteOldSnapshotZip);
|
||||
exports.deleteOldSnapshotZip = functions.storage.object().onFinalize(deleteOldSnapshotZip);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,8 @@
|
||||
"description": "Cloud Functions to serve files from gcs to code.angularjs.org",
|
||||
"dependencies": {
|
||||
"@google-cloud/storage": "^1.1.1",
|
||||
"firebase-admin": "^4.2.1",
|
||||
"firebase-functions": "^0.5.9"
|
||||
"firebase-admin": "^5.11.0",
|
||||
"firebase-functions": "^1.0.4"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -21,21 +21,21 @@ rm -f angular.js.size
|
||||
|
||||
|
||||
# BUILD #
|
||||
yarn run grunt -- ci-checks package --no-color
|
||||
yarn grunt ci-checks package --no-color
|
||||
|
||||
mkdir -p test_out
|
||||
|
||||
# UNIT TESTS #
|
||||
yarn run grunt -- test:unit --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
|
||||
yarn grunt test:unit --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
|
||||
|
||||
# END TO END TESTS #
|
||||
yarn run grunt -- test:ci-protractor
|
||||
yarn grunt test:ci-protractor
|
||||
|
||||
# DOCS APP TESTS #
|
||||
yarn run grunt -- test:docs --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
|
||||
yarn grunt test:docs --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
|
||||
|
||||
# Promises/A+ TESTS #
|
||||
yarn run grunt -- test:promises-aplus --no-color
|
||||
yarn grunt test:promises-aplus --no-color
|
||||
|
||||
|
||||
# CHECK SIZE #
|
||||
|
||||
@@ -8,11 +8,11 @@ nvm install
|
||||
|
||||
# clean out and install yarn
|
||||
rm -rf ~/.yarn
|
||||
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
|
||||
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.10.1
|
||||
export PATH="$HOME/.yarn/bin:$PATH"
|
||||
|
||||
# Ensure that we have the local dependencies installed
|
||||
yarn install
|
||||
|
||||
echo testing grunt version
|
||||
yarn run grunt -- --version
|
||||
yarn grunt --version
|
||||
|
||||
@@ -37,7 +37,7 @@ function init {
|
||||
function build {
|
||||
cd ../..
|
||||
source scripts/jenkins/init-node.sh
|
||||
yarn run grunt -- ci-checks package --no-color
|
||||
yarn grunt ci-checks package --no-color
|
||||
|
||||
cd $SCRIPT_DIR
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
set -e
|
||||
|
||||
yarn global add grunt-cli@1.2.0
|
||||
|
||||
mkdir -p "$LOGS_DIR"
|
||||
|
||||
if [ "$JOB" != "ci-checks" ]; then
|
||||
@@ -13,13 +11,13 @@ fi
|
||||
|
||||
# ci-checks and unit tests do not run against the packaged code
|
||||
if [[ "$JOB" != "ci-checks" ]] && [[ "$JOB" != unit-* ]]; then
|
||||
grunt package
|
||||
yarn grunt package
|
||||
fi
|
||||
|
||||
# unit runs the docs tests too which need a built version of the code
|
||||
if [[ "$JOB" = unit-* ]]; then
|
||||
grunt validate-angular-files
|
||||
grunt build
|
||||
yarn grunt validate-angular-files
|
||||
yarn grunt build
|
||||
fi
|
||||
|
||||
# check this after the package, because at this point the browser_provider
|
||||
|
||||
+10
-7
@@ -11,14 +11,14 @@ export SAUCE_ACCESS_KEY
|
||||
BROWSER_STACK_ACCESS_KEY=$(echo "$BROWSER_STACK_ACCESS_KEY" | rev)
|
||||
SAUCE_ACCESS_KEY=$(echo "$SAUCE_ACCESS_KEY" | rev)
|
||||
|
||||
# TODO: restore "SL_EDGE-1" once Sauce Labs adds Edge 17 and "SL_EDGE-1" refers
|
||||
# to version 16. Edge 15 disconnects from Karma frequently causing extreme build instability.
|
||||
# The currently latest version of Safari on Saucelabs (v12) is unstable and disconnects frequently.
|
||||
# TODO: Add `SL_Safari` back, once/if it becomes more stable again.
|
||||
BROWSERS="SL_Chrome,SL_Chrome-1,\
|
||||
SL_Firefox,SL_Firefox-1,\
|
||||
SL_Safari,SL_Safari-1,\
|
||||
SL_iOS_10,SL_iOS_11,\
|
||||
SL_Safari-1,\
|
||||
SL_iOS,SL_iOS-1,\
|
||||
SL_IE_9,SL_IE_10,SL_IE_11,\
|
||||
SL_EDGE"
|
||||
SL_EDGE,SL_EDGE-1"
|
||||
|
||||
case "$JOB" in
|
||||
"ci-checks")
|
||||
@@ -29,19 +29,21 @@ case "$JOB" in
|
||||
# convert commit range to 2 dots, as commitplease uses `git log`.
|
||||
# See https://github.com/travis-ci/travis-ci/issues/4596 for more info
|
||||
echo "Validate commit messages in PR:"
|
||||
yarn run commitplease -- "${TRAVIS_COMMIT_RANGE/.../..}"
|
||||
yarn run commitplease "${TRAVIS_COMMIT_RANGE/.../..}"
|
||||
fi
|
||||
;;
|
||||
"unit-core")
|
||||
grunt test:promises-aplus
|
||||
grunt test:jqlite --browsers="$BROWSERS" --reporters=spec
|
||||
grunt test:modules --browsers="$BROWSERS" --reporters=spec
|
||||
;;
|
||||
"unit-jquery")
|
||||
grunt test:jquery --browsers="$BROWSERS" --reporters=spec
|
||||
grunt test:jquery-2.2 --browsers="$BROWSERS" --reporters=spec
|
||||
grunt test:jquery-2.1 --browsers="$BROWSERS" --reporters=spec
|
||||
;;
|
||||
"unit-modules")
|
||||
grunt test:modules --browsers="$BROWSERS" --reporters=spec
|
||||
;;
|
||||
"docs-app")
|
||||
grunt tests:docs --browsers="$BROWSERS" --reporters=spec
|
||||
grunt test:travis-protractor --specs="docs/app/e2e/**/*.scenario.js"
|
||||
@@ -102,6 +104,7 @@ case "$JOB" in
|
||||
'ci-checks',\
|
||||
'unit-core',\
|
||||
'unit-jquery',\
|
||||
'unit-modules',\
|
||||
'docs-app',\
|
||||
'e2e',\
|
||||
or\
|
||||
|
||||
@@ -7,5 +7,5 @@ for FILE in $LOG_FILES; do
|
||||
echo "================================================================================"
|
||||
echo " $FILE"
|
||||
echo "================================================================================"
|
||||
cat $FILE
|
||||
cat $FILE || true
|
||||
done
|
||||
|
||||
@@ -171,9 +171,15 @@
|
||||
/* ng/q.js */
|
||||
"markQExceptionHandled": false,
|
||||
|
||||
/* sce.js */
|
||||
"SCE_CONTEXTS": false,
|
||||
|
||||
/* ng/directive/directives.js */
|
||||
"ngDirective": false,
|
||||
|
||||
/* ng/directive/ngEventDirs.js */
|
||||
"createEventDirective": false,
|
||||
|
||||
/* ng/directive/input.js */
|
||||
"VALID_CLASS": false,
|
||||
"INVALID_CLASS": false,
|
||||
|
||||
+32
-8
@@ -396,8 +396,8 @@ function extend(dst) {
|
||||
* sinceVersion="1.6.5"
|
||||
* This function is deprecated, but will not be removed in the 1.x lifecycle.
|
||||
* There are edge cases (see {@link angular.merge#known-issues known issues}) that are not
|
||||
* supported by this function. We suggest
|
||||
* using [lodash's merge()](https://lodash.com/docs/4.17.4#merge) instead.
|
||||
* supported by this function. We suggest using another, similar library for all-purpose merging,
|
||||
* such as [lodash's merge()](https://lodash.com/docs/4.17.4#merge).
|
||||
*
|
||||
* @knownIssue
|
||||
* This is a list of (known) object types that are not handled correctly by this function:
|
||||
@@ -406,6 +406,8 @@ function extend(dst) {
|
||||
* - [`CanvasGradient`](https://developer.mozilla.org/docs/Web/API/CanvasGradient)
|
||||
* - AngularJS {@link $rootScope.Scope scopes};
|
||||
*
|
||||
* `angular.merge` also does not support merging objects with circular references.
|
||||
*
|
||||
* @param {Object} dst Destination object.
|
||||
* @param {...Object} src Source object(s).
|
||||
* @returns {Object} Reference to `dst`.
|
||||
@@ -783,7 +785,9 @@ function arrayRemove(array, value) {
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Creates a deep copy of `source`, which should be an object or an array.
|
||||
* Creates a deep copy of `source`, which should be an object or an array. This functions is used
|
||||
* internally, mostly in the change-detection code. It is not intended as an all-purpose copy
|
||||
* function, and has several limitations (see below).
|
||||
*
|
||||
* * If no destination is supplied, a copy of the object or array is created.
|
||||
* * If a destination is provided, all of its elements (for arrays) or properties (for objects)
|
||||
@@ -792,15 +796,35 @@ function arrayRemove(array, value) {
|
||||
* * If `source` is identical to `destination` an exception will be thrown.
|
||||
*
|
||||
* <br />
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
|
||||
* and on `destination`) will be ignored.
|
||||
* </div>
|
||||
*
|
||||
* @param {*} source The source that will be used to make a copy.
|
||||
* Can be any type, including primitives, `null`, and `undefined`.
|
||||
* @param {(Object|Array)=} destination Destination into which the source is copied. If
|
||||
* provided, must be of the same type as `source`.
|
||||
* <div class="alert alert-warning">
|
||||
* `angular.copy` does not check if destination and source are of the same type. It's the
|
||||
* developer's responsibility to make sure they are compatible.
|
||||
* </div>
|
||||
*
|
||||
* @knownIssue
|
||||
* This is a non-exhaustive list of object types / features that are not handled correctly by
|
||||
* `angular.copy`. Note that since this functions is used by the change detection code, this
|
||||
* means binding or watching objects of these types (or that include these types) might not work
|
||||
* correctly.
|
||||
* - [`File`](https://developer.mozilla.org/docs/Web/API/File)
|
||||
* - [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)
|
||||
* - [`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData)
|
||||
* - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream)
|
||||
* - [`Set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)
|
||||
* - [`WeakMap`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)
|
||||
* - ['getter'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/
|
||||
* [`setter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set)`
|
||||
*
|
||||
* @param {*} source The source that will be used to make a copy. Can be any type, including
|
||||
* primitives, `null`, and `undefined`.
|
||||
* @param {(Object|Array)=} destination Destination into which the source is copied. If provided,
|
||||
* must be of the same type as `source`.
|
||||
* @returns {*} The copy or updated `destination`, if `destination` was specified.
|
||||
*
|
||||
* @example
|
||||
@@ -1909,7 +1933,7 @@ function bindJQuery() {
|
||||
jqLite.cleanData = function(elems) {
|
||||
var events;
|
||||
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
|
||||
events = jqLite._data(elem).events;
|
||||
events = (jqLite._data(elem) || {}).events;
|
||||
if (events && events.$destroy) {
|
||||
jqLite(elem).triggerHandler('$destroy');
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
htmlAnchorDirective,
|
||||
inputDirective,
|
||||
inputDirective,
|
||||
hiddenInputBrowserCacheDirective,
|
||||
formDirective,
|
||||
scriptDirective,
|
||||
selectDirective,
|
||||
@@ -28,6 +28,7 @@
|
||||
ngInitDirective,
|
||||
ngNonBindableDirective,
|
||||
ngPluralizeDirective,
|
||||
ngRefDirective,
|
||||
ngRepeatDirective,
|
||||
ngShowDirective,
|
||||
ngStyleDirective,
|
||||
@@ -69,6 +70,7 @@
|
||||
$FilterProvider,
|
||||
$$ForceReflowProvider,
|
||||
$InterpolateProvider,
|
||||
$$IntervalFactoryProvider,
|
||||
$IntervalProvider,
|
||||
$HttpProvider,
|
||||
$HttpParamSerializerProvider,
|
||||
@@ -87,6 +89,7 @@
|
||||
$SceProvider,
|
||||
$SceDelegateProvider,
|
||||
$SnifferProvider,
|
||||
$$TaskTrackerFactoryProvider,
|
||||
$TemplateCacheProvider,
|
||||
$TemplateRequestProvider,
|
||||
$$TestabilityProvider,
|
||||
@@ -194,6 +197,7 @@ function publishExternalAPI(angular) {
|
||||
ngInit: ngInitDirective,
|
||||
ngNonBindable: ngNonBindableDirective,
|
||||
ngPluralize: ngPluralizeDirective,
|
||||
ngRef: ngRefDirective,
|
||||
ngRepeat: ngRepeatDirective,
|
||||
ngShow: ngShowDirective,
|
||||
ngStyle: ngStyleDirective,
|
||||
@@ -217,7 +221,8 @@ function publishExternalAPI(angular) {
|
||||
ngModelOptions: ngModelOptionsDirective
|
||||
}).
|
||||
directive({
|
||||
ngInclude: ngIncludeFillContentDirective
|
||||
ngInclude: ngIncludeFillContentDirective,
|
||||
input: hiddenInputBrowserCacheDirective
|
||||
}).
|
||||
directive(ngAttributeAliasDirectives).
|
||||
directive(ngEventDirectives);
|
||||
@@ -239,6 +244,7 @@ function publishExternalAPI(angular) {
|
||||
$$forceReflow: $$ForceReflowProvider,
|
||||
$interpolate: $InterpolateProvider,
|
||||
$interval: $IntervalProvider,
|
||||
$$intervalFactory: $$IntervalFactoryProvider,
|
||||
$http: $HttpProvider,
|
||||
$httpParamSerializer: $HttpParamSerializerProvider,
|
||||
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
|
||||
@@ -254,6 +260,7 @@ function publishExternalAPI(angular) {
|
||||
$sce: $SceProvider,
|
||||
$sceDelegate: $SceDelegateProvider,
|
||||
$sniffer: $SnifferProvider,
|
||||
$$taskTrackerFactory: $$TaskTrackerFactoryProvider,
|
||||
$templateCache: $TemplateCacheProvider,
|
||||
$templateRequest: $TemplateRequestProvider,
|
||||
$$testability: $$TestabilityProvider,
|
||||
|
||||
+8
-4
@@ -45,11 +45,10 @@ function NgMapShim() {
|
||||
}
|
||||
NgMapShim.prototype = {
|
||||
_idx: function(key) {
|
||||
if (key === this._lastKey) {
|
||||
return this._lastIndex;
|
||||
if (key !== this._lastKey) {
|
||||
this._lastKey = key;
|
||||
this._lastIndex = this._keys.indexOf(key);
|
||||
}
|
||||
this._lastKey = key;
|
||||
this._lastIndex = this._keys.indexOf(key);
|
||||
return this._lastIndex;
|
||||
},
|
||||
_transformKey: function(key) {
|
||||
@@ -62,6 +61,11 @@ NgMapShim.prototype = {
|
||||
return this._values[idx];
|
||||
}
|
||||
},
|
||||
has: function(key) {
|
||||
key = this._transformKey(key);
|
||||
var idx = this._idx(key);
|
||||
return idx !== -1;
|
||||
},
|
||||
set: function(key, value) {
|
||||
key = this._transformKey(key);
|
||||
var idx = this._idx(key);
|
||||
|
||||
+16
-3
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
var minErrConfig = {
|
||||
objectMaxDepth: 5
|
||||
objectMaxDepth: 5,
|
||||
urlErrorParamsEnabled: true
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -30,12 +31,21 @@ var minErrConfig = {
|
||||
* * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a
|
||||
* non-positive or non-numeric value, removes the max depth limit.
|
||||
* Default: 5
|
||||
*
|
||||
* * `urlErrorParamsEnabled` **{Boolean}** - Specifies wether the generated error url will
|
||||
* contain the parameters of the thrown error. Disabling the parameters can be useful if the
|
||||
* generated error url is very long.
|
||||
*
|
||||
* Default: true. When used without argument, it returns the current value.
|
||||
*/
|
||||
function errorHandlingConfig(config) {
|
||||
if (isObject(config)) {
|
||||
if (isDefined(config.objectMaxDepth)) {
|
||||
minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN;
|
||||
}
|
||||
if (isDefined(config.urlErrorParamsEnabled) && isBoolean(config.urlErrorParamsEnabled)) {
|
||||
minErrConfig.urlErrorParamsEnabled = config.urlErrorParamsEnabled;
|
||||
}
|
||||
} else {
|
||||
return minErrConfig;
|
||||
}
|
||||
@@ -50,6 +60,7 @@ function isValidObjectMaxDepth(maxDepth) {
|
||||
return isNumber(maxDepth) && maxDepth > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description
|
||||
*
|
||||
@@ -113,8 +124,10 @@ function minErr(module, ErrorConstructor) {
|
||||
|
||||
message += '\n' + url + (module ? module + '/' : '') + code;
|
||||
|
||||
for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
|
||||
if (minErrConfig.urlErrorParamsEnabled) {
|
||||
for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new ErrorConstructor(message);
|
||||
|
||||
+28
-5
@@ -369,14 +369,39 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note**: Generally, the events that are fired correspond 1:1 to `$animate` method names,
|
||||
* e.g. {@link ng.$animate#addClass addClass()} will fire `addClass`, and {@link ng.ngClass}
|
||||
* will fire `addClass` if classes are added, and `removeClass` if classes are removed.
|
||||
* However, there are two exceptions:
|
||||
*
|
||||
* <ul>
|
||||
* <li>if both an {@link ng.$animate#addClass addClass()} and a
|
||||
* {@link ng.$animate#removeClass removeClass()} action are performed during the same
|
||||
* animation, the event fired will be `setClass`. This is true even for `ngClass`.</li>
|
||||
* <li>an {@link ng.$animate#animate animate()} call that adds and removes classes will fire
|
||||
* the `setClass` event, but if it either removes or adds classes,
|
||||
* it will fire `animate` instead.</li>
|
||||
* </ul>
|
||||
*
|
||||
* </div>
|
||||
*
|
||||
* @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
|
||||
* @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
|
||||
* as well as among its children
|
||||
* @param {Function} callback the callback function that will be fired when the listener is triggered
|
||||
* @param {Function} callback the callback function that will be fired when the listener is triggered.
|
||||
*
|
||||
* The arguments present in the callback function are:
|
||||
* * `element` - The captured DOM element that the animation was fired on.
|
||||
* * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
|
||||
* * `data` - an object with these properties:
|
||||
* * addClass - `{string|null}` - space-separated CSS classes to add to the element
|
||||
* * removeClass - `{string|null}` - space-separated CSS classes to remove from the element
|
||||
* * from - `{Object|null}` - CSS properties & values at the beginning of the animation
|
||||
* * to - `{Object|null}` - CSS properties & values at the end of the animation
|
||||
*
|
||||
* Note that the callback does not trigger a scope digest. Wrap your call into a
|
||||
* {@link $rootScope.Scope#$apply scope.$apply} to propagate changes to the scope.
|
||||
*/
|
||||
on: $$animateQueue.on,
|
||||
|
||||
@@ -644,9 +669,8 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element.
|
||||
* The object can have the following properties:
|
||||
*
|
||||
* - **addClass** - `{string}` - space-separated CSS classes to add to element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
|
||||
*
|
||||
* @return {Runner} animationRunner the animation runner
|
||||
@@ -676,7 +700,6 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
*
|
||||
* - **addClass** - `{string}` - space-separated CSS classes to add to element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
|
||||
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
|
||||
*
|
||||
* @return {Runner} the animation runner
|
||||
@@ -706,8 +729,8 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) {
|
||||
* The object can have the following properties:
|
||||
*
|
||||
* - **addClass** - `{string}` - space-separated CSS classes to add to element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
|
||||
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
|
||||
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
|
||||
*
|
||||
* @return {Runner} the animation runner
|
||||
|
||||
+50
-64
@@ -1,5 +1,14 @@
|
||||
'use strict';
|
||||
/* global stripHash: true */
|
||||
/* global getHash: true, stripHash: false */
|
||||
|
||||
function getHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? '' : url.substr(index);
|
||||
}
|
||||
|
||||
function trimEmptyHash(url) {
|
||||
return url.replace(/#$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* ! This is a private undocumented service !
|
||||
@@ -22,61 +31,27 @@
|
||||
* @param {object} $log window.console or an object with the same interface.
|
||||
* @param {object} $sniffer $sniffer service
|
||||
*/
|
||||
function Browser(window, document, $log, $sniffer) {
|
||||
function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
|
||||
var self = this,
|
||||
location = window.location,
|
||||
history = window.history,
|
||||
setTimeout = window.setTimeout,
|
||||
clearTimeout = window.clearTimeout,
|
||||
pendingDeferIds = {};
|
||||
pendingDeferIds = {},
|
||||
taskTracker = $$taskTrackerFactory($log);
|
||||
|
||||
self.isMock = false;
|
||||
|
||||
var outstandingRequestCount = 0;
|
||||
var outstandingRequestCallbacks = [];
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Task-tracking API
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO(vojta): remove this temporary api
|
||||
self.$$completeOutstandingRequest = completeOutstandingRequest;
|
||||
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
|
||||
self.$$completeOutstandingRequest = taskTracker.completeTask;
|
||||
self.$$incOutstandingRequestCount = taskTracker.incTaskCount;
|
||||
|
||||
/**
|
||||
* Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
|
||||
* counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
|
||||
*/
|
||||
function completeOutstandingRequest(fn) {
|
||||
try {
|
||||
fn.apply(null, sliceArgs(arguments, 1));
|
||||
} finally {
|
||||
outstandingRequestCount--;
|
||||
if (outstandingRequestCount === 0) {
|
||||
while (outstandingRequestCallbacks.length) {
|
||||
try {
|
||||
outstandingRequestCallbacks.pop()();
|
||||
} catch (e) {
|
||||
$log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? '' : url.substr(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* TODO(vojta): prefix this method with $$ ?
|
||||
* @param {function()} callback Function that will be called when no outstanding request
|
||||
*/
|
||||
self.notifyWhenNoOutstandingRequests = function(callback) {
|
||||
if (outstandingRequestCount === 0) {
|
||||
callback();
|
||||
} else {
|
||||
outstandingRequestCallbacks.push(callback);
|
||||
}
|
||||
};
|
||||
// TODO(vojta): prefix this method with $$ ?
|
||||
self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// URL API
|
||||
@@ -101,20 +76,21 @@ function Browser(window, document, $log, $sniffer) {
|
||||
*
|
||||
* @description
|
||||
* GETTER:
|
||||
* Without any argument, this method just returns current value of location.href.
|
||||
* Without any argument, this method just returns current value of `location.href` (with a
|
||||
* trailing `#` stripped of if the hash is empty).
|
||||
*
|
||||
* SETTER:
|
||||
* With at least one argument, this method sets url to new value.
|
||||
* If html5 history api supported, pushState/replaceState is used, otherwise
|
||||
* location.href/location.replace is used.
|
||||
* Returns its own instance to allow chaining
|
||||
* If html5 history api supported, `pushState`/`replaceState` is used, otherwise
|
||||
* `location.href`/`location.replace` is used.
|
||||
* Returns its own instance to allow chaining.
|
||||
*
|
||||
* NOTE: this api is intended for use only by the $location service. Please use the
|
||||
* NOTE: this api is intended for use only by the `$location` service. Please use the
|
||||
* {@link ng.$location $location service} to change url.
|
||||
*
|
||||
* @param {string} url New url (when used as setter)
|
||||
* @param {boolean=} replace Should new url replace current history record?
|
||||
* @param {object=} state object to use with pushState/replaceState
|
||||
* @param {object=} state State object to use with `pushState`/`replaceState`
|
||||
*/
|
||||
self.url = function(url, replace, state) {
|
||||
// In modern browsers `history.state` is `null` by default; treating it separately
|
||||
@@ -132,6 +108,9 @@ function Browser(window, document, $log, $sniffer) {
|
||||
if (url) {
|
||||
var sameState = lastHistoryState === state;
|
||||
|
||||
// Normalize the inputted URL
|
||||
url = urlResolve(url).href;
|
||||
|
||||
// Don't change anything if previous and current URLs and states match. This also prevents
|
||||
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
|
||||
// See https://github.com/angular/angular.js/commit/ffb2701
|
||||
@@ -172,7 +151,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// - pendingLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened or if there is a bug like in iOS 9 (see
|
||||
// https://openradar.appspot.com/22186109).
|
||||
return pendingLocation || location.href;
|
||||
return trimEmptyHash(pendingLocation || location.href);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -307,7 +286,8 @@ function Browser(window, document, $log, $sniffer) {
|
||||
/**
|
||||
* @name $browser#defer
|
||||
* @param {function()} fn A function, who's execution should be deferred.
|
||||
* @param {number=} [delay=0] of milliseconds to defer the function execution.
|
||||
* @param {number=} [delay=0] Number of milliseconds to defer the function execution.
|
||||
* @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is deferred.
|
||||
* @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
|
||||
*
|
||||
* @description
|
||||
@@ -318,14 +298,19 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* via `$browser.defer.flush()`.
|
||||
*
|
||||
*/
|
||||
self.defer = function(fn, delay) {
|
||||
self.defer = function(fn, delay, taskType) {
|
||||
var timeoutId;
|
||||
outstandingRequestCount++;
|
||||
|
||||
delay = delay || 0;
|
||||
taskType = taskType || taskTracker.DEFAULT_TASK_TYPE;
|
||||
|
||||
taskTracker.incTaskCount(taskType);
|
||||
timeoutId = setTimeout(function() {
|
||||
delete pendingDeferIds[timeoutId];
|
||||
completeOutstandingRequest(fn);
|
||||
}, delay || 0);
|
||||
pendingDeferIds[timeoutId] = true;
|
||||
taskTracker.completeTask(fn, taskType);
|
||||
}, delay);
|
||||
pendingDeferIds[timeoutId] = taskType;
|
||||
|
||||
return timeoutId;
|
||||
};
|
||||
|
||||
@@ -341,10 +326,11 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* canceled.
|
||||
*/
|
||||
self.defer.cancel = function(deferId) {
|
||||
if (pendingDeferIds[deferId]) {
|
||||
if (pendingDeferIds.hasOwnProperty(deferId)) {
|
||||
var taskType = pendingDeferIds[deferId];
|
||||
delete pendingDeferIds[deferId];
|
||||
clearTimeout(deferId);
|
||||
completeOutstandingRequest(noop);
|
||||
taskTracker.completeTask(noop, taskType);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -354,8 +340,8 @@ function Browser(window, document, $log, $sniffer) {
|
||||
|
||||
/** @this */
|
||||
function $BrowserProvider() {
|
||||
this.$get = ['$window', '$log', '$sniffer', '$document',
|
||||
function($window, $log, $sniffer, $document) {
|
||||
return new Browser($window, $document, $log, $sniffer);
|
||||
}];
|
||||
this.$get = ['$window', '$log', '$sniffer', '$document', '$$taskTrackerFactory',
|
||||
function($window, $log, $sniffer, $document, $$taskTrackerFactory) {
|
||||
return new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory);
|
||||
}];
|
||||
}
|
||||
|
||||
+590
-86
@@ -1030,8 +1030,7 @@
|
||||
*
|
||||
* See issue [#2573](https://github.com/angular/angular.js/issues/2573).
|
||||
*
|
||||
* #### `transclude: element` in the replace template root can have
|
||||
* unexpected effects
|
||||
* #### `transclude: element` in the replace template root can have unexpected effects
|
||||
*
|
||||
* When the replace template has a directive at the root node that uses
|
||||
* {@link $compile#-transclude- `transclude: element`}, e.g.
|
||||
@@ -1045,6 +1044,325 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngProp
|
||||
* @restrict A
|
||||
* @element ANY
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* ```html
|
||||
* <ANY ng-prop-propname="expression">
|
||||
* </ANY>
|
||||
* ```
|
||||
*
|
||||
* or with uppercase letters in property (e.g. "propName"):
|
||||
*
|
||||
*
|
||||
* ```html
|
||||
* <ANY ng-prop-prop_name="expression">
|
||||
* </ANY>
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* @description
|
||||
* The `ngProp` directive binds an expression to a DOM element property.
|
||||
* `ngProp` allows writing to arbitrary properties by including
|
||||
* the property name in the attribute, e.g. `ng-prop-value="'my value'"` binds 'my value' to
|
||||
* the `value` property.
|
||||
*
|
||||
* Usually, it's not necessary to write to properties in AngularJS, as the built-in directives
|
||||
* handle the most common use cases (instead of the above example, you would use {@link ngValue}).
|
||||
*
|
||||
* However, [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements)
|
||||
* often use custom properties to hold data, and `ngProp` can be used to provide input to these
|
||||
* custom elements.
|
||||
*
|
||||
* ## Binding to camelCase properties
|
||||
*
|
||||
* Since HTML attributes are case-insensitive, camelCase properties like `innerHTML` must be escaped.
|
||||
* AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
|
||||
* `innerHTML` must be written as `ng-prop-inner_h_t_m_l="expression"` (Note that this is just an
|
||||
* example, and for binding HTML {@link ngBindHtml} should be used.
|
||||
*
|
||||
* ## Security
|
||||
*
|
||||
* Binding expressions to arbitrary properties poses a security risk, as properties like `innerHTML`
|
||||
* can insert potentially dangerous HTML into the application, e.g. script tags that execute
|
||||
* malicious code.
|
||||
* For this reason, `ngProp` applies Strict Contextual Escaping with the {@link ng.$sce $sce service}.
|
||||
* This means vulnerable properties require their content to be "trusted", based on the
|
||||
* context of the property. For example, the `innerHTML` is in the `HTML` context, and the
|
||||
* `iframe.src` property is in the `RESOURCE_URL` context, which requires that values written to
|
||||
* this property are trusted as a `RESOURCE_URL`.
|
||||
*
|
||||
* This can be set explicitly by calling $sce.trustAs(type, value) on the value that is
|
||||
* trusted before passing it to the `ng-prop-*` directive. There are exist shorthand methods for
|
||||
* each context type in the form of {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl()} et al.
|
||||
*
|
||||
* In some cases you can also rely upon automatic sanitization of untrusted values - see below.
|
||||
*
|
||||
* Based on the context, other options may exist to mark a value as trusted / configure the behavior
|
||||
* of {@link ng.$sce}. For example, to restrict the `RESOURCE_URL` context to specific origins, use
|
||||
* the {@link $sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist()}
|
||||
* and {@link $sceDelegateProvider#resourceUrlBlacklist resourceUrlBlacklist()}.
|
||||
*
|
||||
* {@link ng.$sce#what-trusted-context-types-are-supported- Find out more about the different context types}.
|
||||
*
|
||||
* ### HTML Sanitization
|
||||
*
|
||||
* By default, `$sce` will throw an error if it detects untrusted HTML content, and will not bind the
|
||||
* content.
|
||||
* However, if you include the {@link ngSanitize ngSanitize module}, it will try to sanitize the
|
||||
* potentially dangerous HTML, e.g. strip non-whitelisted tags and attributes when binding to
|
||||
* `innerHTML`.
|
||||
*
|
||||
* @example
|
||||
* ### Binding to different contexts
|
||||
*
|
||||
* <example name="ngProp" module="exampleNgProp">
|
||||
* <file name="app.js">
|
||||
* angular.module('exampleNgProp', [])
|
||||
* .component('main', {
|
||||
* templateUrl: 'main.html',
|
||||
* controller: function($sce) {
|
||||
* this.safeContent = '<strong>Safe content</strong>';
|
||||
* this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
|
||||
* this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="main.html">
|
||||
* <div>
|
||||
* <div class="prop-unit">
|
||||
* Binding to a property without security context:
|
||||
* <div class="prop-binding" ng-prop-inner_text="$ctrl.safeContent"></div>
|
||||
* <span class="prop-note">innerText</span> (safeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* "Safe" content that requires a security context will throw because the contents could potentially be dangerous ...
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (safeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* ... so that actually dangerous content cannot be executed:
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (unsafeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* ... but unsafe Content that has been trusted explicitly works - only do this if you are 100% sure!
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (trustedUnsafeContent)
|
||||
* </div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <main></main>
|
||||
* </file>
|
||||
* <file name="styles.css">
|
||||
* .prop-unit {
|
||||
* margin-bottom: 10px;
|
||||
* }
|
||||
*
|
||||
* .prop-binding {
|
||||
* min-height: 30px;
|
||||
* border: 1px solid blue;
|
||||
* }
|
||||
*
|
||||
* .prop-note {
|
||||
* font-family: Monospace;
|
||||
* }
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ### Binding to innerHTML with ngSanitize
|
||||
*
|
||||
* <example name="ngProp" module="exampleNgProp" deps="angular-sanitize.js">
|
||||
* <file name="app.js">
|
||||
* angular.module('exampleNgProp', ['ngSanitize'])
|
||||
* .component('main', {
|
||||
* templateUrl: 'main.html',
|
||||
* controller: function($sce) {
|
||||
* this.safeContent = '<strong>Safe content</strong>';
|
||||
* this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
|
||||
* this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="main.html">
|
||||
* <div>
|
||||
* <div class="prop-unit">
|
||||
* "Safe" content will be sanitized ...
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (safeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* ... as will dangerous content:
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (unsafeContent)
|
||||
* </div>
|
||||
*
|
||||
* <div class="prop-unit">
|
||||
* ... and content that has been trusted explicitly works the same as without ngSanitize:
|
||||
* <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
|
||||
* <span class="prop-note">innerHTML</span> (trustedUnsafeContent)
|
||||
* </div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <main></main>
|
||||
* </file>
|
||||
* <file name="styles.css">
|
||||
* .prop-unit {
|
||||
* margin-bottom: 10px;
|
||||
* }
|
||||
*
|
||||
* .prop-binding {
|
||||
* min-height: 30px;
|
||||
* border: 1px solid blue;
|
||||
* }
|
||||
*
|
||||
* .prop-note {
|
||||
* font-family: Monospace;
|
||||
* }
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*/
|
||||
|
||||
/** @ngdoc directive
|
||||
* @name ngOn
|
||||
* @restrict A
|
||||
* @element ANY
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* ```html
|
||||
* <ANY ng-on-eventname="expression">
|
||||
* </ANY>
|
||||
* ```
|
||||
*
|
||||
* or with uppercase letters in property (e.g. "eventName"):
|
||||
*
|
||||
*
|
||||
* ```html
|
||||
* <ANY ng-on-event_name="expression">
|
||||
* </ANY>
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* The `ngOn` directive adds an event listener to a DOM element via
|
||||
* {@link angular.element angular.element().on()}, and evaluates an expression when the event is
|
||||
* fired.
|
||||
* `ngOn` allows adding listeners for arbitrary events by including
|
||||
* the event name in the attribute, e.g. `ng-on-drop="onDrop()"` executes the 'onDrop()' expression
|
||||
* when the `drop` event is fired.
|
||||
*
|
||||
* AngularJS provides specific directives for many events, such as {@link ngClick}, so in most
|
||||
* cases it is not necessary to use `ngOn`. However, AngularJS does not support all events
|
||||
* (e.g. the `drop` event in the example above), and new events might be introduced in later DOM
|
||||
* standards.
|
||||
*
|
||||
* Another use-case for `ngOn` is listening to
|
||||
* [custom events](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events)
|
||||
* fired by
|
||||
* [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements).
|
||||
*
|
||||
* ## Binding to camelCase properties
|
||||
*
|
||||
* Since HTML attributes are case-insensitive, camelCase properties like `myEvent` must be escaped.
|
||||
* AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
|
||||
* `myEvent` must be written as `ng-on-my_event="expression"`.
|
||||
*
|
||||
* @example
|
||||
* ### Bind to built-in DOM events
|
||||
*
|
||||
* <example name="ngOn" module="exampleNgOn">
|
||||
* <file name="app.js">
|
||||
* angular.module('exampleNgOn', [])
|
||||
* .component('main', {
|
||||
* templateUrl: 'main.html',
|
||||
* controller: function() {
|
||||
* this.clickCount = 0;
|
||||
* this.mouseoverCount = 0;
|
||||
*
|
||||
* this.loadingState = 0;
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="main.html">
|
||||
* <div>
|
||||
* This is equivalent to `ngClick` and `ngMouseover`:<br>
|
||||
* <button
|
||||
* ng-on-click="$ctrl.clickCount = $ctrl.clickCount + 1"
|
||||
* ng-on-mouseover="$ctrl.mouseoverCount = $ctrl.mouseoverCount + 1">Click or mouseover</button><br>
|
||||
* clickCount: {{$ctrl.clickCount}}<br>
|
||||
* mouseover: {{$ctrl.mouseoverCount}}
|
||||
*
|
||||
* <hr>
|
||||
*
|
||||
* For the `error` and `load` event on images no built-in AngularJS directives exist:<br>
|
||||
* <img src="thisimagedoesnotexist.png" ng-on-error="$ctrl.loadingState = -1" ng-on-load="$ctrl.loadingState = 1"><br>
|
||||
* <div ng-switch="$ctrl.loadingState">
|
||||
* <span ng-switch-when="0">Image is loading</span>
|
||||
* <span ng-switch-when="-1">Image load error</span>
|
||||
* <span ng-switch-when="1">Image loaded successfully</span>
|
||||
* </div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <main></main>
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ### Bind to custom DOM events
|
||||
*
|
||||
* <example name="ngOnCustom" module="exampleNgOn">
|
||||
* <file name="app.js">
|
||||
* angular.module('exampleNgOn', [])
|
||||
* .component('main', {
|
||||
* templateUrl: 'main.html',
|
||||
* controller: function() {
|
||||
* this.eventLog = '';
|
||||
*
|
||||
* this.listener = function($event) {
|
||||
* this.eventLog = 'Event with type "' + $event.type + '" fired at ' + $event.detail;
|
||||
* };
|
||||
* }
|
||||
* })
|
||||
* .component('childComponent', {
|
||||
* templateUrl: 'child.html',
|
||||
* controller: function($element) {
|
||||
* this.fireEvent = function() {
|
||||
* var event = new CustomEvent('customtype', { detail: new Date()});
|
||||
*
|
||||
* $element[0].dispatchEvent(event);
|
||||
* };
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="main.html">
|
||||
* <child-component ng-on-customtype="$ctrl.listener($event)"></child-component><br>
|
||||
* <span>Event log: {{$ctrl.eventLog}}</span>
|
||||
* </file>
|
||||
* <file name="child.html">
|
||||
<button ng-click="$ctrl.fireEvent()">Fire custom event</button>
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <main></main>
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
|
||||
var $compileMinErr = minErr('$compile');
|
||||
|
||||
function UNINITIALIZED_VALUE() {}
|
||||
@@ -1587,6 +1905,91 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return cssClassDirectivesEnabledConfig;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The security context of DOM Properties.
|
||||
* @private
|
||||
*/
|
||||
var PROP_CONTEXTS = createMap();
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compileProvider#addPropertySecurityContext
|
||||
* @description
|
||||
*
|
||||
* Defines the security context for DOM properties bound by ng-prop-*.
|
||||
*
|
||||
* @param {string} elementName The element name or '*' to match any element.
|
||||
* @param {string} propertyName The DOM property name.
|
||||
* @param {string} ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL`
|
||||
* @returns {object} `this` for chaining
|
||||
*/
|
||||
this.addPropertySecurityContext = function(elementName, propertyName, ctx) {
|
||||
var key = (elementName.toLowerCase() + '|' + propertyName.toLowerCase());
|
||||
|
||||
if (key in PROP_CONTEXTS && PROP_CONTEXTS[key] !== ctx) {
|
||||
throw $compileMinErr('ctxoverride', 'Property context \'{0}.{1}\' already set to \'{2}\', cannot override to \'{3}\'.', elementName, propertyName, PROP_CONTEXTS[key], ctx);
|
||||
}
|
||||
|
||||
PROP_CONTEXTS[key] = ctx;
|
||||
return this;
|
||||
};
|
||||
|
||||
/* Default property contexts.
|
||||
*
|
||||
* Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58
|
||||
* Changing:
|
||||
* - SecurityContext.* => SCE_CONTEXTS/$sce.*
|
||||
* - STYLE => CSS
|
||||
* - various URL => MEDIA_URL
|
||||
* - *|formAction, form|action URL => RESOURCE_URL (like the attribute)
|
||||
*/
|
||||
(function registerNativePropertyContexts() {
|
||||
function registerContext(ctx, values) {
|
||||
forEach(values, function(v) { PROP_CONTEXTS[v.toLowerCase()] = ctx; });
|
||||
}
|
||||
|
||||
registerContext(SCE_CONTEXTS.HTML, [
|
||||
'iframe|srcdoc',
|
||||
'*|innerHTML',
|
||||
'*|outerHTML'
|
||||
]);
|
||||
registerContext(SCE_CONTEXTS.CSS, ['*|style']);
|
||||
registerContext(SCE_CONTEXTS.URL, [
|
||||
'area|href', 'area|ping',
|
||||
'a|href', 'a|ping',
|
||||
'blockquote|cite',
|
||||
'body|background',
|
||||
'del|cite',
|
||||
'input|src',
|
||||
'ins|cite',
|
||||
'q|cite'
|
||||
]);
|
||||
registerContext(SCE_CONTEXTS.MEDIA_URL, [
|
||||
'audio|src',
|
||||
'img|src', 'img|srcset',
|
||||
'source|src', 'source|srcset',
|
||||
'track|src',
|
||||
'video|src', 'video|poster'
|
||||
]);
|
||||
registerContext(SCE_CONTEXTS.RESOURCE_URL, [
|
||||
'*|formAction',
|
||||
'applet|code', 'applet|codebase',
|
||||
'base|href',
|
||||
'embed|src',
|
||||
'frame|src',
|
||||
'form|action',
|
||||
'head|profile',
|
||||
'html|manifest',
|
||||
'iframe|src',
|
||||
'link|href',
|
||||
'media|src',
|
||||
'object|codebase', 'object|data',
|
||||
'script|src'
|
||||
]);
|
||||
})();
|
||||
|
||||
|
||||
this.$get = [
|
||||
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
|
||||
'$controller', '$rootScope', '$sce', '$animate',
|
||||
@@ -1632,6 +2035,57 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
|
||||
function sanitizeSrcset(value, invokeType) {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if (!isString(value)) {
|
||||
throw $compileMinErr('srcset', 'Can\'t pass trusted values to `{0}`: "{1}"', invokeType, value.toString());
|
||||
}
|
||||
|
||||
// Such values are a bit too complex to handle automatically inside $sce.
|
||||
// Instead, we sanitize each of the URIs individually, which works, even dynamically.
|
||||
|
||||
// It's not possible to work around this using `$sce.trustAsMediaUrl`.
|
||||
// If you want to programmatically set explicitly trusted unsafe URLs, you should use
|
||||
// `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the
|
||||
// `ng-bind-html` directive.
|
||||
|
||||
var result = '';
|
||||
|
||||
// first check if there are spaces because it's not the same pattern
|
||||
var trimmedSrcset = trim(value);
|
||||
// ( 999x ,| 999w ,| ,|, )
|
||||
var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
|
||||
var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
|
||||
|
||||
// split srcset into tuple of uri and descriptor except for the last item
|
||||
var rawUris = trimmedSrcset.split(pattern);
|
||||
|
||||
// for each tuples
|
||||
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
|
||||
for (var i = 0; i < nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i * 2;
|
||||
// sanitize the uri
|
||||
result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx]));
|
||||
// add the descriptor
|
||||
result += ' ' + trim(rawUris[innerIdx + 1]);
|
||||
}
|
||||
|
||||
// split the last item into uri and descriptor
|
||||
var lastTuple = trim(rawUris[i * 2]).split(/\s/);
|
||||
|
||||
// sanitize the last uri
|
||||
result += $sce.getTrustedMediaUrl(trim(lastTuple[0]));
|
||||
|
||||
// and add the last descriptor if any
|
||||
if (lastTuple.length === 2) {
|
||||
result += (' ' + trim(lastTuple[1]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function Attributes(element, attributesToCopy) {
|
||||
if (attributesToCopy) {
|
||||
var keys = Object.keys(attributesToCopy);
|
||||
@@ -1768,51 +2222,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
nodeName = nodeName_(this.$$element);
|
||||
|
||||
// Sanitize img[srcset] values.
|
||||
if (nodeName === 'img' && key === 'srcset' && value) {
|
||||
if (!isString(value)) {
|
||||
throw $compileMinErr('srcset', 'Can\'t pass trusted values to `$set(\'srcset\', value)`: "{0}"', value.toString());
|
||||
}
|
||||
|
||||
// Such values are a bit too complex to handle automatically inside $sce.
|
||||
// Instead, we sanitize each of the URIs individually, which works, even dynamically.
|
||||
|
||||
// It's not possible to work around this using `$sce.trustAsMediaUrl`.
|
||||
// If you want to programmatically set explicitly trusted unsafe URLs, you should use
|
||||
// `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the
|
||||
// `ng-bind-html` directive.
|
||||
|
||||
var result = '';
|
||||
|
||||
// first check if there are spaces because it's not the same pattern
|
||||
var trimmedSrcset = trim(value);
|
||||
// ( 999x ,| 999w ,| ,|, )
|
||||
var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
|
||||
var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
|
||||
|
||||
// split srcset into tuple of uri and descriptor except for the last item
|
||||
var rawUris = trimmedSrcset.split(pattern);
|
||||
|
||||
// for each tuples
|
||||
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
|
||||
for (var i = 0; i < nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i * 2;
|
||||
// sanitize the uri
|
||||
result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx]));
|
||||
// add the descriptor
|
||||
result += ' ' + trim(rawUris[innerIdx + 1]);
|
||||
}
|
||||
|
||||
// split the last item into uri and descriptor
|
||||
var lastTuple = trim(rawUris[i * 2]).split(/\s/);
|
||||
|
||||
// sanitize the last uri
|
||||
result += $sce.getTrustedMediaUrl(trim(lastTuple[0]));
|
||||
|
||||
// and add the last descriptor if any
|
||||
if (lastTuple.length === 2) {
|
||||
result += (' ' + trim(lastTuple[1]));
|
||||
}
|
||||
this[key] = value = result;
|
||||
if (nodeName === 'img' && key === 'srcset') {
|
||||
this[key] = value = sanitizeSrcset(value, '$set(\'srcset\', value)');
|
||||
}
|
||||
|
||||
if (writeAttr !== false) {
|
||||
@@ -1820,7 +2231,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
if (SIMPLE_ATTR_NAME.test(attrName)) {
|
||||
this.$$element.attr(attrName, value);
|
||||
// jQuery skips special boolean attrs treatment in XML nodes for
|
||||
// historical reasons and hence AngularJS cannot freely call
|
||||
// `.attr(attrName, false) with such attributes. To avoid issues
|
||||
// in XHTML, call `removeAttr` in such cases instead.
|
||||
// See https://github.com/jquery/jquery/issues/4249
|
||||
if (booleanKey && value === false) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
this.$$element.attr(attrName, value);
|
||||
}
|
||||
} else {
|
||||
setSpecialAttr(this.$$element[0], attrName, value);
|
||||
}
|
||||
@@ -1909,7 +2329,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
: function denormalizeTemplate(template) {
|
||||
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
|
||||
},
|
||||
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
|
||||
NG_PREFIX_BINDING = /^ng(Attr|Prop|On)([A-Z].*)$/;
|
||||
var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
|
||||
|
||||
compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
|
||||
@@ -2245,43 +2665,66 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective);
|
||||
|
||||
// iterate over the attributes
|
||||
for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
|
||||
for (var attr, name, nName, value, ngPrefixMatch, nAttrs = node.attributes,
|
||||
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
|
||||
var attrStartName = false;
|
||||
var attrEndName = false;
|
||||
|
||||
var isNgAttr = false, isNgProp = false, isNgEvent = false;
|
||||
var multiElementMatch;
|
||||
|
||||
attr = nAttrs[j];
|
||||
name = attr.name;
|
||||
value = attr.value;
|
||||
|
||||
// support ngAttr attribute binding
|
||||
ngAttrName = directiveNormalize(name);
|
||||
isNgAttr = NG_ATTR_BINDING.test(ngAttrName);
|
||||
if (isNgAttr) {
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
|
||||
// Support ng-attr-*, ng-prop-* and ng-on-*
|
||||
if ((ngPrefixMatch = nName.match(NG_PREFIX_BINDING))) {
|
||||
isNgAttr = ngPrefixMatch[1] === 'Attr';
|
||||
isNgProp = ngPrefixMatch[1] === 'Prop';
|
||||
isNgEvent = ngPrefixMatch[1] === 'On';
|
||||
|
||||
// Normalize the non-prefixed name
|
||||
name = name.replace(PREFIX_REGEXP, '')
|
||||
.substr(8).replace(/_(.)/g, function(match, letter) {
|
||||
.toLowerCase()
|
||||
.substr(4 + ngPrefixMatch[1].length).replace(/_(.)/g, function(match, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
|
||||
if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
|
||||
// Support *-start / *-end multi element directives
|
||||
} else if ((multiElementMatch = nName.match(MULTI_ELEMENT_DIR_RE)) && directiveIsMultiElement(multiElementMatch[1])) {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
attrsMap[nName] = name;
|
||||
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
|
||||
if (isNgProp || isNgEvent) {
|
||||
attrs[nName] = value;
|
||||
attrsMap[nName] = attr.name;
|
||||
|
||||
if (isNgProp) {
|
||||
addPropertyDirective(node, directives, nName, name);
|
||||
} else {
|
||||
addEventDirective(directives, nName, name);
|
||||
}
|
||||
} else {
|
||||
// Update nName for cases where a prefix was removed
|
||||
// NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-*
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
attrsMap[nName] = name;
|
||||
|
||||
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
|
||||
attrs[nName] = value;
|
||||
if (getBooleanAttrName(node, nName)) {
|
||||
attrs[nName] = true; // presence means true
|
||||
}
|
||||
}
|
||||
|
||||
addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
|
||||
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
|
||||
attrEndName);
|
||||
}
|
||||
addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
|
||||
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
|
||||
attrEndName);
|
||||
}
|
||||
|
||||
if (nodeName === 'input' && node.getAttribute('type') === 'hidden') {
|
||||
@@ -2575,7 +3018,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// We have transclusion slots,
|
||||
// collect them up, compile them and store their transclusion functions
|
||||
$template = [];
|
||||
$template = window.document.createDocumentFragment();
|
||||
|
||||
var slotMap = createMap();
|
||||
var filledSlots = createMap();
|
||||
@@ -2603,10 +3046,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var slotName = slotMap[directiveNormalize(nodeName_(node))];
|
||||
if (slotName) {
|
||||
filledSlots[slotName] = true;
|
||||
slots[slotName] = slots[slotName] || [];
|
||||
slots[slotName].push(node);
|
||||
slots[slotName] = slots[slotName] || window.document.createDocumentFragment();
|
||||
slots[slotName].appendChild(node);
|
||||
} else {
|
||||
$template.push(node);
|
||||
$template.appendChild(node);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2620,9 +3063,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
for (var slotName in slots) {
|
||||
if (slots[slotName]) {
|
||||
// Only define a transclusion function if the slot was filled
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
|
||||
var slotCompileNodes = jqLite(slots[slotName].childNodes);
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slotCompileNodes, transcludeFn);
|
||||
}
|
||||
}
|
||||
|
||||
$template = jqLite($template.childNodes);
|
||||
}
|
||||
|
||||
$compileNode.empty(); // clear contents
|
||||
@@ -2958,7 +3404,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
if (!value) {
|
||||
var dataName = '$' + name + 'Controller';
|
||||
value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
|
||||
|
||||
if (inheritType === '^^' && $element[0] && $element[0].nodeType === NODE_TYPE_DOCUMENT) {
|
||||
// inheritedData() uses the documentElement when it finds the document, so we would
|
||||
// require from the element itself.
|
||||
value = null;
|
||||
} else {
|
||||
value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!value && !optional) {
|
||||
@@ -3315,42 +3768,95 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
|
||||
function getTrustedContext(node, attrNormalizedName) {
|
||||
function getTrustedAttrContext(nodeName, attrNormalizedName) {
|
||||
if (attrNormalizedName === 'srcdoc') {
|
||||
return $sce.HTML;
|
||||
}
|
||||
var tag = nodeName_(node);
|
||||
// All tags with src attributes require a RESOURCE_URL value, except for
|
||||
// img and various html5 media tags, which require the MEDIA_URL context.
|
||||
// All nodes with src attributes require a RESOURCE_URL value, except for
|
||||
// img and various html5 media nodes, which require the MEDIA_URL context.
|
||||
if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
|
||||
if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) {
|
||||
if (['img', 'video', 'audio', 'source', 'track'].indexOf(nodeName) === -1) {
|
||||
return $sce.RESOURCE_URL;
|
||||
}
|
||||
return $sce.MEDIA_URL;
|
||||
} else if (attrNormalizedName === 'xlinkHref') {
|
||||
// Some xlink:href are okay, most aren't
|
||||
if (tag === 'image') return $sce.MEDIA_URL;
|
||||
if (tag === 'a') return $sce.URL;
|
||||
if (nodeName === 'image') return $sce.MEDIA_URL;
|
||||
if (nodeName === 'a') return $sce.URL;
|
||||
return $sce.RESOURCE_URL;
|
||||
} else if (
|
||||
// Formaction
|
||||
(tag === 'form' && attrNormalizedName === 'action') ||
|
||||
(nodeName === 'form' && attrNormalizedName === 'action') ||
|
||||
// If relative URLs can go where they are not expected to, then
|
||||
// all sorts of trust issues can arise.
|
||||
(tag === 'base' && attrNormalizedName === 'href') ||
|
||||
(nodeName === 'base' && attrNormalizedName === 'href') ||
|
||||
// links can be stylesheets or imports, which can run script in the current origin
|
||||
(tag === 'link' && attrNormalizedName === 'href')
|
||||
(nodeName === 'link' && attrNormalizedName === 'href')
|
||||
) {
|
||||
return $sce.RESOURCE_URL;
|
||||
} else if (tag === 'a' && (attrNormalizedName === 'href' ||
|
||||
} else if (nodeName === 'a' && (attrNormalizedName === 'href' ||
|
||||
attrNormalizedName === 'ngHref')) {
|
||||
return $sce.URL;
|
||||
}
|
||||
}
|
||||
|
||||
function getTrustedPropContext(nodeName, propNormalizedName) {
|
||||
var prop = propNormalizedName.toLowerCase();
|
||||
return PROP_CONTEXTS[nodeName + '|' + prop] || PROP_CONTEXTS['*|' + prop];
|
||||
}
|
||||
|
||||
function sanitizeSrcsetPropertyValue(value) {
|
||||
return sanitizeSrcset($sce.valueOf(value), 'ng-prop-srcset');
|
||||
}
|
||||
function addPropertyDirective(node, directives, attrName, propName) {
|
||||
if (EVENT_HANDLER_ATTR_REGEXP.test(propName)) {
|
||||
throw $compileMinErr('nodomevents', 'Property bindings for HTML DOM event properties are disallowed');
|
||||
}
|
||||
|
||||
var nodeName = nodeName_(node);
|
||||
var trustedContext = getTrustedPropContext(nodeName, propName);
|
||||
|
||||
var sanitizer = identity;
|
||||
// Sanitize img[srcset] + source[srcset] values.
|
||||
if (propName === 'srcset' && (nodeName === 'img' || nodeName === 'source')) {
|
||||
sanitizer = sanitizeSrcsetPropertyValue;
|
||||
} else if (trustedContext) {
|
||||
sanitizer = $sce.getTrusted.bind($sce, trustedContext);
|
||||
}
|
||||
|
||||
directives.push({
|
||||
priority: 100,
|
||||
compile: function ngPropCompileFn(_, attr) {
|
||||
var ngPropGetter = $parse(attr[attrName]);
|
||||
var ngPropWatch = $parse(attr[attrName], function sceValueOf(val) {
|
||||
// Unwrap the value to compare the actual inner safe value, not the wrapper object.
|
||||
return $sce.valueOf(val);
|
||||
});
|
||||
|
||||
return {
|
||||
pre: function ngPropPreLinkFn(scope, $element) {
|
||||
function applyPropValue() {
|
||||
var propValue = ngPropGetter(scope);
|
||||
$element[0][propName] = sanitizer(propValue);
|
||||
}
|
||||
|
||||
applyPropValue();
|
||||
scope.$watch(ngPropWatch, applyPropValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addEventDirective(directives, attrName, eventName) {
|
||||
directives.push(
|
||||
createEventDirective($parse, $rootScope, $exceptionHandler, attrName, eventName, /*forceAsync=*/false)
|
||||
);
|
||||
}
|
||||
|
||||
function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
|
||||
var trustedContext = getTrustedContext(node, name);
|
||||
var nodeName = nodeName_(node);
|
||||
var trustedContext = getTrustedAttrContext(nodeName, name);
|
||||
var mustHaveExpression = !isNgAttr;
|
||||
var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
|
||||
|
||||
@@ -3359,16 +3865,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// no interpolation found -> ignore
|
||||
if (!interpolateFn) return;
|
||||
|
||||
if (name === 'multiple' && nodeName_(node) === 'select') {
|
||||
if (name === 'multiple' && nodeName === 'select') {
|
||||
throw $compileMinErr('selmulti',
|
||||
'Binding to the \'multiple\' attribute is not supported. Element: {0}',
|
||||
startingTag(node));
|
||||
}
|
||||
|
||||
if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
|
||||
throw $compileMinErr('nodomevents',
|
||||
'Interpolations for HTML DOM event attributes are disallowed. Please use the ' +
|
||||
'ng- versions (such as ng-click instead of onclick) instead.');
|
||||
throw $compileMinErr('nodomevents', 'Interpolations for HTML DOM event attributes are disallowed');
|
||||
}
|
||||
|
||||
directives.push({
|
||||
|
||||
@@ -181,7 +181,6 @@
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
* @element INPUT
|
||||
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
|
||||
* then the `disabled` attribute will be set on the element
|
||||
*/
|
||||
@@ -408,7 +407,7 @@ forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
|
||||
// ng-src, ng-srcset, ng-href are interpolated
|
||||
forEach(['src', 'srcset', 'href'], function(attrName) {
|
||||
var normalized = directiveNormalize('ng-' + attrName);
|
||||
ngAttributeAliasDirectives[normalized] = function() {
|
||||
ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) {
|
||||
return {
|
||||
priority: 99, // it needs to run after the attributes are interpolated
|
||||
link: function(scope, element, attr) {
|
||||
@@ -422,6 +421,10 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
|
||||
propName = null;
|
||||
}
|
||||
|
||||
// We need to sanitize the url at least once, in case it is a constant
|
||||
// non-interpolated attribute.
|
||||
attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized]));
|
||||
|
||||
attr.$observe(normalized, function(value) {
|
||||
if (!value) {
|
||||
if (attrName === 'href') {
|
||||
@@ -441,5 +444,5 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
var nullFormCtrl = {
|
||||
$addControl: noop,
|
||||
$getControls: valueFn([]),
|
||||
$$renameControl: nullFormRenameControl,
|
||||
$removeControl: noop,
|
||||
$setValidity: noop,
|
||||
@@ -159,6 +160,30 @@ FormController.prototype = {
|
||||
control.$$parentForm = this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$getControls
|
||||
* @returns {Array} the controls that are currently part of this form
|
||||
*
|
||||
* @description
|
||||
* This method returns a **shallow copy** of the controls that are currently part of this form.
|
||||
* The controls can be instances of {@link form.FormController `FormController`}
|
||||
* ({@link ngForm "child-forms"}) and of {@link ngModel.NgModelController `NgModelController`}.
|
||||
* If you need access to the controls of child-forms, you have to call `$getControls()`
|
||||
* recursively on them.
|
||||
* This can be used for example to iterate over all controls to validate them.
|
||||
*
|
||||
* The controls can be accessed normally, but adding to, or removing controls from the array has
|
||||
* no effect on the form. Instead, use {@link form.FormController#$addControl `$addControl()`} and
|
||||
* {@link form.FormController#$removeControl `$removeControl()`} for this use-case.
|
||||
* Likewise, adding a control to, or removing a control from the form is not reflected
|
||||
* in the shallow copy. That means you should get a fresh copy from `$getControls()` every time
|
||||
* you need access to the controls.
|
||||
*/
|
||||
$getControls: function() {
|
||||
return shallowCopy(this.$$controls);
|
||||
},
|
||||
|
||||
// Private API: rename a form control
|
||||
$$renameControl: function(control, newName) {
|
||||
var oldName = control.$name;
|
||||
|
||||
+148
-33
@@ -255,6 +255,10 @@ var inputType = {
|
||||
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
||||
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
||||
*
|
||||
* The format of the displayed time can be adjusted with the
|
||||
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
|
||||
* and `timeStripZeroSeconds`.
|
||||
*
|
||||
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
@@ -356,7 +360,12 @@ var inputType = {
|
||||
* Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
|
||||
*
|
||||
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
||||
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
||||
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default,
|
||||
* this is the timezone of the browser.
|
||||
*
|
||||
* The format of the displayed time can be adjusted with the
|
||||
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
|
||||
* and `timeStripZeroSeconds`.
|
||||
*
|
||||
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
@@ -900,8 +909,10 @@ var inputType = {
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
|
||||
* used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
|
||||
* use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
|
||||
* used in Chromium, which may not fulfill your app's requirements.
|
||||
* If you need stricter (e.g. requiring a top-level domain), or more relaxed validation
|
||||
* (e.g. allowing IPv6 address literals) you can use `ng-pattern` or
|
||||
* modify the built-in validators (see the {@link guide/forms Forms guide}).
|
||||
* </div>
|
||||
*
|
||||
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
||||
@@ -1488,9 +1499,11 @@ function createDateParser(regexp, mapping) {
|
||||
}
|
||||
|
||||
function createDateInputType(type, regexp, parseDate, format) {
|
||||
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
|
||||
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
|
||||
badInputChecker(scope, element, attr, ctrl, type);
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
var isTimeType = type === 'time' || type === 'datetimelocal';
|
||||
var previousDate;
|
||||
var previousTimezone;
|
||||
|
||||
@@ -1514,11 +1527,13 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
if (isValidDate(value)) {
|
||||
previousDate = value;
|
||||
var timezone = ctrl.$options.getOption('timezone');
|
||||
|
||||
if (timezone) {
|
||||
previousTimezone = timezone;
|
||||
previousDate = convertTimezoneToLocal(previousDate, timezone, true);
|
||||
}
|
||||
return $filter('date')(value, format, timezone);
|
||||
|
||||
return formatter(value, timezone);
|
||||
} else {
|
||||
previousDate = null;
|
||||
previousTimezone = null;
|
||||
@@ -1527,24 +1542,34 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
});
|
||||
|
||||
if (isDefined(attr.min) || attr.ngMin) {
|
||||
var minVal;
|
||||
var minVal = attr.min || $parse(attr.ngMin)(scope);
|
||||
var parsedMinVal = parseObservedDateValue(minVal);
|
||||
|
||||
ctrl.$validators.min = function(value) {
|
||||
return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
|
||||
return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal;
|
||||
};
|
||||
attr.$observe('min', function(val) {
|
||||
minVal = parseObservedDateValue(val);
|
||||
ctrl.$validate();
|
||||
if (val !== minVal) {
|
||||
parsedMinVal = parseObservedDateValue(val);
|
||||
minVal = val;
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(attr.max) || attr.ngMax) {
|
||||
var maxVal;
|
||||
var maxVal = attr.max || $parse(attr.ngMax)(scope);
|
||||
var parsedMaxVal = parseObservedDateValue(maxVal);
|
||||
|
||||
ctrl.$validators.max = function(value) {
|
||||
return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
|
||||
return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal;
|
||||
};
|
||||
attr.$observe('max', function(val) {
|
||||
maxVal = parseObservedDateValue(val);
|
||||
ctrl.$validate();
|
||||
if (val !== maxVal) {
|
||||
parsedMaxVal = parseObservedDateValue(val);
|
||||
maxVal = val;
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1573,6 +1598,24 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
}
|
||||
return parsedDate;
|
||||
}
|
||||
|
||||
function formatter(value, timezone) {
|
||||
var targetFormat = format;
|
||||
|
||||
if (isTimeType && isString(ctrl.$options.getOption('timeSecondsFormat'))) {
|
||||
targetFormat = format
|
||||
.replace('ss.sss', ctrl.$options.getOption('timeSecondsFormat'))
|
||||
.replace(/:$/, '');
|
||||
}
|
||||
|
||||
var formatted = $filter('date')(value, targetFormat, timezone);
|
||||
|
||||
if (isTimeType && ctrl.$options.getOption('timeStripZeroSeconds')) {
|
||||
formatted = formatted.replace(/(?::00)?(?:\.000)?$/, '');
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1678,50 +1721,68 @@ function isValidForStep(viewValue, stepBase, step) {
|
||||
return (value - stepBase) % step === 0;
|
||||
}
|
||||
|
||||
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
|
||||
badInputChecker(scope, element, attr, ctrl, 'number');
|
||||
numberFormatterParser(ctrl);
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
var minVal;
|
||||
var maxVal;
|
||||
var parsedMinVal;
|
||||
|
||||
if (isDefined(attr.min) || attr.ngMin) {
|
||||
var minVal = attr.min || $parse(attr.ngMin)(scope);
|
||||
parsedMinVal = parseNumberAttrVal(minVal);
|
||||
|
||||
ctrl.$validators.min = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal;
|
||||
};
|
||||
|
||||
attr.$observe('min', function(val) {
|
||||
minVal = parseNumberAttrVal(val);
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
if (val !== minVal) {
|
||||
parsedMinVal = parseNumberAttrVal(val);
|
||||
minVal = val;
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(attr.max) || attr.ngMax) {
|
||||
var maxVal = attr.max || $parse(attr.ngMax)(scope);
|
||||
var parsedMaxVal = parseNumberAttrVal(maxVal);
|
||||
|
||||
ctrl.$validators.max = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal;
|
||||
};
|
||||
|
||||
attr.$observe('max', function(val) {
|
||||
maxVal = parseNumberAttrVal(val);
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
if (val !== maxVal) {
|
||||
parsedMaxVal = parseNumberAttrVal(val);
|
||||
maxVal = val;
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(attr.step) || attr.ngStep) {
|
||||
var stepVal;
|
||||
var stepVal = attr.step || $parse(attr.ngStep)(scope);
|
||||
var parsedStepVal = parseNumberAttrVal(stepVal);
|
||||
|
||||
ctrl.$validators.step = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
|
||||
isValidForStep(viewValue, minVal || 0, stepVal);
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) ||
|
||||
isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal);
|
||||
};
|
||||
|
||||
attr.$observe('step', function(val) {
|
||||
stepVal = parseNumberAttrVal(val);
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
if (val !== stepVal) {
|
||||
parsedStepVal = parseNumberAttrVal(val);
|
||||
stepVal = val;
|
||||
ctrl.$validate();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1751,6 +1812,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
originalRender;
|
||||
|
||||
if (hasMinAttr) {
|
||||
minVal = parseNumberAttrVal(attr.min);
|
||||
|
||||
ctrl.$validators.min = supportsRange ?
|
||||
// Since all browsers set the input to a valid value, we don't need to check validity
|
||||
function noopMinValidator() { return true; } :
|
||||
@@ -1763,6 +1826,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
if (hasMaxAttr) {
|
||||
maxVal = parseNumberAttrVal(attr.max);
|
||||
|
||||
ctrl.$validators.max = supportsRange ?
|
||||
// Since all browsers set the input to a valid value, we don't need to check validity
|
||||
function noopMaxValidator() { return true; } :
|
||||
@@ -1775,6 +1840,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
if (hasStepAttr) {
|
||||
stepVal = parseNumberAttrVal(attr.step);
|
||||
|
||||
ctrl.$validators.step = supportsRange ?
|
||||
function nativeStepValidator() {
|
||||
// Currently, only FF implements the spec on step change correctly (i.e. adjusting the
|
||||
@@ -1796,7 +1863,13 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
// attribute value when the input is first rendered, so that the browser can adjust the
|
||||
// input value based on the min/max value
|
||||
element.attr(htmlAttrName, attr[htmlAttrName]);
|
||||
attr.$observe(htmlAttrName, changeFn);
|
||||
var oldVal = attr[htmlAttrName];
|
||||
attr.$observe(htmlAttrName, function wrappedObserver(val) {
|
||||
if (val !== oldVal) {
|
||||
oldVal = val;
|
||||
changeFn(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function minChange(val) {
|
||||
@@ -1850,11 +1923,11 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
// Some browsers don't adjust the input value correctly, but set the stepMismatch error
|
||||
if (supportsRange && ctrl.$viewValue !== element.val()) {
|
||||
ctrl.$setViewValue(element.val());
|
||||
} else {
|
||||
if (!supportsRange) {
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
} else if (ctrl.$viewValue !== element.val()) {
|
||||
ctrl.$setViewValue(element.val());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2162,6 +2235,48 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
|
||||
}];
|
||||
|
||||
|
||||
var hiddenInputBrowserCacheDirective = function() {
|
||||
var valueProperty = {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
get: function() {
|
||||
return this.getAttribute('value') || '';
|
||||
},
|
||||
set: function(val) {
|
||||
this.setAttribute('value', val);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
priority: 200,
|
||||
compile: function(_, attr) {
|
||||
if (lowercase(attr.type) !== 'hidden') {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
pre: function(scope, element, attr, ctrls) {
|
||||
var node = element[0];
|
||||
|
||||
// Support: Edge
|
||||
// Moving the DOM around prevents autofillling
|
||||
if (node.parentNode) {
|
||||
node.parentNode.insertBefore(node, node.nextSibling);
|
||||
}
|
||||
|
||||
// Support: FF, IE
|
||||
// Avoiding direct assignment to .value prevents autofillling
|
||||
if (Object.defineProperty) {
|
||||
Object.defineProperty(node, 'value', valueProperty);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
|
||||
/**
|
||||
|
||||
@@ -125,6 +125,8 @@ function classDirective(name, selector) {
|
||||
}
|
||||
|
||||
function toClassString(classValue) {
|
||||
if (!classValue) return classValue;
|
||||
|
||||
var classString = classValue;
|
||||
|
||||
if (isArray(classValue)) {
|
||||
@@ -133,6 +135,8 @@ function classDirective(name, selector) {
|
||||
classString = Object.keys(classValue).
|
||||
filter(function(key) { return classValue[key]; }).
|
||||
join(' ');
|
||||
} else if (!isString(classValue)) {
|
||||
classString = classValue + '';
|
||||
}
|
||||
|
||||
return classString;
|
||||
@@ -178,6 +182,7 @@ function classDirective(name, selector) {
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link ng.$animate#addClass addClass} | just before the class is applied to the element |
|
||||
* | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
|
||||
* | {@link ng.$animate#setClass setClass} | just before classes are added and classes are removed from the element at the same time |
|
||||
*
|
||||
* ### ngClass and pre-existing CSS3 Transitions/Animations
|
||||
The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
|
||||
|
||||
@@ -50,33 +50,44 @@ forEach(
|
||||
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
|
||||
function(eventName) {
|
||||
var directiveName = directiveNormalize('ng-' + eventName);
|
||||
ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function($element, attr) {
|
||||
// NOTE:
|
||||
// We expose the powerful `$event` object on the scope that provides access to the Window,
|
||||
// etc. This is OK, because expressions are not sandboxed any more (and the expression
|
||||
// sandbox was never meant to be a security feature anyway).
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
fn(scope, {$event: event});
|
||||
};
|
||||
if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
|
||||
scope.$evalAsync(callback);
|
||||
} else {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) {
|
||||
return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[eventName]);
|
||||
}];
|
||||
}
|
||||
);
|
||||
|
||||
function createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsync) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function($element, attr) {
|
||||
// NOTE:
|
||||
// We expose the powerful `$event` object on the scope that provides access to the Window,
|
||||
// etc. This is OK, because expressions are not sandboxed any more (and the expression
|
||||
// sandbox was never meant to be a security feature anyway).
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
fn(scope, {$event: event});
|
||||
};
|
||||
|
||||
if (!$rootScope.$$phase) {
|
||||
scope.$apply(callback);
|
||||
} else if (forceAsync) {
|
||||
scope.$evalAsync(callback);
|
||||
} else {
|
||||
try {
|
||||
callback();
|
||||
} catch (error) {
|
||||
$exceptionHandler(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngDblclick
|
||||
|
||||
@@ -287,6 +287,7 @@ function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $
|
||||
this.$$currentValidationRunId = 0;
|
||||
|
||||
this.$$scope = $scope;
|
||||
this.$$rootScope = $scope.$root;
|
||||
this.$$attr = $attr;
|
||||
this.$$element = $element;
|
||||
this.$$animate = $animate;
|
||||
@@ -561,6 +562,7 @@ NgModelController.prototype = {
|
||||
* `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
|
||||
*/
|
||||
$validate: function() {
|
||||
|
||||
// ignore $validate before model is initialized
|
||||
if (isNumberNaN(this.$modelValue)) {
|
||||
return;
|
||||
@@ -864,7 +866,7 @@ NgModelController.prototype = {
|
||||
this.$$pendingDebounce = this.$$timeout(function() {
|
||||
that.$commitViewValue();
|
||||
}, debounceDelay);
|
||||
} else if (this.$$scope.$root.$$phase) {
|
||||
} else if (this.$$rootScope.$$phase) {
|
||||
this.$commitViewValue();
|
||||
} else {
|
||||
this.$$scope.$apply(function() {
|
||||
|
||||
@@ -41,7 +41,7 @@ ModelOptions.prototype = {
|
||||
options = extend({}, options);
|
||||
|
||||
// Inherit options from the parent if specified by the value `"$inherit"`
|
||||
forEach(options, /* @this */ function(option, key) {
|
||||
forEach(options, /** @this */ function(option, key) {
|
||||
if (option === '$inherit') {
|
||||
if (key === '*') {
|
||||
inheritAll = true;
|
||||
@@ -406,12 +406,6 @@ defaultModelOptions = new ModelOptions({
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* ## Specifying timezones
|
||||
*
|
||||
* You can specify the timezone that date/time input directives expect by providing its name in the
|
||||
* `timezone` property.
|
||||
*
|
||||
*
|
||||
* ## Programmatically changing options
|
||||
*
|
||||
* The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
|
||||
@@ -423,8 +417,70 @@ defaultModelOptions = new ModelOptions({
|
||||
* Default events, extra triggers, and catch-all debounce values}.
|
||||
*
|
||||
*
|
||||
* ## Specifying timezones
|
||||
*
|
||||
* You can specify the timezone that date/time input directives expect by providing its name in the
|
||||
* `timezone` property.
|
||||
*
|
||||
*
|
||||
* ## Formatting the value of `time` and `datetime-local`
|
||||
*
|
||||
* With the options `timeSecondsFormat` and `timeStripZeroSeconds` it is possible to adjust the value
|
||||
* that is displayed in the control. Note that browsers may apply their own formatting
|
||||
* in the user interface.
|
||||
*
|
||||
<example name="ngModelOptions-time-format" module="timeExample">
|
||||
<file name="index.html">
|
||||
<time-example></time-example>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
angular.module('timeExample', [])
|
||||
.component('timeExample', {
|
||||
templateUrl: 'timeExample.html',
|
||||
controller: function() {
|
||||
this.time = new Date(1970, 0, 1, 14, 57, 0);
|
||||
|
||||
this.options = {
|
||||
timeSecondsFormat: 'ss',
|
||||
timeStripZeroSeconds: true
|
||||
};
|
||||
|
||||
this.optionChange = function() {
|
||||
this.timeForm.timeFormatted.$overrideModelOptions(this.options);
|
||||
this.time = new Date(this.time);
|
||||
};
|
||||
}
|
||||
});
|
||||
</file>
|
||||
<file name="timeExample.html">
|
||||
<form name="$ctrl.timeForm">
|
||||
<strong>Default</strong>:
|
||||
<input type="time" ng-model="$ctrl.time" step="any" /><br>
|
||||
<strong>With options</strong>:
|
||||
<input type="time" name="timeFormatted" ng-model="$ctrl.time" step="any" ng-model-options="$ctrl.options" />
|
||||
<br>
|
||||
|
||||
Options:<br>
|
||||
<code>timeSecondsFormat</code>:
|
||||
<input
|
||||
type="text"
|
||||
ng-model="$ctrl.options.timeSecondsFormat"
|
||||
ng-change="$ctrl.optionChange()">
|
||||
<br>
|
||||
<code>timeStripZeroSeconds</code>:
|
||||
<input
|
||||
type="checkbox"
|
||||
ng-model="$ctrl.options.timeStripZeroSeconds"
|
||||
ng-change="$ctrl.optionChange()">
|
||||
</form>
|
||||
</file>
|
||||
* </example>
|
||||
*
|
||||
* @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
|
||||
* and its descendents. Valid keys are:
|
||||
* and its descendents.
|
||||
*
|
||||
* **General options**:
|
||||
*
|
||||
* - `updateOn`: string specifying which event should the input be bound to. You can set several
|
||||
* events using an space delimited list. There is a special event called `default` that
|
||||
* matches the default events belonging to the control. These are the events that are bound to
|
||||
@@ -457,6 +513,10 @@ defaultModelOptions = new ModelOptions({
|
||||
* not validate correctly instead of the default behavior of setting the model to undefined.
|
||||
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
|
||||
* `ngModel` as getters/setters.
|
||||
*
|
||||
*
|
||||
* **Input-type specific options**:
|
||||
*
|
||||
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
|
||||
* `<input type="date" />`, `<input type="time" />`, ... . It understands UTC/GMT and the
|
||||
* continental US time zone abbreviations, but for general use, use a time zone offset, for
|
||||
@@ -465,6 +525,24 @@ defaultModelOptions = new ModelOptions({
|
||||
* Note that changing the timezone will have no effect on the current date, and is only applied after
|
||||
* the next input / model change.
|
||||
*
|
||||
* - `timeSecondsFormat`: Defines if the `time` and `datetime-local` types should show seconds and
|
||||
* milliseconds. The option follows the format string of {@link date date filter}.
|
||||
* By default, the options is `undefined` which is equal to `'ss.sss'` (seconds and milliseconds).
|
||||
* The other options are `'ss'` (strips milliseconds), and `''` (empty string), which strips both
|
||||
* seconds and milliseconds.
|
||||
* Note that browsers that support `time` and `datetime-local` require the hour and minutes
|
||||
* part of the time string, and may show the value differently in the user interface.
|
||||
* {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
|
||||
*
|
||||
* - `timeStripZeroSeconds`: Defines if the `time` and `datetime-local` types should strip the
|
||||
* seconds and milliseconds from the formatted value if they are zero. This option is applied
|
||||
* after `timeSecondsFormat`.
|
||||
* This option can be used to make the formatting consistent over different browsers, as some
|
||||
* browsers with support for `time` will natively hide the milliseconds and
|
||||
* seconds if they are zero, but others won't, and browsers that don't implement these input
|
||||
* types will always show the full string.
|
||||
* {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
|
||||
*
|
||||
*/
|
||||
var ngModelOptionsDirective = function() {
|
||||
NgModelOptionsController.$inject = ['$attrs', '$scope'];
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngRef
|
||||
* @restrict A
|
||||
*
|
||||
* @description
|
||||
* The `ngRef` attribute tells AngularJS to assign the controller of a component (or a directive)
|
||||
* to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM
|
||||
* element to the scope.
|
||||
*
|
||||
* If the element with `ngRef` is destroyed `null` is assigned to the property.
|
||||
*
|
||||
* Note that if you want to assign from a child into the parent scope, you must initialize the
|
||||
* target property on the parent scope, otherwise `ngRef` will assign on the child scope.
|
||||
* This commonly happens when assigning elements or components wrapped in {@link ngIf} or
|
||||
* {@link ngRepeat}. See the second example below.
|
||||
*
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string} ngRef property name - A valid AngularJS expression identifier to which the
|
||||
* controller or jqlite-wrapped DOM element will be bound.
|
||||
* @param {string=} ngRefRead read value - The name of a directive (or component) on this element,
|
||||
* or the special string `$element`. If a name is provided, `ngRef` will
|
||||
* assign the matching controller. If `$element` is provided, the element
|
||||
* itself is assigned (even if a controller is available).
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ### Simple toggle
|
||||
* This example shows how the controller of the component toggle
|
||||
* is reused in the template through the scope to use its logic.
|
||||
* <example name="ng-ref-component" module="myApp">
|
||||
* <file name="index.html">
|
||||
* <my-toggle ng-ref="myToggle"></my-toggle>
|
||||
* <button ng-click="myToggle.toggle()">Toggle</button>
|
||||
* <div ng-show="myToggle.isOpen()">
|
||||
* You are using a component in the same template to show it.
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.js">
|
||||
* angular.module('myApp', [])
|
||||
* .component('myToggle', {
|
||||
* controller: function ToggleController() {
|
||||
* var opened = false;
|
||||
* this.isOpen = function() { return opened; };
|
||||
* this.toggle = function() { opened = !opened; };
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should publish the toggle into the scope', function() {
|
||||
* var toggle = element(by.buttonText('Toggle'));
|
||||
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(false);
|
||||
* toggle.click();
|
||||
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(true);
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @example
|
||||
* ### ngRef inside scopes
|
||||
* This example shows how `ngRef` works with child scopes. The `ngRepeat`-ed `myWrapper` components
|
||||
* are assigned to the scope of `myRoot`, because the `toggles` property has been initialized.
|
||||
* The repeated `myToggle` components are published to the child scopes created by `ngRepeat`.
|
||||
* `ngIf` behaves similarly - the assignment of `myToggle` happens in the `ngIf` child scope,
|
||||
* because the target property has not been initialized on the `myRoot` component controller.
|
||||
*
|
||||
* <example name="ng-ref-scopes" module="myApp">
|
||||
* <file name="index.html">
|
||||
* <my-root></my-root>
|
||||
* </file>
|
||||
* <file name="index.js">
|
||||
* angular.module('myApp', [])
|
||||
* .component('myRoot', {
|
||||
* templateUrl: 'root.html',
|
||||
* controller: function() {
|
||||
* this.wrappers = []; // initialize the array so that the wrappers are assigned into the parent scope
|
||||
* }
|
||||
* })
|
||||
* .component('myToggle', {
|
||||
* template: '<strong>myToggle</strong><button ng-click="$ctrl.toggle()" ng-transclude></button>',
|
||||
* transclude: true,
|
||||
* controller: function ToggleController() {
|
||||
* var opened = false;
|
||||
* this.isOpen = function() { return opened; };
|
||||
* this.toggle = function() { opened = !opened; };
|
||||
* }
|
||||
* })
|
||||
* .component('myWrapper', {
|
||||
* transclude: true,
|
||||
* template: '<strong>myWrapper</strong>' +
|
||||
* '<div>ngRepeatToggle.isOpen(): {{$ctrl.ngRepeatToggle.isOpen() | json}}</div>' +
|
||||
* '<my-toggle ng-ref="$ctrl.ngRepeatToggle"><ng-transclude></ng-transclude></my-toggle>'
|
||||
* });
|
||||
* </file>
|
||||
* <file name="root.html">
|
||||
* <strong>myRoot</strong>
|
||||
* <my-toggle ng-ref="$ctrl.outerToggle">Outer Toggle</my-toggle>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* <div><em>wrappers assigned to root</em><br>
|
||||
* <div ng-repeat="wrapper in $ctrl.wrappers">
|
||||
* wrapper.ngRepeatToggle.isOpen(): {{wrapper.ngRepeatToggle.isOpen() | json}}
|
||||
* </div>
|
||||
*
|
||||
* <ul>
|
||||
* <li ng-repeat="(index, value) in [1,2,3]">
|
||||
* <strong>ngRepeat</strong>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* <my-wrapper ng-ref="$ctrl.wrappers[index]">ngRepeat Toggle {{$index + 1}}</my-wrapper>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen()}} // This is always undefined because it's
|
||||
* assigned to the child scope created by ngIf.
|
||||
* </div>
|
||||
* <div ng-if="true">
|
||||
<strong>ngIf</strong>
|
||||
* <my-toggle ng-ref="ngIfToggle">ngIf Toggle</my-toggle>
|
||||
* <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen() | json}}</div>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="styles.css">
|
||||
* ul {
|
||||
* list-style: none;
|
||||
* padding-left: 0;
|
||||
* }
|
||||
*
|
||||
* li[ng-repeat] {
|
||||
* background: lightgreen;
|
||||
* padding: 8px;
|
||||
* margin: 8px;
|
||||
* }
|
||||
*
|
||||
* [ng-if] {
|
||||
* background: lightgrey;
|
||||
* padding: 8px;
|
||||
* }
|
||||
*
|
||||
* my-root {
|
||||
* background: lightgoldenrodyellow;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
*
|
||||
* my-wrapper {
|
||||
* background: lightsalmon;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
*
|
||||
* my-toggle {
|
||||
* background: lightblue;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* var OuterToggle = function() {
|
||||
* this.toggle = function() {
|
||||
* element(by.buttonText('Outer Toggle')).click();
|
||||
* };
|
||||
* this.isOpen = function() {
|
||||
* return element.all(by.binding('outerToggle.isOpen()')).first().getText();
|
||||
* };
|
||||
* };
|
||||
* var NgRepeatToggle = function(i) {
|
||||
* var parent = element.all(by.repeater('(index, value) in [1,2,3]')).get(i - 1);
|
||||
* this.toggle = function() {
|
||||
* element(by.buttonText('ngRepeat Toggle ' + i)).click();
|
||||
* };
|
||||
* this.isOpen = function() {
|
||||
* return parent.element(by.binding('ngRepeatToggle.isOpen() | json')).getText();
|
||||
* };
|
||||
* this.isOuterOpen = function() {
|
||||
* return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
|
||||
* };
|
||||
* };
|
||||
* var NgRepeatToggles = function() {
|
||||
* var toggles = [1,2,3].map(function(i) { return new NgRepeatToggle(i); });
|
||||
* this.forEach = function(fn) {
|
||||
* toggles.forEach(fn);
|
||||
* };
|
||||
* this.isOuterOpen = function(i) {
|
||||
* return toggles[i - 1].isOuterOpen();
|
||||
* };
|
||||
* };
|
||||
* var NgIfToggle = function() {
|
||||
* var parent = element(by.css('[ng-if]'));
|
||||
* this.toggle = function() {
|
||||
* element(by.buttonText('ngIf Toggle')).click();
|
||||
* };
|
||||
* this.isOpen = function() {
|
||||
* return by.binding('ngIfToggle.isOpen() | json').getText();
|
||||
* };
|
||||
* this.isOuterOpen = function() {
|
||||
* return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* it('should toggle the outer toggle', function() {
|
||||
* var outerToggle = new OuterToggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* outerToggle.toggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
|
||||
* });
|
||||
*
|
||||
* it('should toggle all outer toggles', function() {
|
||||
* var outerToggle = new OuterToggle();
|
||||
* var repeatToggles = new NgRepeatToggles();
|
||||
* var ifToggle = new NgIfToggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): false');
|
||||
* expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* outerToggle.toggle();
|
||||
* expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): true');
|
||||
* expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): true');
|
||||
* });
|
||||
*
|
||||
* it('should toggle each repeat iteration separately', function() {
|
||||
* var repeatToggles = new NgRepeatToggles();
|
||||
*
|
||||
* repeatToggles.forEach(function(repeatToggle) {
|
||||
* expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): false');
|
||||
* expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* repeatToggle.toggle();
|
||||
* expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): true');
|
||||
* expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
|
||||
* });
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*/
|
||||
|
||||
var ngRefMinErr = minErr('ngRef');
|
||||
|
||||
var ngRefDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
priority: -1, // Needed for compatibility with element transclusion on the same element
|
||||
restrict: 'A',
|
||||
compile: function(tElement, tAttrs) {
|
||||
// Get the expected controller name, converts <data-some-thing> into "someThing"
|
||||
var controllerName = directiveNormalize(nodeName_(tElement));
|
||||
|
||||
// Get the expression for value binding
|
||||
var getter = $parse(tAttrs.ngRef);
|
||||
var setter = getter.assign || function() {
|
||||
throw ngRefMinErr('nonassign', 'Expression in ngRef="{0}" is non-assignable!', tAttrs.ngRef);
|
||||
};
|
||||
|
||||
return function(scope, element, attrs) {
|
||||
var refValue;
|
||||
|
||||
if (attrs.hasOwnProperty('ngRefRead')) {
|
||||
if (attrs.ngRefRead === '$element') {
|
||||
refValue = element;
|
||||
} else {
|
||||
refValue = element.data('$' + attrs.ngRefRead + 'Controller');
|
||||
|
||||
if (!refValue) {
|
||||
throw ngRefMinErr(
|
||||
'noctrl',
|
||||
'The controller for ngRefRead="{0}" could not be found on ngRef="{1}"',
|
||||
attrs.ngRefRead,
|
||||
tAttrs.ngRef
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refValue = element.data('$' + controllerName + 'Controller');
|
||||
}
|
||||
|
||||
refValue = refValue || element;
|
||||
|
||||
setter(scope, refValue);
|
||||
|
||||
// when the element is removed, remove it (nullify it)
|
||||
element.on('$destroy', function() {
|
||||
// only remove it if value has not changed,
|
||||
// because animations (and other procedures) may duplicate elements
|
||||
if (getter(scope) === refValue) {
|
||||
setter(scope, null);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
@@ -74,7 +74,7 @@
|
||||
* For example, if an item is added to the collection, `ngRepeat` will know that all other items
|
||||
* already have DOM elements, and will not re-render them.
|
||||
*
|
||||
* All different types of tracking functions, their syntax, and and their support for duplicate
|
||||
* All different types of tracking functions, their syntax, and their support for duplicate
|
||||
* items in collections can be found in the
|
||||
* {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}.
|
||||
*
|
||||
@@ -454,6 +454,13 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
|
||||
return block.clone[block.clone.length - 1];
|
||||
};
|
||||
|
||||
var trackByIdArrayFn = function($scope, key, value) {
|
||||
return hashKey(value);
|
||||
};
|
||||
|
||||
var trackByIdObjFn = function($scope, key) {
|
||||
return key;
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
@@ -493,32 +500,23 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
|
||||
aliasAs);
|
||||
}
|
||||
|
||||
var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
|
||||
var hashFnLocals = {$id: hashKey};
|
||||
var trackByIdExpFn;
|
||||
|
||||
if (trackByExp) {
|
||||
trackByExpGetter = $parse(trackByExp);
|
||||
} else {
|
||||
trackByIdArrayFn = function(key, value) {
|
||||
return hashKey(value);
|
||||
};
|
||||
trackByIdObjFn = function(key) {
|
||||
return key;
|
||||
var hashFnLocals = {$id: hashKey};
|
||||
var trackByExpGetter = $parse(trackByExp);
|
||||
|
||||
trackByIdExpFn = function($scope, key, value, index) {
|
||||
// assign key, value, and $index to the locals so that they can be used in hash functions
|
||||
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
|
||||
hashFnLocals[valueIdentifier] = value;
|
||||
hashFnLocals.$index = index;
|
||||
return trackByExpGetter($scope, hashFnLocals);
|
||||
};
|
||||
}
|
||||
|
||||
return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
|
||||
|
||||
if (trackByExpGetter) {
|
||||
trackByIdExpFn = function(key, value, index) {
|
||||
// assign key, value, and $index to the locals so that they can be used in hash functions
|
||||
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
|
||||
hashFnLocals[valueIdentifier] = value;
|
||||
hashFnLocals.$index = index;
|
||||
return trackByExpGetter($scope, hashFnLocals);
|
||||
};
|
||||
}
|
||||
|
||||
// Store a list of elements from previous run. This is a hash where key is the item from the
|
||||
// iterator, and the value is objects with following properties.
|
||||
// - scope: bound scope
|
||||
@@ -572,7 +570,7 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
|
||||
for (index = 0; index < collectionLength; index++) {
|
||||
key = (collection === collectionKeys) ? index : collectionKeys[index];
|
||||
value = collection[key];
|
||||
trackById = trackByIdFn(key, value, index);
|
||||
trackById = trackByIdFn($scope, key, value, index);
|
||||
if (lastBlockMap[trackById]) {
|
||||
// found previously seen block
|
||||
block = lastBlockMap[trackById];
|
||||
@@ -594,6 +592,12 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the value property from the hashFnLocals object to prevent a reference to the last value
|
||||
// being leaked into the ngRepeatCompile function scope
|
||||
if (hashFnLocals) {
|
||||
hashFnLocals[valueIdentifier] = undefined;
|
||||
}
|
||||
|
||||
// remove leftover items
|
||||
for (var blockKey in lastBlockMap) {
|
||||
block = lastBlockMap[blockKey];
|
||||
|
||||
@@ -54,7 +54,14 @@
|
||||
var ngStyleDirective = ngDirective(function(scope, element, attr) {
|
||||
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
if (oldStyles && (newStyles !== oldStyles)) {
|
||||
forEach(oldStyles, function(val, style) { element.css(style, '');});
|
||||
if (!newStyles) {
|
||||
newStyles = {};
|
||||
}
|
||||
forEach(oldStyles, function(val, style) {
|
||||
if (newStyles[style] == null) {
|
||||
newStyles[style] = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
if (newStyles) element.css(newStyles);
|
||||
});
|
||||
|
||||
@@ -383,7 +383,7 @@ var SelectController =
|
||||
|
||||
if (optionAttrs.$attr.ngValue) {
|
||||
// The value attribute is set by ngValue
|
||||
var oldVal, hashedVal = NaN;
|
||||
var oldVal, hashedVal;
|
||||
optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
|
||||
|
||||
var removal;
|
||||
@@ -556,18 +556,6 @@ var SelectController =
|
||||
* {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
|
||||
*
|
||||
*
|
||||
* @knownIssue
|
||||
*
|
||||
* In Firefox, the select model is only updated when the select element is blurred. For example,
|
||||
* when switching between options with the keyboard, the select model is only set to the
|
||||
* currently selected option when the select is blurred, e.g via tab key or clicking the mouse
|
||||
* outside the select.
|
||||
*
|
||||
* This is due to an ambiguity in the select element specification. See the
|
||||
* [issue on the Firefox bug tracker](https://bugzilla.mozilla.org/show_bug.cgi?id=126379)
|
||||
* for more information, and this
|
||||
* [Github comment for a workaround](https://github.com/angular/angular.js/issues/9134#issuecomment-130800488)
|
||||
*
|
||||
* @example
|
||||
* ### Simple `select` elements with static options
|
||||
*
|
||||
|
||||
@@ -62,24 +62,29 @@
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var requiredDirective = function() {
|
||||
var requiredDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
var value = attr.required || $parse(attr.ngRequired)(scope);
|
||||
|
||||
attr.required = true; // force truthy in case we are on non input element
|
||||
|
||||
ctrl.$validators.required = function(modelValue, viewValue) {
|
||||
return !attr.required || !ctrl.$isEmpty(viewValue);
|
||||
return !value || !ctrl.$isEmpty(viewValue);
|
||||
};
|
||||
|
||||
attr.$observe('required', function() {
|
||||
ctrl.$validate();
|
||||
attr.$observe('required', function(newVal) {
|
||||
if (value !== newVal) {
|
||||
value = newVal;
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
@@ -162,36 +167,59 @@ var requiredDirective = function() {
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var patternDirective = function() {
|
||||
var patternDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
compile: function(tElm, tAttr) {
|
||||
var patternExp;
|
||||
var parseFn;
|
||||
|
||||
var regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
attr.$observe('pattern', function(regex) {
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
if (tAttr.ngPattern) {
|
||||
patternExp = tAttr.ngPattern;
|
||||
|
||||
// ngPattern might be a scope expression, or an inlined regex, which is not parsable.
|
||||
// We get value of the attribute here, so we can compare the old and the new value
|
||||
// in the observer to avoid unnecessary validations
|
||||
if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) {
|
||||
parseFn = function() { return tAttr.ngPattern; };
|
||||
} else {
|
||||
parseFn = $parse(tAttr.ngPattern);
|
||||
}
|
||||
}
|
||||
|
||||
return function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var attrVal = attr.pattern;
|
||||
|
||||
if (attr.ngPattern) {
|
||||
attrVal = parseFn(scope);
|
||||
} else {
|
||||
patternExp = attr.pattern;
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
throw minErr('ngPattern')('noregexp',
|
||||
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
|
||||
regex, startingTag(elm));
|
||||
}
|
||||
var regexp = parsePatternAttr(attrVal, patternExp, elm);
|
||||
|
||||
regexp = regex || undefined;
|
||||
ctrl.$validate();
|
||||
});
|
||||
attr.$observe('pattern', function(newVal) {
|
||||
var oldRegexp = regexp;
|
||||
|
||||
ctrl.$validators.pattern = function(modelValue, viewValue) {
|
||||
// HTML5 pattern constraint validates the input value, so we validate the viewValue
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
|
||||
regexp = parsePatternAttr(newVal, patternExp, elm);
|
||||
|
||||
if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) {
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
|
||||
ctrl.$validators.pattern = function(modelValue, viewValue) {
|
||||
// HTML5 pattern constraint validates the input value, so we validate the viewValue
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
@@ -264,25 +292,29 @@ var patternDirective = function() {
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var maxlengthDirective = function() {
|
||||
var maxlengthDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var maxlength = -1;
|
||||
var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope);
|
||||
var maxlengthParsed = parseLength(maxlength);
|
||||
|
||||
attr.$observe('maxlength', function(value) {
|
||||
var intVal = toInt(value);
|
||||
maxlength = isNumberNaN(intVal) ? -1 : intVal;
|
||||
ctrl.$validate();
|
||||
if (maxlength !== value) {
|
||||
maxlengthParsed = parseLength(value);
|
||||
maxlength = value;
|
||||
ctrl.$validate();
|
||||
}
|
||||
});
|
||||
ctrl.$validators.maxlength = function(modelValue, viewValue) {
|
||||
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
|
||||
return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
@@ -353,21 +385,49 @@ var maxlengthDirective = function() {
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var minlengthDirective = function() {
|
||||
var minlengthDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var minlength = 0;
|
||||
var minlength = attr.minlength || $parse(attr.ngMinlength)(scope);
|
||||
var minlengthParsed = parseLength(minlength) || -1;
|
||||
|
||||
attr.$observe('minlength', function(value) {
|
||||
minlength = toInt(value) || 0;
|
||||
ctrl.$validate();
|
||||
if (minlength !== value) {
|
||||
minlengthParsed = parseLength(value) || -1;
|
||||
minlength = value;
|
||||
ctrl.$validate();
|
||||
}
|
||||
|
||||
});
|
||||
ctrl.$validators.minlength = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
|
||||
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlengthParsed;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
function parsePatternAttr(regex, patternExp, elm) {
|
||||
if (!regex) return undefined;
|
||||
|
||||
if (isString(regex)) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (!regex.test) {
|
||||
throw minErr('ngPattern')('noregexp',
|
||||
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
|
||||
regex, startingTag(elm));
|
||||
}
|
||||
|
||||
return regex;
|
||||
}
|
||||
|
||||
function parseLength(val) {
|
||||
var intVal = toInt(val);
|
||||
return isNumberNaN(intVal) ? -1 : intVal;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1054,7 +1054,7 @@ function $HttpProvider() {
|
||||
config.paramSerializer = isString(config.paramSerializer) ?
|
||||
$injector.get(config.paramSerializer) : config.paramSerializer;
|
||||
|
||||
$browser.$$incOutstandingRequestCount();
|
||||
$browser.$$incOutstandingRequestCount('$http');
|
||||
|
||||
var requestInterceptors = [];
|
||||
var responseInterceptors = [];
|
||||
@@ -1092,7 +1092,7 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
function completeOutstandingRequest() {
|
||||
$browser.$$completeOutstandingRequest(noop);
|
||||
$browser.$$completeOutstandingRequest(noop, '$http');
|
||||
}
|
||||
|
||||
function executeHeaderFns(headers, config) {
|
||||
|
||||
@@ -242,7 +242,7 @@ function $InterpolateProvider() {
|
||||
|
||||
// Provide a quick exit and simplified result function for text with no interpolation
|
||||
if (!text.length || text.indexOf(startSymbol) === -1) {
|
||||
if (mustHaveExpression && !contextAllowsConcatenation) return;
|
||||
if (mustHaveExpression) return;
|
||||
|
||||
var unescapedText = unescapeText(text);
|
||||
if (contextAllowsConcatenation) {
|
||||
|
||||
+13
-48
@@ -4,10 +4,18 @@ var $intervalMinErr = minErr('$interval');
|
||||
|
||||
/** @this */
|
||||
function $IntervalProvider() {
|
||||
this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
|
||||
function($rootScope, $window, $q, $$q, $browser) {
|
||||
this.$get = ['$$intervalFactory', '$window',
|
||||
function($$intervalFactory, $window) {
|
||||
var intervals = {};
|
||||
|
||||
var setIntervalFn = function(tick, delay, deferred) {
|
||||
var id = $window.setInterval(tick, delay);
|
||||
intervals[id] = deferred;
|
||||
return id;
|
||||
};
|
||||
var clearIntervalFn = function(id) {
|
||||
$window.clearInterval(id);
|
||||
delete intervals[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
@@ -135,49 +143,7 @@ function $IntervalProvider() {
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
function interval(fn, delay, count, invokeApply) {
|
||||
var hasParams = arguments.length > 4,
|
||||
args = hasParams ? sliceArgs(arguments, 4) : [],
|
||||
setInterval = $window.setInterval,
|
||||
clearInterval = $window.clearInterval,
|
||||
iteration = 0,
|
||||
skipApply = (isDefined(invokeApply) && !invokeApply),
|
||||
deferred = (skipApply ? $$q : $q).defer(),
|
||||
promise = deferred.promise;
|
||||
|
||||
count = isDefined(count) ? count : 0;
|
||||
|
||||
promise.$$intervalId = setInterval(function tick() {
|
||||
if (skipApply) {
|
||||
$browser.defer(callback);
|
||||
} else {
|
||||
$rootScope.$evalAsync(callback);
|
||||
}
|
||||
deferred.notify(iteration++);
|
||||
|
||||
if (count > 0 && iteration >= count) {
|
||||
deferred.resolve(iteration);
|
||||
clearInterval(promise.$$intervalId);
|
||||
delete intervals[promise.$$intervalId];
|
||||
}
|
||||
|
||||
if (!skipApply) $rootScope.$apply();
|
||||
|
||||
}, delay);
|
||||
|
||||
intervals[promise.$$intervalId] = deferred;
|
||||
|
||||
return promise;
|
||||
|
||||
function callback() {
|
||||
if (!hasParams) {
|
||||
fn(iteration);
|
||||
} else {
|
||||
fn.apply(null, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var interval = $$intervalFactory(setIntervalFn, clearIntervalFn);
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -205,8 +171,7 @@ function $IntervalProvider() {
|
||||
// Interval cancels should not report an unhandled promise.
|
||||
markQExceptionHandled(deferred.promise);
|
||||
deferred.reject('canceled');
|
||||
$window.clearInterval(id);
|
||||
delete intervals[id];
|
||||
clearIntervalFn(id);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
/** @this */
|
||||
function $$IntervalFactoryProvider() {
|
||||
this.$get = ['$browser', '$q', '$$q', '$rootScope',
|
||||
function($browser, $q, $$q, $rootScope) {
|
||||
return function intervalFactory(setIntervalFn, clearIntervalFn) {
|
||||
return function intervalFn(fn, delay, count, invokeApply) {
|
||||
var hasParams = arguments.length > 4,
|
||||
args = hasParams ? sliceArgs(arguments, 4) : [],
|
||||
iteration = 0,
|
||||
skipApply = isDefined(invokeApply) && !invokeApply,
|
||||
deferred = (skipApply ? $$q : $q).defer(),
|
||||
promise = deferred.promise;
|
||||
|
||||
count = isDefined(count) ? count : 0;
|
||||
|
||||
function callback() {
|
||||
if (!hasParams) {
|
||||
fn(iteration);
|
||||
} else {
|
||||
fn.apply(null, args);
|
||||
}
|
||||
}
|
||||
|
||||
function tick() {
|
||||
if (skipApply) {
|
||||
$browser.defer(callback);
|
||||
} else {
|
||||
$rootScope.$evalAsync(callback);
|
||||
}
|
||||
deferred.notify(iteration++);
|
||||
|
||||
if (count > 0 && iteration >= count) {
|
||||
deferred.resolve(iteration);
|
||||
clearIntervalFn(promise.$$intervalId);
|
||||
}
|
||||
|
||||
if (!skipApply) $rootScope.$apply();
|
||||
}
|
||||
|
||||
promise.$$intervalId = setIntervalFn(tick, delay, deferred, skipApply);
|
||||
|
||||
return promise;
|
||||
};
|
||||
};
|
||||
}];
|
||||
}
|
||||
+36
-44
@@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
/* global stripHash: true */
|
||||
|
||||
var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/,
|
||||
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
|
||||
@@ -38,6 +39,14 @@ function decodePath(path, html5Mode) {
|
||||
return segments.join('/');
|
||||
}
|
||||
|
||||
function normalizePath(pathValue, searchValue, hashValue) {
|
||||
var search = toKeyValue(searchValue),
|
||||
hash = hashValue ? '#' + encodeUriSegment(hashValue) : '',
|
||||
path = encodePath(pathValue);
|
||||
|
||||
return path + (search ? '?' + search : '') + hash;
|
||||
}
|
||||
|
||||
function parseAbsoluteUrl(absoluteUrl, locationObj) {
|
||||
var parsedUrl = urlResolve(absoluteUrl);
|
||||
|
||||
@@ -86,17 +95,11 @@ function stripBaseUrl(base, url) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function stripHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? url : url.substr(0, index);
|
||||
}
|
||||
|
||||
function trimEmptyHash(url) {
|
||||
return url.replace(/(#.+)|#$/, '$1');
|
||||
}
|
||||
|
||||
|
||||
function stripFile(url) {
|
||||
return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
|
||||
}
|
||||
@@ -143,18 +146,8 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
|
||||
this.$$compose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Compose url and update `absUrl` property
|
||||
* @private
|
||||
*/
|
||||
this.$$compose = function() {
|
||||
var search = toKeyValue(this.$$search),
|
||||
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
this.$$normalizeUrl = function(url) {
|
||||
return appBaseNoFile + url.substr(1); // first char is always '/'
|
||||
};
|
||||
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
@@ -278,18 +271,8 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compose hashbang URL and update `absUrl` property
|
||||
* @private
|
||||
*/
|
||||
this.$$compose = function() {
|
||||
var search = toKeyValue(this.$$search),
|
||||
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
this.$$normalizeUrl = function(url) {
|
||||
return appBase + (url ? hashPrefix + url : '');
|
||||
};
|
||||
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
@@ -340,17 +323,10 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
|
||||
return !!rewrittenUrl;
|
||||
};
|
||||
|
||||
this.$$compose = function() {
|
||||
var search = toKeyValue(this.$$search),
|
||||
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$normalizeUrl = function(url) {
|
||||
// include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
|
||||
this.$$absUrl = appBase + hashPrefix + this.$$url;
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
return appBase + hashPrefix + url;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -374,6 +350,16 @@ var locationPrototype = {
|
||||
*/
|
||||
$$replace: false,
|
||||
|
||||
/**
|
||||
* Compose url and update `url` and `absUrl` property
|
||||
* @private
|
||||
*/
|
||||
$$compose: function() {
|
||||
this.$$url = normalizePath(this.$$path, this.$$search, this.$$hash);
|
||||
this.$$absUrl = this.$$normalizeUrl(this.$$url);
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $location#absUrl
|
||||
@@ -879,6 +865,13 @@ function $LocationProvider() {
|
||||
|
||||
var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
|
||||
|
||||
// Determine if two URLs are equal despite potentially having different encoding/normalizing
|
||||
// such as $location.absUrl() vs $browser.url()
|
||||
// See https://github.com/angular/angular.js/issues/16592
|
||||
function urlsEqual(a, b) {
|
||||
return a === b || urlResolve(a).href === urlResolve(b).href;
|
||||
}
|
||||
|
||||
function setBrowserUrlWithFallback(url, replace, state) {
|
||||
var oldUrl = $location.url();
|
||||
var oldState = $location.$$state;
|
||||
@@ -945,7 +938,7 @@ function $LocationProvider() {
|
||||
|
||||
|
||||
// rewrite hashbang url <> html5 url
|
||||
if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) {
|
||||
if ($location.absUrl() !== initialUrl) {
|
||||
$browser.url($location.absUrl(), true);
|
||||
}
|
||||
|
||||
@@ -964,7 +957,6 @@ function $LocationProvider() {
|
||||
var oldUrl = $location.absUrl();
|
||||
var oldState = $location.$$state;
|
||||
var defaultPrevented;
|
||||
newUrl = trimEmptyHash(newUrl);
|
||||
$location.$$parse(newUrl);
|
||||
$location.$$state = newState;
|
||||
|
||||
@@ -992,11 +984,11 @@ function $LocationProvider() {
|
||||
if (initializing || $location.$$urlUpdatedByLocation) {
|
||||
$location.$$urlUpdatedByLocation = false;
|
||||
|
||||
var oldUrl = trimEmptyHash($browser.url());
|
||||
var newUrl = trimEmptyHash($location.absUrl());
|
||||
var oldUrl = $browser.url();
|
||||
var newUrl = $location.absUrl();
|
||||
var oldState = $browser.state();
|
||||
var currentReplace = $location.$$replace;
|
||||
var urlOrStateChanged = oldUrl !== newUrl ||
|
||||
var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) ||
|
||||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
|
||||
|
||||
if (initializing || urlOrStateChanged) {
|
||||
|
||||
+7
-1
@@ -683,5 +683,11 @@ function markQStateExceptionHandled(state) {
|
||||
state.pur = true;
|
||||
}
|
||||
function markQExceptionHandled(q) {
|
||||
markQStateExceptionHandled(q.$$state);
|
||||
// Built-in `$q` promises will always have a `$$state` property. This check is to allow
|
||||
// overwriting `$q` with a different promise library (e.g. Bluebird + angular-bluebird-promises).
|
||||
// (Currently, this is the only method that might be called with a promise, even if it is not
|
||||
// created by the built-in `$q`.)
|
||||
if (q.$$state) {
|
||||
markQStateExceptionHandled(q.$$state);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1122,7 +1122,7 @@ function $RootScopeProvider() {
|
||||
if (asyncQueue.length) {
|
||||
$rootScope.$digest();
|
||||
}
|
||||
});
|
||||
}, null, '$evalAsync');
|
||||
}
|
||||
|
||||
asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
|
||||
@@ -1493,7 +1493,7 @@ function $RootScopeProvider() {
|
||||
if (applyAsyncId === null) {
|
||||
applyAsyncId = $browser.defer(function() {
|
||||
$rootScope.$apply(flushApplyAsync);
|
||||
});
|
||||
}, null, '$applyAsync');
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
+2
-2
@@ -440,7 +440,7 @@ function $SceDelegateProvider() {
|
||||
// If we get here, then we will either sanitize the value or throw an exception.
|
||||
if (type === SCE_CONTEXTS.MEDIA_URL || type === SCE_CONTEXTS.URL) {
|
||||
// we attempt to sanitize non-resource URLs
|
||||
return $$sanitizeUri(maybeTrusted, type === SCE_CONTEXTS.MEDIA_URL);
|
||||
return $$sanitizeUri(maybeTrusted.toString(), type === SCE_CONTEXTS.MEDIA_URL);
|
||||
} else if (type === SCE_CONTEXTS.RESOURCE_URL) {
|
||||
if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
|
||||
return maybeTrusted;
|
||||
@@ -623,7 +623,7 @@ function $SceDelegateProvider() {
|
||||
* | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
|
||||
* | `$sce.MEDIA_URL` | For URLs that are safe to render as media. Is automatically converted from string by sanitizing when needed. |
|
||||
* | `$sce.URL` | For URLs that are safe to follow as links. Is automatically converted from string by sanitizing when needed. Note that `$sce.URL` makes a stronger statement about the URL than `$sce.MEDIA_URL` does and therefore contexts requiring values trusted for `$sce.URL` can be used anywhere that values trusted for `$sce.MEDIA_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` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_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` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_URL` are required. <br><br> The {@link $sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider#resourceUrlWhitelist()} and {@link $sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider#resourceUrlBlacklist()} can be used to restrict trusted origins for `RESOURCE_URL` |
|
||||
* | `$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. |
|
||||
*
|
||||
*
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ! This is a private undocumented service !
|
||||
*
|
||||
* @name $$taskTrackerFactory
|
||||
* @description
|
||||
* A function to create `TaskTracker` instances.
|
||||
*
|
||||
* A `TaskTracker` can keep track of pending tasks (grouped by type) and can notify interested
|
||||
* parties when all pending tasks (or tasks of a specific type) have been completed.
|
||||
*
|
||||
* @param {$log} log - A logger instance (such as `$log`). Used to log error during callback
|
||||
* execution.
|
||||
*
|
||||
* @this
|
||||
*/
|
||||
function $$TaskTrackerFactoryProvider() {
|
||||
this.$get = valueFn(function(log) { return new TaskTracker(log); });
|
||||
}
|
||||
|
||||
function TaskTracker(log) {
|
||||
var self = this;
|
||||
var taskCounts = {};
|
||||
var taskCallbacks = [];
|
||||
|
||||
var ALL_TASKS_TYPE = self.ALL_TASKS_TYPE = '$$all$$';
|
||||
var DEFAULT_TASK_TYPE = self.DEFAULT_TASK_TYPE = '$$default$$';
|
||||
|
||||
/**
|
||||
* Execute the specified function and decrement the appropriate `taskCounts` counter.
|
||||
* If the counter reaches 0, all corresponding `taskCallbacks` are executed.
|
||||
*
|
||||
* @param {Function} fn - The function to execute.
|
||||
* @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task that is being completed.
|
||||
*/
|
||||
self.completeTask = completeTask;
|
||||
|
||||
/**
|
||||
* Increase the task count for the specified task type (or the default task type if non is
|
||||
* specified).
|
||||
*
|
||||
* @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task whose count will be increased.
|
||||
*/
|
||||
self.incTaskCount = incTaskCount;
|
||||
|
||||
/**
|
||||
* Execute the specified callback when all pending tasks have been completed.
|
||||
*
|
||||
* If there are no pending tasks, the callback is executed immediately. You can optionally limit
|
||||
* the tasks that will be waited for to a specific type, by passing a `taskType`.
|
||||
*
|
||||
* @param {function} callback - The function to call when there are no pending tasks.
|
||||
* @param {string=} [taskType=ALL_TASKS_TYPE] - The type of tasks that will be waited for.
|
||||
*/
|
||||
self.notifyWhenNoPendingTasks = notifyWhenNoPendingTasks;
|
||||
|
||||
function completeTask(fn, taskType) {
|
||||
taskType = taskType || DEFAULT_TASK_TYPE;
|
||||
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
decTaskCount(taskType);
|
||||
|
||||
var countForType = taskCounts[taskType];
|
||||
var countForAll = taskCounts[ALL_TASKS_TYPE];
|
||||
|
||||
// If at least one of the queues (`ALL_TASKS_TYPE` or `taskType`) is empty, run callbacks.
|
||||
if (!countForAll || !countForType) {
|
||||
var getNextCallback = !countForAll ? getLastCallback : getLastCallbackForType;
|
||||
var nextCb;
|
||||
|
||||
while ((nextCb = getNextCallback(taskType))) {
|
||||
try {
|
||||
nextCb();
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decTaskCount(taskType) {
|
||||
taskType = taskType || DEFAULT_TASK_TYPE;
|
||||
if (taskCounts[taskType]) {
|
||||
taskCounts[taskType]--;
|
||||
taskCounts[ALL_TASKS_TYPE]--;
|
||||
}
|
||||
}
|
||||
|
||||
function getLastCallback() {
|
||||
var cbInfo = taskCallbacks.pop();
|
||||
return cbInfo && cbInfo.cb;
|
||||
}
|
||||
|
||||
function getLastCallbackForType(taskType) {
|
||||
for (var i = taskCallbacks.length - 1; i >= 0; --i) {
|
||||
var cbInfo = taskCallbacks[i];
|
||||
if (cbInfo.type === taskType) {
|
||||
taskCallbacks.splice(i, 1);
|
||||
return cbInfo.cb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function incTaskCount(taskType) {
|
||||
taskType = taskType || DEFAULT_TASK_TYPE;
|
||||
taskCounts[taskType] = (taskCounts[taskType] || 0) + 1;
|
||||
taskCounts[ALL_TASKS_TYPE] = (taskCounts[ALL_TASKS_TYPE] || 0) + 1;
|
||||
}
|
||||
|
||||
function notifyWhenNoPendingTasks(callback, taskType) {
|
||||
taskType = taskType || ALL_TASKS_TYPE;
|
||||
if (!taskCounts[taskType]) {
|
||||
callback();
|
||||
} else {
|
||||
taskCallbacks.push({type: taskType, cb: callback});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,15 @@ function $$TestabilityProvider() {
|
||||
* @name $$testability#whenStable
|
||||
*
|
||||
* @description
|
||||
* Calls the callback when $timeout and $http requests are completed.
|
||||
* Calls the callback when all pending tasks are completed.
|
||||
*
|
||||
* Types of tasks waited for include:
|
||||
* - Pending timeouts (via {@link $timeout}).
|
||||
* - Pending HTTP requests (via {@link $http}).
|
||||
* - In-progress route transitions (via {@link $route}).
|
||||
* - Pending tasks scheduled via {@link $rootScope#$applyAsync}.
|
||||
* - Pending tasks scheduled via {@link $rootScope#$evalAsync}.
|
||||
* These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
|
||||
*
|
||||
* @param {function} callback
|
||||
*/
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ function $TimeoutProvider() {
|
||||
}
|
||||
|
||||
if (!skipApply) $rootScope.$apply();
|
||||
}, delay);
|
||||
}, delay, '$timeout');
|
||||
|
||||
promise.$$timeoutId = timeoutId;
|
||||
deferreds[timeoutId] = deferred;
|
||||
|
||||
+13
-1
@@ -10,6 +10,12 @@ var urlParsingNode = window.document.createElement('a');
|
||||
var originUrl = urlResolve(window.location.href);
|
||||
var baseUrlParsingNode;
|
||||
|
||||
urlParsingNode.href = 'http://[::1]';
|
||||
|
||||
// Support: IE 9-11 only, Edge 16-17 only (fixed in 18 Preview)
|
||||
// IE/Edge don't wrap IPv6 addresses' hostnames in square brackets
|
||||
// when parsed out of an anchor element.
|
||||
var ipv6InBrackets = urlParsingNode.hostname === '[::1]';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -72,13 +78,19 @@ function urlResolve(url) {
|
||||
|
||||
urlParsingNode.setAttribute('href', href);
|
||||
|
||||
var hostname = urlParsingNode.hostname;
|
||||
|
||||
if (!ipv6InBrackets && hostname.indexOf(':') > -1) {
|
||||
hostname = '[' + hostname + ']';
|
||||
}
|
||||
|
||||
return {
|
||||
href: urlParsingNode.href,
|
||||
protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
|
||||
host: urlParsingNode.host,
|
||||
search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
|
||||
hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
|
||||
hostname: urlParsingNode.hostname,
|
||||
hostname: hostname,
|
||||
port: urlParsingNode.port,
|
||||
pathname: (urlParsingNode.pathname.charAt(0) === '/')
|
||||
? urlParsingNode.pathname
|
||||
|
||||
@@ -57,11 +57,11 @@
|
||||
"applyInlineStyle": false,
|
||||
"assertArg": false,
|
||||
"blockKeyframeAnimations": false,
|
||||
"blockTransitions": false,
|
||||
"clearGeneratedClasses": false,
|
||||
"concatWithSpace": false,
|
||||
"extractElementNode": false,
|
||||
"getDomNode": false,
|
||||
"helpers": false,
|
||||
"mergeAnimationDetails": false,
|
||||
"mergeClasses": false,
|
||||
"packageStyles": false,
|
||||
@@ -73,6 +73,7 @@
|
||||
/* ngAnimate directives/services */
|
||||
"ngAnimateSwapDirective": true,
|
||||
"$$rAFSchedulerFactory": true,
|
||||
"$$AnimateCacheProvider": true,
|
||||
"$$AnimateChildrenDirective": true,
|
||||
"$$AnimateQueueProvider": true,
|
||||
"$$AnimationProvider": true,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
/** @this */
|
||||
var $$AnimateCacheProvider = function() {
|
||||
|
||||
var KEY = '$$ngAnimateParentKey';
|
||||
var parentCounter = 0;
|
||||
var cache = Object.create(null);
|
||||
|
||||
this.$get = [function() {
|
||||
return {
|
||||
cacheKey: function(node, method, addClass, removeClass) {
|
||||
var parentNode = node.parentNode;
|
||||
var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
|
||||
var parts = [parentID, method, node.getAttribute('class')];
|
||||
if (addClass) {
|
||||
parts.push(addClass);
|
||||
}
|
||||
if (removeClass) {
|
||||
parts.push(removeClass);
|
||||
}
|
||||
return parts.join(' ');
|
||||
},
|
||||
|
||||
containsCachedAnimationWithoutDuration: function(key) {
|
||||
var entry = cache[key];
|
||||
|
||||
// nothing cached, so go ahead and animate
|
||||
// otherwise it should be a valid animation
|
||||
return (entry && !entry.isValid) || false;
|
||||
},
|
||||
|
||||
flush: function() {
|
||||
cache = Object.create(null);
|
||||
},
|
||||
|
||||
count: function(key) {
|
||||
var entry = cache[key];
|
||||
return entry ? entry.total : 0;
|
||||
},
|
||||
|
||||
get: function(key) {
|
||||
var entry = cache[key];
|
||||
return entry && entry.value;
|
||||
},
|
||||
|
||||
put: function(key, value, isValid) {
|
||||
if (!cache[key]) {
|
||||
cache[key] = { total: 1, value: value, isValid: isValid };
|
||||
} else {
|
||||
cache[key].total++;
|
||||
cache[key].value = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
};
|
||||
+38
-62
@@ -304,33 +304,6 @@ function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
|
||||
return [style, value];
|
||||
}
|
||||
|
||||
function createLocalCacheLookup() {
|
||||
var cache = Object.create(null);
|
||||
return {
|
||||
flush: function() {
|
||||
cache = Object.create(null);
|
||||
},
|
||||
|
||||
count: function(key) {
|
||||
var entry = cache[key];
|
||||
return entry ? entry.total : 0;
|
||||
},
|
||||
|
||||
get: function(key) {
|
||||
var entry = cache[key];
|
||||
return entry && entry.value;
|
||||
},
|
||||
|
||||
put: function(key, value) {
|
||||
if (!cache[key]) {
|
||||
cache[key] = { total: 1, value: value };
|
||||
} else {
|
||||
cache[key].total++;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// we do not reassign an already present style value since
|
||||
// if we detect the style property value again we may be
|
||||
// detecting styles that were added via the `from` styles.
|
||||
@@ -349,26 +322,16 @@ function registerRestorableStyles(backup, node, properties) {
|
||||
}
|
||||
|
||||
var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animateProvider) {
|
||||
var gcsLookup = createLocalCacheLookup();
|
||||
var gcsStaggerLookup = createLocalCacheLookup();
|
||||
|
||||
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
|
||||
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$animateCache',
|
||||
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
|
||||
function($window, $$jqLite, $$AnimateRunner, $timeout,
|
||||
function($window, $$jqLite, $$AnimateRunner, $timeout, $$animateCache,
|
||||
$$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
|
||||
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
|
||||
var parentCounter = 0;
|
||||
function gcsHashFn(node, extraClasses) {
|
||||
var KEY = '$$ngAnimateParentKey';
|
||||
var parentNode = node.parentNode;
|
||||
var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
|
||||
return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
|
||||
}
|
||||
|
||||
function computeCachedCssStyles(node, className, cacheKey, properties) {
|
||||
var timings = gcsLookup.get(cacheKey);
|
||||
function computeCachedCssStyles(node, className, cacheKey, allowNoDuration, properties) {
|
||||
var timings = $$animateCache.get(cacheKey);
|
||||
|
||||
if (!timings) {
|
||||
timings = computeCssStyles($window, node, properties);
|
||||
@@ -377,20 +340,26 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
}
|
||||
}
|
||||
|
||||
// if a css animation has no duration we
|
||||
// should mark that so that repeated addClass/removeClass calls are skipped
|
||||
var hasDuration = allowNoDuration || (timings.transitionDuration > 0 || timings.animationDuration > 0);
|
||||
|
||||
// we keep putting this in multiple times even though the value and the cacheKey are the same
|
||||
// because we're keeping an internal tally of how many duplicate animations are detected.
|
||||
gcsLookup.put(cacheKey, timings);
|
||||
$$animateCache.put(cacheKey, timings, hasDuration);
|
||||
|
||||
return timings;
|
||||
}
|
||||
|
||||
function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
|
||||
var stagger;
|
||||
var staggerCacheKey = 'stagger-' + cacheKey;
|
||||
|
||||
// if we have one or more existing matches of matching elements
|
||||
// containing the same parent + CSS styles (which is how cacheKey works)
|
||||
// then staggering is possible
|
||||
if (gcsLookup.count(cacheKey) > 0) {
|
||||
stagger = gcsStaggerLookup.get(cacheKey);
|
||||
if ($$animateCache.count(cacheKey) > 0) {
|
||||
stagger = $$animateCache.get(staggerCacheKey);
|
||||
|
||||
if (!stagger) {
|
||||
var staggerClassName = pendClasses(className, '-stagger');
|
||||
@@ -405,7 +374,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
$$jqLite.removeClass(node, staggerClassName);
|
||||
|
||||
gcsStaggerLookup.put(cacheKey, stagger);
|
||||
$$animateCache.put(staggerCacheKey, stagger, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,8 +385,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
function waitUntilQuiet(callback) {
|
||||
rafWaitQueue.push(callback);
|
||||
$$rAFScheduler.waitUntilQuiet(function() {
|
||||
gcsLookup.flush();
|
||||
gcsStaggerLookup.flush();
|
||||
$$animateCache.flush();
|
||||
|
||||
// DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
|
||||
// PLEASE EXAMINE THE `$$forceReflow` service to understand why.
|
||||
@@ -432,8 +400,8 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
});
|
||||
}
|
||||
|
||||
function computeTimings(node, className, cacheKey) {
|
||||
var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
|
||||
function computeTimings(node, className, cacheKey, allowNoDuration) {
|
||||
var timings = computeCachedCssStyles(node, className, cacheKey, allowNoDuration, DETECT_CSS_PROPERTIES);
|
||||
var aD = timings.animationDelay;
|
||||
var tD = timings.transitionDelay;
|
||||
timings.maxDelay = aD && tD
|
||||
@@ -520,7 +488,6 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
|
||||
var fullClassName = classes + ' ' + preparationClasses;
|
||||
var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
|
||||
var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
|
||||
var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
|
||||
|
||||
@@ -533,7 +500,12 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
var cacheKey, stagger;
|
||||
var stagger, cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass);
|
||||
if ($$animateCache.containsCachedAnimationWithoutDuration(cacheKey)) {
|
||||
preparationClasses = null;
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
if (options.stagger > 0) {
|
||||
var staggerVal = parseFloat(options.stagger);
|
||||
stagger = {
|
||||
@@ -543,7 +515,6 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
animationDuration: 0
|
||||
};
|
||||
} else {
|
||||
cacheKey = gcsHashFn(node, fullClassName);
|
||||
stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
|
||||
}
|
||||
|
||||
@@ -577,7 +548,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
var itemIndex = stagger
|
||||
? options.staggerIndex >= 0
|
||||
? options.staggerIndex
|
||||
: gcsLookup.count(cacheKey)
|
||||
: $$animateCache.count(cacheKey)
|
||||
: 0;
|
||||
|
||||
var isFirst = itemIndex === 0;
|
||||
@@ -589,10 +560,10 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
// that if there is no transition defined then nothing will happen and this will also allow
|
||||
// other transitions to be stacked on top of each other without any chopping them out.
|
||||
if (isFirst && !options.skipBlocking) {
|
||||
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
|
||||
helpers.blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
|
||||
}
|
||||
|
||||
var timings = computeTimings(node, fullClassName, cacheKey);
|
||||
var timings = computeTimings(node, fullClassName, cacheKey, !isStructural);
|
||||
var relativeDelay = timings.maxDelay;
|
||||
maxDelay = Math.max(relativeDelay, 0);
|
||||
maxDuration = timings.maxDuration;
|
||||
@@ -630,6 +601,8 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
|
||||
|
||||
if (options.delay != null) {
|
||||
var delayStyle;
|
||||
if (typeof options.delay !== 'boolean') {
|
||||
@@ -673,7 +646,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
if (flags.blockTransition || flags.blockKeyframeAnimation) {
|
||||
applyBlocking(maxDuration);
|
||||
} else if (!options.skipBlocking) {
|
||||
blockTransitions(node, false);
|
||||
helpers.blockTransitions(node, false);
|
||||
}
|
||||
|
||||
// TODO(matsko): for 1.5 change this code to have an animator object for better debugging
|
||||
@@ -717,13 +690,16 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
animationClosed = true;
|
||||
animationPaused = false;
|
||||
|
||||
if (!options.$$skipPreparationClasses) {
|
||||
if (preparationClasses && !options.$$skipPreparationClasses) {
|
||||
$$jqLite.removeClass(element, preparationClasses);
|
||||
}
|
||||
$$jqLite.removeClass(element, activeClasses);
|
||||
|
||||
if (activeClasses) {
|
||||
$$jqLite.removeClass(element, activeClasses);
|
||||
}
|
||||
|
||||
blockKeyframeAnimations(node, false);
|
||||
blockTransitions(node, false);
|
||||
helpers.blockTransitions(node, false);
|
||||
|
||||
forEach(temporaryStyles, function(entry) {
|
||||
// There is only one way to remove inline style properties entirely from elements.
|
||||
@@ -774,7 +750,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
function applyBlocking(duration) {
|
||||
if (flags.blockTransition) {
|
||||
blockTransitions(node, duration);
|
||||
helpers.blockTransitions(node, duration);
|
||||
}
|
||||
|
||||
if (flags.blockKeyframeAnimation) {
|
||||
@@ -904,9 +880,9 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
if (flags.recalculateTimingStyles) {
|
||||
fullClassName = node.getAttribute('class') + ' ' + preparationClasses;
|
||||
cacheKey = gcsHashFn(node, fullClassName);
|
||||
cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass);
|
||||
|
||||
timings = computeTimings(node, fullClassName, cacheKey);
|
||||
timings = computeTimings(node, fullClassName, cacheKey, false);
|
||||
relativeDelay = timings.maxDelay;
|
||||
maxDelay = Math.max(relativeDelay, 0);
|
||||
maxDuration = timings.maxDuration;
|
||||
|
||||
@@ -13,6 +13,15 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
join: []
|
||||
};
|
||||
|
||||
function getEventData(options) {
|
||||
return {
|
||||
addClass: options.addClass,
|
||||
removeClass: options.removeClass,
|
||||
from: options.from,
|
||||
to: options.to
|
||||
};
|
||||
}
|
||||
|
||||
function makeTruthyCssClassMap(classString) {
|
||||
if (!classString) {
|
||||
return null;
|
||||
@@ -111,6 +120,10 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
var disabledElementsLookup = new $$Map();
|
||||
var animationsEnabled = null;
|
||||
|
||||
function removeFromDisabledElementsLookup(evt) {
|
||||
disabledElementsLookup.delete(evt.target);
|
||||
}
|
||||
|
||||
function postDigestTaskFactory() {
|
||||
var postDigestCalled = false;
|
||||
return function(fn) {
|
||||
@@ -294,6 +307,11 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
bool = !disabledElementsLookup.get(node);
|
||||
} else {
|
||||
// (element, bool) - Element setter
|
||||
if (!disabledElementsLookup.has(node)) {
|
||||
// The element is added to the map for the first time.
|
||||
// Create a listener to remove it on `$destroy` (to avoid memory leak).
|
||||
jqLite(element).on('$destroy', removeFromDisabledElementsLookup);
|
||||
}
|
||||
disabledElementsLookup.set(node, !bool);
|
||||
}
|
||||
}
|
||||
@@ -379,9 +397,9 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
|
||||
if (skipAnimations) {
|
||||
// Callbacks should fire even if the document is hidden (regression fix for issue #14120)
|
||||
if (documentHidden) notifyProgress(runner, event, 'start');
|
||||
if (documentHidden) notifyProgress(runner, event, 'start', getEventData(options));
|
||||
close();
|
||||
if (documentHidden) notifyProgress(runner, event, 'close');
|
||||
if (documentHidden) notifyProgress(runner, event, 'close', getEventData(options));
|
||||
return runner;
|
||||
}
|
||||
|
||||
@@ -438,7 +456,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
if (existingAnimation.state === RUNNING_STATE) {
|
||||
normalizeAnimationDetails(element, newAnimation);
|
||||
} else {
|
||||
applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
|
||||
applyGeneratedPreparationClasses($$jqLite, element, isStructural ? event : null, options);
|
||||
|
||||
event = newAnimation.event = existingAnimation.event;
|
||||
options = mergeAnimationDetails(element, existingAnimation, newAnimation);
|
||||
@@ -543,7 +561,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
// this will update the runner's flow-control events based on
|
||||
// the `realRunner` object.
|
||||
runner.setHost(realRunner);
|
||||
notifyProgress(runner, event, 'start', {});
|
||||
notifyProgress(runner, event, 'start', getEventData(options));
|
||||
|
||||
realRunner.done(function(status) {
|
||||
close(!status);
|
||||
@@ -551,7 +569,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
if (animationDetails && animationDetails.counter === counter) {
|
||||
clearElementAnimationState(node);
|
||||
}
|
||||
notifyProgress(runner, event, 'close', {});
|
||||
notifyProgress(runner, event, 'close', getEventData(options));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+56
-17
@@ -8,6 +8,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
var drivers = this.drivers = [];
|
||||
|
||||
var RUNNER_STORAGE_KEY = '$$animationRunner';
|
||||
var PREPARE_CLASSES_KEY = '$$animatePrepareClasses';
|
||||
|
||||
function setRunner(element, runner) {
|
||||
element.data(RUNNER_STORAGE_KEY, runner);
|
||||
@@ -21,8 +22,8 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
return element.data(RUNNER_STORAGE_KEY);
|
||||
}
|
||||
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$Map', '$$rAFScheduler',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$Map, $$rAFScheduler) {
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$Map', '$$rAFScheduler', '$$animateCache',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$Map, $$rAFScheduler, $$animateCache) {
|
||||
|
||||
var animationQueue = [];
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
@@ -37,6 +38,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
var animation = animations[i];
|
||||
lookup.set(animation.domNode, animations[i] = {
|
||||
domNode: animation.domNode,
|
||||
element: animation.element,
|
||||
fn: animation.fn,
|
||||
children: []
|
||||
});
|
||||
@@ -93,7 +95,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
result.push(row);
|
||||
row = [];
|
||||
}
|
||||
row.push(entry.fn);
|
||||
row.push(entry);
|
||||
entry.children.forEach(function(childEntry) {
|
||||
nextLevelEntries++;
|
||||
queue.push(childEntry);
|
||||
@@ -128,8 +130,6 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
return runner;
|
||||
}
|
||||
|
||||
setRunner(element, runner);
|
||||
|
||||
var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
|
||||
var tempClasses = options.tempClasses;
|
||||
if (tempClasses) {
|
||||
@@ -137,12 +137,12 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
options.tempClasses = null;
|
||||
}
|
||||
|
||||
var prepareClassName;
|
||||
if (isStructural) {
|
||||
prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
|
||||
$$jqLite.addClass(element, prepareClassName);
|
||||
element.data(PREPARE_CLASSES_KEY, 'ng-' + event + PREPARE_CLASS_SUFFIX);
|
||||
}
|
||||
|
||||
setRunner(element, runner);
|
||||
|
||||
animationQueue.push({
|
||||
// this data is used by the postDigest code and passed into
|
||||
// the driver step function
|
||||
@@ -182,16 +182,31 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
var toBeSortedAnimations = [];
|
||||
|
||||
forEach(groupedAnimations, function(animationEntry) {
|
||||
var element = animationEntry.from ? animationEntry.from.element : animationEntry.element;
|
||||
var extraClasses = options.addClass;
|
||||
|
||||
extraClasses = (extraClasses ? (extraClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
|
||||
var cacheKey = $$animateCache.cacheKey(element[0], animationEntry.event, extraClasses, options.removeClass);
|
||||
|
||||
toBeSortedAnimations.push({
|
||||
domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
|
||||
element: element,
|
||||
domNode: getDomNode(element),
|
||||
fn: function triggerAnimationStart() {
|
||||
var startAnimationFn, closeFn = animationEntry.close;
|
||||
|
||||
// in the event that we've cached the animation status for this element
|
||||
// and it's in fact an invalid animation (something that has duration = 0)
|
||||
// then we should skip all the heavy work from here on
|
||||
if ($$animateCache.containsCachedAnimationWithoutDuration(cacheKey)) {
|
||||
closeFn();
|
||||
return;
|
||||
}
|
||||
|
||||
// it's important that we apply the `ng-animate` CSS class and the
|
||||
// temporary classes before we do any driver invoking since these
|
||||
// CSS classes may be required for proper CSS detection.
|
||||
animationEntry.beforeStart();
|
||||
|
||||
var startAnimationFn, closeFn = animationEntry.close;
|
||||
|
||||
// in the event that the element was removed before the digest runs or
|
||||
// during the RAF sequencing then we should not trigger the animation.
|
||||
var targetElement = animationEntry.anchors
|
||||
@@ -221,7 +236,32 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
// we need to sort each of the animations in order of parent to child
|
||||
// relationships. This ensures that the child classes are applied at the
|
||||
// right time.
|
||||
$$rAFScheduler(sortAnimations(toBeSortedAnimations));
|
||||
var finalAnimations = sortAnimations(toBeSortedAnimations);
|
||||
for (var i = 0; i < finalAnimations.length; i++) {
|
||||
var innerArray = finalAnimations[i];
|
||||
for (var j = 0; j < innerArray.length; j++) {
|
||||
var entry = innerArray[j];
|
||||
var element = entry.element;
|
||||
|
||||
// the RAFScheduler code only uses functions
|
||||
finalAnimations[i][j] = entry.fn;
|
||||
|
||||
// the first row of elements shouldn't have a prepare-class added to them
|
||||
// since the elements are at the top of the animation hierarchy and they
|
||||
// will be applied without a RAF having to pass...
|
||||
if (i === 0) {
|
||||
element.removeData(PREPARE_CLASSES_KEY);
|
||||
continue;
|
||||
}
|
||||
|
||||
var prepareClassName = element.data(PREPARE_CLASSES_KEY);
|
||||
if (prepareClassName) {
|
||||
$$jqLite.addClass(element, prepareClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$$rAFScheduler(finalAnimations);
|
||||
});
|
||||
|
||||
return runner;
|
||||
@@ -359,10 +399,10 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
}
|
||||
|
||||
function beforeStart() {
|
||||
element.addClass(NG_ANIMATE_CLASSNAME);
|
||||
if (tempClasses) {
|
||||
$$jqLite.addClass(element, tempClasses);
|
||||
}
|
||||
tempClasses = (tempClasses ? (tempClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
|
||||
$$jqLite.addClass(element, tempClasses);
|
||||
|
||||
var prepareClassName = element.data(PREPARE_CLASSES_KEY);
|
||||
if (prepareClassName) {
|
||||
$$jqLite.removeClass(element, prepareClassName);
|
||||
prepareClassName = null;
|
||||
@@ -402,7 +442,6 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
$$jqLite.removeClass(element, tempClasses);
|
||||
}
|
||||
|
||||
element.removeClass(NG_ANIMATE_CLASSNAME);
|
||||
runner.complete(!rejected);
|
||||
}
|
||||
};
|
||||
|
||||
+36
-14
@@ -17,20 +17,28 @@
|
||||
* ## Directive Support
|
||||
* The following directives are "animation aware":
|
||||
*
|
||||
* | Directive | Supported Animations |
|
||||
* |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
|
||||
* | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
* | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
* | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
* | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
|
||||
* | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
|
||||
* | {@link ng.directive:form#animations form} & {@link ng.directive:ngModel#animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
|
||||
* | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
|
||||
* | {@link module:ngMessages#animations ngMessage} | enter and leave |
|
||||
* | Directive | Supported Animations |
|
||||
* |-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
|
||||
* | {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave |
|
||||
* | {@link ng.directive:ngClass#animations ngClass / {{class}​}} | add and remove |
|
||||
* | {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove |
|
||||
* | {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove |
|
||||
* | {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) |
|
||||
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
|
||||
* | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
|
||||
* | {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
|
||||
* | {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
|
||||
* | {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
|
||||
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
|
||||
* | {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
|
||||
* | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
|
||||
* | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
|
||||
*
|
||||
* (More information can be found by visiting each the documentation associated with each directive.)
|
||||
* (More information can be found by visiting the documentation associated with each directive.)
|
||||
*
|
||||
* For a full breakdown of the steps involved during each animation event, refer to the
|
||||
* {@link ng.$animate `$animate` API docs}.
|
||||
*
|
||||
* ## CSS-based Animations
|
||||
*
|
||||
@@ -267,9 +275,22 @@
|
||||
* .message.ng-enter-prepare {
|
||||
* opacity: 0;
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ### Animating between value changes
|
||||
*
|
||||
* Sometimes you need to animate between different expression states, whose values
|
||||
* don't necessary need to be known or referenced in CSS styles.
|
||||
* Unless possible with another {@link ngAnimate#directive-support "animation aware" directive},
|
||||
* that specific use case can always be covered with {@link ngAnimate.directive:ngAnimateSwap} as
|
||||
* can be seen in {@link ngAnimate.directive:ngAnimateSwap#examples this example}.
|
||||
*
|
||||
* Note that {@link ngAnimate.directive:ngAnimateSwap} is a *structural directive*, which means it
|
||||
* creates a new instance of the element (including any other/child directives it may have) and
|
||||
* links it to a new scope every time *swap* happens. In some cases this might not be desirable
|
||||
* (e.g. for performance reasons, or when you wish to retain internal state on the original
|
||||
* element instance).
|
||||
*
|
||||
* ## JavaScript-based Animations
|
||||
*
|
||||
* ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
|
||||
@@ -765,6 +786,7 @@ angular.module('ngAnimate', [], function initAngularHelpers() {
|
||||
.factory('$$rAFScheduler', $$rAFSchedulerFactory)
|
||||
|
||||
.provider('$$animateQueue', $$AnimateQueueProvider)
|
||||
.provider('$$animateCache', $$AnimateCacheProvider)
|
||||
.provider('$$animation', $$AnimationProvider)
|
||||
|
||||
.provider('$animateCss', $AnimateCssProvider)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user