Compare commits
563 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eba192b863 | |||
| 75099e6137 | |||
| 172a40931b | |||
| 23e5109b64 | |||
| cc84ce3bf5 | |||
| bee2d1fbb9 | |||
| 7101a02c93 | |||
| 888be00712 | |||
| 05597c24c7 | |||
| bf55f23325 | |||
| ab051e78ea | |||
| 87d46a84c3 | |||
| ae764f1844 | |||
| ad8092ed80 | |||
| 55e1b3e1c8 | |||
| 6f465a2b26 | |||
| 3ecac62251 | |||
| c64610269d | |||
| 5c2302949b | |||
| 7c84e2632f | |||
| 18d18f07db | |||
| 9f5be534fc | |||
| 1cd7912614 | |||
| 514da451fc | |||
| 2788cc4e12 | |||
| 7f6322df6a | |||
| 5bd8613168 | |||
| 32b507890e | |||
| bfedafdede | |||
| cdefbe3425 | |||
| f75f4bce82 | |||
| 4349de3d41 | |||
| df545d7eed | |||
| 3b5f346314 | |||
| 3aab87b381 | |||
| 0973175058 | |||
| 112da45c07 | |||
| e3dc85841d | |||
| ef1c352bc9 | |||
| a5b6444324 | |||
| dd18c00b1d | |||
| a0fad24dc2 | |||
| da0e3c99f5 | |||
| a41c58e285 | |||
| bce5b49133 | |||
| 816b84230c | |||
| 873acf8fab | |||
| 9063a0c2e7 | |||
| 03cbc0d6b1 | |||
| 931789ec14 | |||
| 9bc807783f | |||
| 2d6ee651b1 | |||
| 7ca24a8264 | |||
| 1d8e42070a | |||
| fe6b2fbfc4 | |||
| 5a222244fb | |||
| 4026074aba | |||
| d9a596addd | |||
| 8c0898b21c | |||
| d9b693bb7a | |||
| da94ab2e63 | |||
| 2fd8dc7061 | |||
| 01012a4d7a | |||
| ce84429adf | |||
| a26acb64fe | |||
| 7088ed1ed3 | |||
| 7027844d42 | |||
| 16c584ed7f | |||
| b586bfdfab | |||
| 1d69015e3d | |||
| f13c33bf10 | |||
| ba62e975f1 | |||
| b89d941cdf | |||
| 07fa87a8a8 | |||
| 0af70eb99e | |||
| cb713e6045 | |||
| 89ca859734 | |||
| bc6fb7cc94 | |||
| 0c80df21b6 | |||
| 3141dbf179 | |||
| aad502bad8 | |||
| dca8972367 | |||
| 284de57435 | |||
| f780ccfa1c | |||
| 55f99e0710 | |||
| 1a99ca9c08 | |||
| 37500fca83 | |||
| 4fe4fc5abf | |||
| 74e1cc683b | |||
| a4faa5cde7 | |||
| 32cb40b86d | |||
| d1cd677433 | |||
| 43c735a816 | |||
| ab2e83c8c8 | |||
| e5f454c8af | |||
| 67c11b9a39 | |||
| 5a306b7ba3 | |||
| 8ce61bf178 | |||
| 9d452bc845 | |||
| 192fecc790 | |||
| 32be6369e4 | |||
| 2a45cea0ba | |||
| ea653e4cdd | |||
| 03777445e8 | |||
| 8b25ea129a | |||
| d71f16e745 | |||
| ed59370d80 | |||
| d8e5acfe27 | |||
| af59f4e69a | |||
| 24aee81634 | |||
| f81d56e66c | |||
| f0904cf12e | |||
| 81b7e5ab0e | |||
| 1b1890274e | |||
| 6d418ef5e3 | |||
| 3fa1606c43 | |||
| 8661a9e3d4 | |||
| cf63292742 | |||
| fd420c4061 | |||
| 1382d4e88e | |||
| b9ddef2a49 | |||
| eafba9e2e5 | |||
| 6f1d9f8ca6 | |||
| bb9310974b | |||
| 30279d7b9b | |||
| 8df5f3259a | |||
| 14e797c1a1 | |||
| 82cd6b87f0 | |||
| 6d7cc572b5 | |||
| 2ebbe00eb5 | |||
| 8b86d363aa | |||
| 9b51067516 | |||
| a3208bf66e | |||
| 4e1fb82628 | |||
| ad466412c6 | |||
| 299a32740c | |||
| eb799bcb71 | |||
| 7314c1b69e | |||
| fcfe2b3793 | |||
| 5e140a99c7 | |||
| 4da169d15d | |||
| f37c6f9f73 | |||
| eae658fd96 | |||
| 286f269753 | |||
| 73640a6b7c | |||
| 2dc55ff5c2 | |||
| 98a2563ec4 | |||
| 8c02122837 | |||
| 1e069532fc | |||
| 6f6f7e82a4 | |||
| d852122442 | |||
| 66cb161221 | |||
| 6c14fb1eb6 | |||
| e906aafb0a | |||
| 6acc73f3e0 | |||
| a4367ab00d | |||
| 2e0464fba4 | |||
| 6ffd53ee3c | |||
| 2395bf604d | |||
| 2e5fe846e3 | |||
| b6388b3f1d | |||
| 669e3aeaa8 | |||
| 55b2f0e862 | |||
| ca566d8d81 | |||
| b306babe29 | |||
| d9317cde4f | |||
| 23c8af232f | |||
| 2fcbd39d0b | |||
| 3ffbf202ce | |||
| 09367d88c2 | |||
| 369f69d67a | |||
| 9f43d02af8 | |||
| 08e6b88fb2 | |||
| 9227a5db94 | |||
| fc6ce59cd2 | |||
| 96a314766c | |||
| 6aa31e17ee | |||
| d18d5f57c2 | |||
| 2d9e96772f | |||
| bc2a5aaf05 | |||
| 4547c11dad | |||
| c0b360b993 | |||
| a659049893 | |||
| a3b9b1d205 | |||
| 3305f38db2 | |||
| 9be4e035d1 | |||
| 85ce5d0db9 | |||
| 5c99720934 | |||
| 8d26238664 | |||
| b7cb454546 | |||
| 199825ec26 | |||
| bcdd925c9d | |||
| 0bcace309e | |||
| 46c9c942df | |||
| 2ad439dfc5 | |||
| 3fbfe3f966 | |||
| 8ff671753c | |||
| 24092127d1 | |||
| f1c7240f04 | |||
| fcee3bea1a | |||
| ad08638c0a | |||
| 093e76fa15 | |||
| 28c0497524 | |||
| 88505d8029 | |||
| c241a57761 | |||
| b0e985fb67 | |||
| a0dbd95bb9 | |||
| 9fd5450ee8 | |||
| b5391fae8f | |||
| b635903ec4 | |||
| c9ee20b64b | |||
| 25ae98ca77 | |||
| adb5ee2e0a | |||
| 78954ffcde | |||
| 3b30a4b64a | |||
| 866057233c | |||
| ec1f4a8c9b | |||
| c94190139d | |||
| 4896a0b4d4 | |||
| 32bd990eda | |||
| 23723298f9 | |||
| edab80cddb | |||
| f4bb006e45 | |||
| cf3f709889 | |||
| e9ecd56dca | |||
| f107ef8bd8 | |||
| e7eab501db | |||
| 97fc47f39e | |||
| 6d1c67727a | |||
| 8ba78f08b9 | |||
| 02a3c31c23 | |||
| d4c3d5caaf | |||
| 279f98c4e3 | |||
| 5e548edf67 | |||
| acaf9be685 | |||
| bdd75c97ad | |||
| a3f1cba8ec | |||
| 4195b04072 | |||
| ccc8ec869b | |||
| 6d8abaedd8 | |||
| fd49d6634c | |||
| cecd5214df | |||
| c5e39c688b | |||
| 240608447a | |||
| bcb53deda8 | |||
| 9d4fa33e35 | |||
| de1461da78 | |||
| 2866daf7d6 | |||
| b3de37e418 | |||
| a4cc9e1944 | |||
| f8a1c56cad | |||
| 113850602d | |||
| e76105a320 | |||
| 14e9be202a | |||
| dd5215eceb | |||
| 497ba08775 | |||
| eaaf4967f9 | |||
| f440ac7492 | |||
| d566c4bc61 | |||
| 766b962eac | |||
| 0388eed7e5 | |||
| 82448b86b5 | |||
| fafcd6285a | |||
| 5319621afd | |||
| 32aa491588 | |||
| 31bdb60f0a | |||
| a8aae48bc0 | |||
| 517917f9fa | |||
| 1748abe8ef | |||
| 5f5bf07bb8 | |||
| 3d0b49c07f | |||
| b6aec5642e | |||
| e44e5f447b | |||
| ca273fd9da | |||
| 5ff453d422 | |||
| d9c75bee93 | |||
| e030e64196 | |||
| 7ba19cc355 | |||
| 21428e5cea | |||
| 9321a5f14c | |||
| 72421b2acf | |||
| c3fe170b8b | |||
| ed18b8c9da | |||
| 0cb276f7ac | |||
| cfccb8f64a | |||
| 0069f87007 | |||
| 9599234bae | |||
| d423117158 | |||
| 3c8a940686 | |||
| 9f8e30f550 | |||
| dcd94a23e1 | |||
| 9b79a00294 | |||
| 02058bfbe2 | |||
| 76647d3855 | |||
| 1f1cad8517 | |||
| e1f1d65d0c | |||
| a5df2d4e36 | |||
| b6514d9e1a | |||
| 1b1f94d8fe | |||
| 1362a9b456 | |||
| 6a26b2c38c | |||
| 9ab9bf6b41 | |||
| e7e56fe9bf | |||
| 85ea376da2 | |||
| 050aae3ceb | |||
| 79d9fd9d57 | |||
| 2dc2265e4f | |||
| 9cd33df50c | |||
| 1db20ce90f | |||
| 76cb5ce7c5 | |||
| 2a778d0038 | |||
| b04d3a8ec6 | |||
| 7839330b40 | |||
| 2d7cb14a16 | |||
| f20b06d26d | |||
| 109e5d1d39 | |||
| 227822dac3 | |||
| 92f6b45e02 | |||
| 24f7999bc1 | |||
| e0203660d3 | |||
| b6eb5fdb05 | |||
| 5bf6e50b40 | |||
| a7ccb7531c | |||
| 6bea059109 | |||
| fcdac65aed | |||
| 373078a94c | |||
| db07ad2d4c | |||
| 19d7a127c7 | |||
| f5a04f59cf | |||
| ab92da43b0 | |||
| 6782c45ddc | |||
| c55477fb2b | |||
| 2e2d62ca12 | |||
| e3141fe5f4 | |||
| 1ebed26678 | |||
| e987efd4c0 | |||
| ea72e5f881 | |||
| c550c12738 | |||
| 651caffe45 | |||
| 45855b8ba2 | |||
| feb54d68d2 | |||
| 2d0f6037f7 | |||
| ebd9a2a960 | |||
| fcfa6ebb6b | |||
| cf8ed01c6e | |||
| c352b92c40 | |||
| 369145467c | |||
| c3ec6ea226 | |||
| 291684c29c | |||
| d7615351df | |||
| a97a172ee9 | |||
| d53a787f0d | |||
| 9682bd0c4e | |||
| 34d0740350 | |||
| 4d9efa2f76 | |||
| f9a7b064a0 | |||
| 26ca443c10 | |||
| 0f37e49039 | |||
| 3fdcde73ae | |||
| d2dc77169b | |||
| d99b506885 | |||
| fef0cfc837 | |||
| 5de845506b | |||
| 38ea542662 | |||
| 2db66f5b69 | |||
| 55fe6d6331 | |||
| f4c08fee85 | |||
| 71bc451f95 | |||
| 7e4e696ec3 | |||
| 6da44a4410 | |||
| a81195c6ca | |||
| 102a3201c1 | |||
| 59244a7776 | |||
| 293cb1fc5d | |||
| 81395ac298 | |||
| ba66c4f3cc | |||
| 4ea57e7e96 | |||
| 6e420ff28d | |||
| 5393814756 | |||
| fab59e7515 | |||
| 553c252d5c | |||
| e145a8df72 | |||
| b49d0cc6e7 | |||
| 97b171ecb2 | |||
| 245de33c00 | |||
| 8d4d437e8c | |||
| 7287dbf71d | |||
| da88449f25 | |||
| eaf1f8546d | |||
| 20d926cc45 | |||
| 9ae9c1c0da | |||
| 8a5972461c | |||
| 35d635cbcb | |||
| db2a4c04d6 | |||
| 0d62257c5f | |||
| f911b84aef | |||
| 32c09c1d19 | |||
| 26064375ca | |||
| 7a294369ab | |||
| fbab287ea2 | |||
| 254dcee93d | |||
| 69e5c369d7 | |||
| c0ccbb7b6a | |||
| b87713687e | |||
| 950ffb5a84 | |||
| 259003056d | |||
| fedc4194d9 | |||
| 16862705e1 | |||
| c694c96e4c | |||
| 5ac8a6e74a | |||
| 0e5106ec2c | |||
| 9091b77fe6 | |||
| 849f998be3 | |||
| 8bc77b68b3 | |||
| 95bd046881 | |||
| bfce9126e1 | |||
| bf2264e2aa | |||
| 5ced7b20ff | |||
| 114cf9e418 | |||
| 1b61b73f28 | |||
| d657d63a21 | |||
| c5bb3a9098 | |||
| 77edce5ded | |||
| f37cd4e4ef | |||
| 2acc91098b | |||
| 5b1c89931e | |||
| 99a2ad381b | |||
| 300263abec | |||
| b0bcf18892 | |||
| ea3b6310c9 | |||
| 764a3beecc | |||
| a603330e7a | |||
| 1f842b1c63 | |||
| 3b09f1bf4e | |||
| 561ddc9ff1 | |||
| 512ecf8f1b | |||
| 6636f1d03f | |||
| 7cccb8b777 | |||
| a275d539f9 | |||
| 17fa2468bc | |||
| fb2ae5660e | |||
| 2c4b3573cc | |||
| b2363e3102 | |||
| edfca4c769 | |||
| a9b5a1087d | |||
| 87b18b9fbe | |||
| ad128e09ff | |||
| 187b4adbd2 | |||
| 375c47d0c0 | |||
| 8fd47a1cd5 | |||
| e48c28fe92 | |||
| 10d3e1e447 | |||
| 93d1c95c61 | |||
| 01a34f513b | |||
| 916e53ce14 | |||
| b91b3119a4 | |||
| 0d60f8d367 | |||
| ca69dc6f17 | |||
| 5b7f1bcc00 | |||
| 9ab594a66c | |||
| 6c82a497c6 | |||
| df804406fb | |||
| dadce485a7 | |||
| 344cdce136 | |||
| f0347d5efa | |||
| 1c27e5fc37 | |||
| 5fb298b90f | |||
| 483325a7b5 | |||
| a86cb7d794 | |||
| c7e60153a5 | |||
| c0416866f5 | |||
| 8ba452544e | |||
| 8f7f0d26ed | |||
| 98d825e10d | |||
| 57b0d91fd8 | |||
| 9226b36572 | |||
| 39635fd0d7 | |||
| cad307fa1f | |||
| 78bc84c497 | |||
| 1f2750136e | |||
| 5b93e5fcfc | |||
| 770fd5a917 | |||
| 4b29186696 | |||
| 7b5be9ee29 | |||
| eeb261bcd5 | |||
| ef88a8a020 | |||
| 86ab885fd9 | |||
| de07ddeac6 | |||
| dc149de936 | |||
| 320f6d1214 | |||
| 1517d6d2f2 | |||
| 6bb17af2e3 | |||
| 505ead7e58 | |||
| 1da4e89385 | |||
| 83f37d78ba | |||
| d4ac25496a | |||
| e84da2283c | |||
| 3dd9572754 | |||
| 922cb7e42f | |||
| 103cb513d9 | |||
| c24e4e4ed5 | |||
| 1b46a7dcdf | |||
| 8d28d65b36 | |||
| fbb125a3af | |||
| ca7336391a | |||
| 771bccc35c | |||
| c794b96bdc | |||
| f108a2a994 | |||
| 7cbf61cabb | |||
| dfdb72559f | |||
| d69793d93c | |||
| b068c8b605 | |||
| ec16352579 | |||
| aa4ba23350 | |||
| 25e639b474 | |||
| ee8e4a946e | |||
| a41a2a1d2c | |||
| eadd8d08d3 | |||
| 602a1142e8 | |||
| 0b7fef3d94 | |||
| 809d47ec77 | |||
| f3444d495d | |||
| 612c882b83 | |||
| f2a6be3129 | |||
| 465663ed77 | |||
| 1102ffaaf8 | |||
| 98f6a82390 | |||
| a43c6e1828 | |||
| 0db301f863 | |||
| 8e6d3875c6 | |||
| b9d77d46ff | |||
| 96f94d4347 | |||
| 63831f118f | |||
| ebe280eede | |||
| 95d6cdc7c7 | |||
| 9223215add | |||
| 309cfd109f | |||
| cfc6175aab | |||
| dc39f368c3 | |||
| c0f3400573 | |||
| 822d7e5ae9 | |||
| 5874db84ab | |||
| e9e8d49216 | |||
| 550fc21ce5 | |||
| a8aba8957b | |||
| ca0ac64997 | |||
| 7678501bc9 | |||
| dec5eb6e83 | |||
| 2eff326781 | |||
| 50ce5746a7 | |||
| 1537f80267 | |||
| 021d3aa21a | |||
| e8c8c5459e | |||
| 1c20aed318 | |||
| 4c4d24a338 | |||
| 8c7b9b8de4 | |||
| f39ac571c4 | |||
| 84f36701bc | |||
| 6d4ce240de | |||
| 229a155aef | |||
| 73250089cd | |||
| a72bc4e69f | |||
| 0812061274 |
+2
-1
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"disallowKeywords": ["with"],
|
||||
"disallowTrailingWhitespace": true,
|
||||
"requireRightStickedOperators": ["!"]
|
||||
"requireRightStickedOperators": ["!"],
|
||||
"requireLeftStickedOperators": [","]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
|
||||
"disallowLeftStickedOperators": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
"disallowRightStickedOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
"requireLeftStickedOperators": [","],
|
||||
"disallowImplicitTypeConversion": ["string"],
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
|
||||
+10
-1
@@ -1,6 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.10
|
||||
- '0.10'
|
||||
|
||||
branches:
|
||||
except:
|
||||
@@ -18,6 +18,15 @@ env:
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
|
||||
|
||||
install:
|
||||
# - npm config set registry http://23.251.144.68
|
||||
# Disable the spinner, it looks bad on Travis
|
||||
- npm config set spin false
|
||||
# Log HTTP requests
|
||||
- npm config set loglevel http
|
||||
# Run npm install twice, because it is flaky.
|
||||
- npm install || npm install
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
- ./lib/sauce/sauce_connect_setup.sh
|
||||
|
||||
+802
-17
@@ -1,3 +1,788 @@
|
||||
|
||||
<a name="1.3.0-beta.15"></a>
|
||||
# 1.3.0-beta.15 unbelievable-advancement (2014-07-11)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- ensure that parallel class-based animations are all eventually closed
|
||||
([f07af61f](https://github.com/angular/angular.js/commit/f07af61f050fcdcece15c13ee8c6a6d32f86d3a1),
|
||||
[#7766](https://github.com/angular/angular.js/issues/7766))
|
||||
- remove the ng-animate className after canceling animation
|
||||
([e18db78d](https://github.com/angular/angular.js/commit/e18db78d7793b1e94d9b19ac15b89d39f21a5729),
|
||||
[#7784](https://github.com/angular/angular.js/issues/7784), [#7801](https://github.com/angular/angular.js/issues/7801), [#7894](https://github.com/angular/angular.js/issues/7894))
|
||||
- **$http:**
|
||||
- don't remove content-type header if data is set by request transform
|
||||
([c7c363cf](https://github.com/angular/angular.js/commit/c7c363cf8d4533f94c5534c83dd1c7135633ddd8),
|
||||
[#7910](https://github.com/angular/angular.js/issues/7910))
|
||||
- add ability to remove default headers
|
||||
([172a4093](https://github.com/angular/angular.js/commit/172a40931be5fe47e7732e5ba173895a1d59c5cd),
|
||||
[#5784](https://github.com/angular/angular.js/issues/5784))
|
||||
- **$location:** remove query args when passed in object
|
||||
([2c7d0857](https://github.com/angular/angular.js/commit/2c7d0857ccbdb3a0967acc20e4346a7e1a6be792),
|
||||
[#6565](https://github.com/angular/angular.js/issues/6565))
|
||||
- **input:**
|
||||
- escape forward slash in email regexp
|
||||
([a88c215f](https://github.com/angular/angular.js/commit/a88c215f17829c1cfdec36bc1ef40bae10c41dff),
|
||||
[#8096](https://github.com/angular/angular.js/issues/8096))
|
||||
- modify email validation regexp to match rfc1035
|
||||
([af6f943a](https://github.com/angular/angular.js/commit/af6f943a22f26cf2968f0ae3a1fab2fd09b52a2b),
|
||||
[#6026](https://github.com/angular/angular.js/issues/6026))
|
||||
- **jqLite:**
|
||||
- correctly dealoc svg elements in IE
|
||||
([012ab1f8](https://github.com/angular/angular.js/commit/012ab1f8745c8985d3f132c2dfa8fd84e7dc7041))
|
||||
- remove exposed dealoc method
|
||||
([9c5b407f](https://github.com/angular/angular.js/commit/9c5b407fd1e296dd525c129743f2b2b47da4dc0d))
|
||||
- **ngModel:** test & update correct model when running $validate
|
||||
([f3cb2741](https://github.com/angular/angular.js/commit/f3cb2741161353f387d02725637ce4ba062a9bc0),
|
||||
[#7836](https://github.com/angular/angular.js/issues/7836), [#7837](https://github.com/angular/angular.js/issues/7837))
|
||||
- **parseKeyValue:** ignore properties in prototype chain
|
||||
([cb42766a](https://github.com/angular/angular.js/commit/cb42766a14f8123aa288b6e20f879141970fb84d),
|
||||
[#8070](https://github.com/angular/angular.js/issues/8070), [#8068](https://github.com/angular/angular.js/issues/8068))
|
||||
- **select:** auto-select new option that is marked as selected
|
||||
([b8ae73e1](https://github.com/angular/angular.js/commit/b8ae73e17c19d9aebf572a75c05a7d981dcac807),
|
||||
[#6828](https://github.com/angular/angular.js/issues/6828))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$animate:** allow directives to cancel animation events
|
||||
([ca752790](https://github.com/angular/angular.js/commit/ca752790d95480b7ad1125a7ddb52b726b987a24),
|
||||
[#7722](https://github.com/angular/angular.js/issues/7722))
|
||||
- **$controller:** disable using global controller constructors
|
||||
([3f2232b5](https://github.com/angular/angular.js/commit/3f2232b5a181512fac23775b1df4a6ebda67d018))
|
||||
- **FormController:** add `$rollbackViewValue` to rollback all controls
|
||||
([85b77314](https://github.com/angular/angular.js/commit/85b77314ed8e4b45d7365a24a47349ed94672aeb),
|
||||
[#7595](https://github.com/angular/angular.js/issues/7595))
|
||||
- **input:** support constant expressions for ngTrueValue/ngFalseValue
|
||||
([c90cefe1](https://github.com/angular/angular.js/commit/c90cefe16142d973a123e945fc9058e8a874c357),
|
||||
[#8041](https://github.com/angular/angular.js/issues/8041), [#5346](https://github.com/angular/angular.js/issues/5346), [#1199](https://github.com/angular/angular.js/issues/1199))
|
||||
- **ngAnimate:** conditionally allow child animations to run in parallel with parent animations
|
||||
([8252b8be](https://github.com/angular/angular.js/commit/8252b8be946367f1759065adf528adc908da00a2),
|
||||
[#7946](https://github.com/angular/angular.js/issues/7946))
|
||||
- **ngModel:** bind to getters/setters
|
||||
([b9fcf017](https://github.com/angular/angular.js/commit/b9fcf017316d37e91959949f56692644ce09d54a),
|
||||
[#768](https://github.com/angular/angular.js/issues/768))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** no longer need nodeType filter when setting $scope data
|
||||
([b0ca5195](https://github.com/angular/angular.js/commit/b0ca5195e88a42611e933c49d7d2768b181b2d1b),
|
||||
[#7887](https://github.com/angular/angular.js/issues/7887))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$controller:** due to [3f2232b5](https://github.com/angular/angular.js/commit/3f2232b5a181512fac23775b1df4a6ebda67d018),
|
||||
|
||||
`$controller` will no longer look for controllers on `window`.
|
||||
The old behavior of looking on `window` for controllers was originally intended
|
||||
for use in examples, demos, and toy apps. We found that allowing global controller
|
||||
functions encouraged poor practices, so we resolved to disable this behavior by
|
||||
default.
|
||||
|
||||
To migrate, register your controllers with modules rather than exposing them
|
||||
as globals:
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
function MyController() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
angular.module('myApp', []).controller('MyController', [function() {
|
||||
// ...
|
||||
}]);
|
||||
```
|
||||
|
||||
Although it's not recommended, you can re-enable the old behavior like this:
|
||||
|
||||
```javascript
|
||||
angular.module('myModule').config(['$controllerProvider', function($controllerProvider) {
|
||||
// this option might be handy for migrating old apps, but please don't use it
|
||||
// in new ones!
|
||||
$controllerProvider.allowGlobals();
|
||||
}]);
|
||||
```
|
||||
- **input:** due to [c90cefe1](https://github.com/angular/angular.js/commit/c90cefe16142d973a123e945fc9058e8a874c357),
|
||||
|
||||
|
||||
Previously, these attributes would always be treated as strings. However, they are now parsed as
|
||||
expressions, and will throw if an expression is non-constant.
|
||||
|
||||
To convert non-constant strings into constant expressions, simply wrap them in an extra pair of quotes, like so:
|
||||
|
||||
<input type="checkbox" ng-model="..." ng-true-value="'truthyValue'">
|
||||
|
||||
Closes #8041
|
||||
Closes #5346
|
||||
Closes #1199
|
||||
|
||||
<a name="1.2.20"></a>
|
||||
# 1.2.20 accidental-beautification (2014-07-11)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$http:**
|
||||
- don't remove content-type header if data is set by request transform
|
||||
([7027844d](https://github.com/angular/angular.js/commit/7027844d42cd428cb799f38f9e9b303da013ac4f),
|
||||
[#7910](https://github.com/angular/angular.js/issues/7910))
|
||||
- add ability to remove default headers
|
||||
([172a4093](https://github.com/angular/angular.js/commit/172a40931be5fe47e7732e5ba173895a1d59c5cd),
|
||||
[#5784](https://github.com/angular/angular.js/issues/5784))
|
||||
- **$location:** remove query args when passed in object
|
||||
([a26acb64](https://github.com/angular/angular.js/commit/a26acb64fe2ed3e05bf21ac1c058d6ac59b89870),
|
||||
[#6565](https://github.com/angular/angular.js/issues/6565))
|
||||
- **input:**
|
||||
- escape forward slash in email regexp
|
||||
([da0e3c99](https://github.com/angular/angular.js/commit/da0e3c99f51c196f58758841d4d8492a9fa09e20),
|
||||
[#8096](https://github.com/angular/angular.js/issues/8096))
|
||||
- modify email validation regexp to match rfc1035
|
||||
([816b8423](https://github.com/angular/angular.js/commit/816b84230cdd8273ba19e8dec3b6f2e800f76612),
|
||||
[#6026](https://github.com/angular/angular.js/issues/6026))
|
||||
- **parseKeyValue:** ignore properties in prototype chain
|
||||
([873acf8f](https://github.com/angular/angular.js/commit/873acf8fab3eb41914920259e713e1916e3c4f38),
|
||||
[#8070](https://github.com/angular/angular.js/issues/8070), [#8068](https://github.com/angular/angular.js/issues/8068))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngAnimate:** conditionally allow child animations to run in parallel with parent animations
|
||||
([931789ec](https://github.com/angular/angular.js/commit/931789ec1476e1d06739e63cb423eb87172b5ebc),
|
||||
[#7946](https://github.com/angular/angular.js/issues/7946))
|
||||
|
||||
|
||||
<a name="1.3.0-beta.14"></a>
|
||||
# 1.3.0-beta.14 harmonious-cacophonies (2014-06-30)
|
||||
|
||||
|
||||
This release contains security fixes for $parse that prevent arbitrary code execution via Angular
|
||||
expressions under some very specific conditions. The only applications affected by these
|
||||
vulnerabilities are those that match all of the following conditions:
|
||||
|
||||
- application mixes server-side and client-side templating
|
||||
- the server-side templating contains XSS vulnerabilities
|
||||
- the vulnerabilities in the server-side templating are being guarded by server-side XSS filters or
|
||||
on the client-side via [CSP](http://en.wikipedia.org/wiki/Content_Security_Policy)
|
||||
- the server-side XSS vulnerabilities can be used to augment the client-side template processed by
|
||||
Angular
|
||||
|
||||
Applications not meeting all of the conditions are not vulnerable.
|
||||
|
||||
This fix is in both 1.3.0-beta.14 and 1.2.19 release.
|
||||
|
||||
The Angular team would like to thank [Jann Horn](http://thejh.net) for reporting these
|
||||
vulnerabilities via [security@angularjs.org].
|
||||
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** bind ng-attr-* even if unbound attribute follows ng-attr-*
|
||||
([8b0258d8](https://github.com/angular/angular.js/commit/8b0258d878cac20cd25c0958fd6e136a08b97df6),
|
||||
[#7739](https://github.com/angular/angular.js/issues/7739))
|
||||
- **$http:**
|
||||
- should not read statusText on IE<10 when request is aborted
|
||||
([31ae3e71](https://github.com/angular/angular.js/commit/31ae3e71647eadbbe1df40f9dedb55e1e0715f98))
|
||||
- add the PATCH shortcut back
|
||||
([b28b5caa](https://github.com/angular/angular.js/commit/b28b5caab1529b3970f10f0a4de43c0c975e3886),
|
||||
[#5894](https://github.com/angular/angular.js/issues/5894))
|
||||
- **$injector:** check if a fn is an array explicitly
|
||||
([b1a6baac](https://github.com/angular/angular.js/commit/b1a6baac2de84a1ecdc000085e8bbd016eb5c100),
|
||||
[#7904](https://github.com/angular/angular.js/issues/7904), [#2653](https://github.com/angular/angular.js/issues/2653))
|
||||
- **$interval:** when canceling, use clearInterval from $window instead of global scope.
|
||||
([a4904c0f](https://github.com/angular/angular.js/commit/a4904c0f83838222b98a875c56779a7f1a4a650a))
|
||||
- **$parse:**
|
||||
- prevent invocation of Function's bind, call and apply
|
||||
([77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5))
|
||||
- forbid __proto__ properties in angular expressions
|
||||
([6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99))
|
||||
- forbid __{define,lookup}{Getter,Setter}__ properties
|
||||
([48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6))
|
||||
- forbid referencing Object in angular expressions
|
||||
([528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc))
|
||||
- handle constants as one-time binding expressions
|
||||
([d9763f1b](https://github.com/angular/angular.js/commit/d9763f1bd355190b9d4e5723e4632cbc232f0543),
|
||||
[#7970](https://github.com/angular/angular.js/issues/7970))
|
||||
- **$timeout/$interval:** if invokeApply is false, do not use evalAsync
|
||||
([19b6b343](https://github.com/angular/angular.js/commit/19b6b3433ae9f8523cbc72ae97dbcf0c06960148),
|
||||
[#7999](https://github.com/angular/angular.js/issues/7999), [#7103](https://github.com/angular/angular.js/issues/7103))
|
||||
- **Angular:** nodeName should always be lowercase
|
||||
([dafb8a3c](https://github.com/angular/angular.js/commit/dafb8a3cd12e7c3247838f536c25eb796331658d),
|
||||
[#3987](https://github.com/angular/angular.js/issues/3987))
|
||||
- **Angular.copy:** preserve prototype chain when copying objects
|
||||
([b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
|
||||
[#5063](https://github.com/angular/angular.js/issues/5063), [#3767](https://github.com/angular/angular.js/issues/3767), [#4996](https://github.com/angular/angular.js/issues/4996))
|
||||
- **core:** drop the toBoolean function
|
||||
([bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
|
||||
[#3969](https://github.com/angular/angular.js/issues/3969), [#4277](https://github.com/angular/angular.js/issues/4277), [#7960](https://github.com/angular/angular.js/issues/7960))
|
||||
- **injector:** allow multiple loading of function modules
|
||||
([2f0a4488](https://github.com/angular/angular.js/commit/2f0a4488731fdb0e8217325dbb52a576defd09bd),
|
||||
[#7255](https://github.com/angular/angular.js/issues/7255))
|
||||
- **input:**
|
||||
- improve html5 validation support
|
||||
([1f6a5a1a](https://github.com/angular/angular.js/commit/1f6a5a1a9255a2db19a1ea4c04cdbcdbb2850b6c),
|
||||
[#7936](https://github.com/angular/angular.js/issues/7936), [#7937](https://github.com/angular/angular.js/issues/7937))
|
||||
- escape forward slash in email regexp
|
||||
([b775e2bc](https://github.com/angular/angular.js/commit/b775e2bca1093e9df62a269b5bda968555ea0ded),
|
||||
[#7938](https://github.com/angular/angular.js/issues/7938))
|
||||
- **jqLite:**
|
||||
- never add to the cache for non-element/document nodes
|
||||
([91754a76](https://github.com/angular/angular.js/commit/91754a76e0ef9a7456a5b9819d1c5807c0a575bb),
|
||||
[#7966](https://github.com/angular/angular.js/issues/7966))
|
||||
- don't attach event handlers to comments or text nodes
|
||||
([462dbb20](https://github.com/angular/angular.js/commit/462dbb2016a218d84760b6da171f1b15c9e416c3),
|
||||
[#7913](https://github.com/angular/angular.js/issues/7913), [#7942](https://github.com/angular/angular.js/issues/7942))
|
||||
- convert NodeList to an Array to make PhantomJS 1.x happy
|
||||
([ceaea861](https://github.com/angular/angular.js/commit/ceaea861ebec957c99bbca6fd88ed33fbc15afbf),
|
||||
[#7851](https://github.com/angular/angular.js/issues/7851))
|
||||
- **numberFilter:** correctly round fractions despite floating-point arithmetics issues in JS
|
||||
([189cd064](https://github.com/angular/angular.js/commit/189cd064feeb710fe54ee2ca83449b3eaf82b403),
|
||||
[#7870](https://github.com/angular/angular.js/issues/7870), [#7878](https://github.com/angular/angular.js/issues/7878))
|
||||
- **testabilityPatch:** fix invocations of angular.mock.dump
|
||||
([e8e07502](https://github.com/angular/angular.js/commit/e8e07502776e48bf48b83a836f7422d164cbb1d7))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **NgModel:**
|
||||
- port the email input type to use the validators pipeline
|
||||
([67379242](https://github.com/angular/angular.js/commit/6737924210570e8369ab72415e3098c6df4d3f6b))
|
||||
- port the URL input type to use the validators pipeline
|
||||
([3ee65730](https://github.com/angular/angular.js/commit/3ee65730639fc61d76e1055a6ca74e35eb48b838))
|
||||
- **jqLite:** support isDefaultPrevented for triggerHandler dummies
|
||||
([7e71acd1](https://github.com/angular/angular.js/commit/7e71acd1781ed44a7306d94338388c90f4420a24),
|
||||
[#8008](https://github.com/angular/angular.js/issues/8008))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **forEach:** use native for loop instead of forEach for Arrays
|
||||
([36625de0](https://github.com/angular/angular.js/commit/36625de0d3ebc1fc091af474d942c6ce16b0a1c0))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$parse:**
|
||||
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
|
||||
|
||||
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
|
||||
This is to disallow changing the behaviour of existing functions
|
||||
in an unforseen fashion.
|
||||
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
|
||||
|
||||
The (deprecated) __proto__ propery does not work inside angular expressions
|
||||
anymore.
|
||||
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
|
||||
|
||||
This prevents the use of __{define,lookup}{Getter,Setter}__ inside angular
|
||||
expressions. If you really need them for some reason, please wrap/bind them to make them
|
||||
less dangerous, then make them available through the scope object.
|
||||
- due to [528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc),
|
||||
|
||||
This prevents the use of `Object` inside angular expressions.
|
||||
If you need Object.keys, make it accessible in the scope.
|
||||
- **Angular.copy:** due to [b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
|
||||
|
||||
|
||||
This changes `angular.copy` so that it applies the prototype of the original
|
||||
object to the copied object. Previously, `angular.copy` would copy properties
|
||||
of the original object's prototype chain directly onto the copied object.
|
||||
|
||||
This means that if you iterate over only the copied object's `hasOwnProperty`
|
||||
properties, it will no longer contain the properties from the prototype.
|
||||
This is actually much more reasonable behaviour and it is unlikely that
|
||||
applications are actually relying on this.
|
||||
|
||||
If this behaviour is relied upon, in an app, then one should simply iterate
|
||||
over all the properties on the object (and its inherited properties) and
|
||||
not filter them with `hasOwnProperty`.
|
||||
|
||||
**Be aware that this change also uses a feature that is not compatible with
|
||||
IE8.** If you need this to work on IE8 then you would need to provide a polyfill
|
||||
for `Object.create` and `Object.getPrototypeOf`.
|
||||
- **core:** due to [bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
|
||||
values 'f', '0', 'false', 'no', 'n', '[]' are no longer
|
||||
treated as falsy. Only JavaScript falsy values are now treated as falsy by the
|
||||
expression parser; there are six of them: false, null, undefined, NaN, 0 and "".
|
||||
|
||||
Closes #3969
|
||||
Closes #4277
|
||||
Closes #7960
|
||||
|
||||
<a name="1.2.19"></a>
|
||||
# 1.2.19 precognitive-flashbacks (2014-06-30)
|
||||
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** bind ng-attr-* even if unbound attribute follows ng-attr-*
|
||||
([ed59370d](https://github.com/angular/angular.js/commit/ed59370d805a88c9ac012a8e417faf2a9f902776))
|
||||
- **$http:** should not read statusText on IE<10 when request is aborted
|
||||
([0c80df21](https://github.com/angular/angular.js/commit/0c80df21b66f4b147b6b55c27ad794be5802b411))
|
||||
- **$injector:** check if a fn is an array explicitly
|
||||
([67c11b9a](https://github.com/angular/angular.js/commit/67c11b9a3914a24aaf72f36bbe038ba5efa7ddf3),
|
||||
[#7904](https://github.com/angular/angular.js/issues/7904), [#2653](https://github.com/angular/angular.js/issues/2653))
|
||||
- **$interval:** when canceling, use clearInterval from $window instead of global scope.
|
||||
([f780ccfa](https://github.com/angular/angular.js/commit/f780ccfa1c9a8d4c6191b0756ff77dc5749cf8c5))
|
||||
- **$parse:**
|
||||
- make the window check in ensureSafeObject IE8 friendly
|
||||
([ba62e975](https://github.com/angular/angular.js/commit/ba62e975f1a0cebf08dedbb1501f72b166af66db))
|
||||
- prevent invocation of Function's bind, call and apply
|
||||
([07fa87a8](https://github.com/angular/angular.js/commit/07fa87a8a82b8be155d8c898bb79e5d9277adfb4))
|
||||
- forbid __proto__ properties in angular expressions
|
||||
([cb713e60](https://github.com/angular/angular.js/commit/cb713e6045413a25b54ad3267476fa29efd70646))
|
||||
- forbid __{define,lookup}{Getter,Setter}__ properties
|
||||
([89ca8597](https://github.com/angular/angular.js/commit/89ca8597341aa5585bcf728fa677022b7ec9c071))
|
||||
- forbid referencing Object in angular expressions
|
||||
([bc6fb7cc](https://github.com/angular/angular.js/commit/bc6fb7cc94afddcb11b94f74d13812a6be1cdb64))
|
||||
- **injector:** allow multiple loading of function modules
|
||||
([d71f16e7](https://github.com/angular/angular.js/commit/d71f16e7459f1d3705ccf47a13227d4727be9670),
|
||||
[#7255](https://github.com/angular/angular.js/issues/7255))
|
||||
- **input:**
|
||||
- improve html5 validation support
|
||||
([ab2e83c8](https://github.com/angular/angular.js/commit/ab2e83c8c8fa60ca15b1a9539a6587dc363b20f1),
|
||||
[#7937](https://github.com/angular/angular.js/issues/7937), [#7957](https://github.com/angular/angular.js/issues/7957))
|
||||
- escape forward slash in email regexp
|
||||
([2a45cea0](https://github.com/angular/angular.js/commit/2a45cea0baaf615b799b54897bfe40d32381e7a2),
|
||||
[#7938](https://github.com/angular/angular.js/issues/7938))
|
||||
- **jqLite:** change expando property to a more unique name
|
||||
([74e1cc68](https://github.com/angular/angular.js/commit/74e1cc683be315f6db05e22e185b3d27460d132a))
|
||||
- **numberFilter:** correctly round fractions despite floating-point arithmetics issues in JS
|
||||
([e5f454c8](https://github.com/angular/angular.js/commit/e5f454c8afc15336dc1faa52704a483cedfacd4a),
|
||||
[#7870](https://github.com/angular/angular.js/issues/7870), [#7878](https://github.com/angular/angular.js/issues/7878))
|
||||
- **testabilityPatch:** fix invocations of angular.mock.dump
|
||||
([5e944a1c](https://github.com/angular/angular.js/commit/5e944a1cf1356bd069d3616f24323a0cb3ace87c))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **jqLite:** don't use reflection to access expandoId
|
||||
([a4faa5cd](https://github.com/angular/angular.js/commit/a4faa5cde722556bd41d75daf346c63a9b6962e9))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$parse:**
|
||||
- due to [07fa87a8](https://github.com/angular/angular.js/commit/07fa87a8a82b8be155d8c898bb79e5d9277adfb4),
|
||||
|
||||
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
|
||||
This is to disallow changing the behaviour of existing functions
|
||||
in an unforseen fashion.
|
||||
- due to [cb713e60](https://github.com/angular/angular.js/commit/cb713e6045413a25b54ad3267476fa29efd70646),
|
||||
|
||||
The (deprecated) __proto__ propery does not work inside angular expressions
|
||||
anymore.
|
||||
- due to [89ca8597](https://github.com/angular/angular.js/commit/89ca8597341aa5585bcf728fa677022b7ec9c071),
|
||||
|
||||
This prevents the use of __{define,lookup}{Getter,Setter}__ inside angular
|
||||
expressions. If you really need them for some reason, please wrap/bind them to make them
|
||||
less dangerous, then make them available through the scope object.
|
||||
- due to [bc6fb7cc](https://github.com/angular/angular.js/commit/bc6fb7cc94afddcb11b94f74d13812a6be1cdb64),
|
||||
|
||||
This prevents the use of `Object` inside angular expressions.
|
||||
If you need Object.keys, make it accessible in the scope.
|
||||
|
||||
<a name="1.3.0-beta.13"></a>
|
||||
# 1.3.0-beta.13 idiosyncratic-numerification (2014-06-16)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **jqLite:** change expando property to a more unique name
|
||||
([20c3c9e2](https://github.com/angular/angular.js/commit/20c3c9e25f6417773333727549ed2ca2d3505b44))
|
||||
|
||||
|
||||
|
||||
<a name="1.3.0-beta.12"></a>
|
||||
# 1.3.0-beta.12 ephemeral-acceleration (2014-06-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- ensure transclude works at root of templateUrl
|
||||
([398053c5](https://github.com/angular/angular.js/commit/398053c56352487751d14ea41b3b892960397019),
|
||||
[#7183](https://github.com/angular/angular.js/issues/7183), [#7772](https://github.com/angular/angular.js/issues/7772))
|
||||
- always error if two directives add isolate-scope and new-scope
|
||||
([2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
|
||||
[#4402](https://github.com/angular/angular.js/issues/4402), [#4421](https://github.com/angular/angular.js/issues/4421))
|
||||
- **$injector:** report circularity in circular dependency error message
|
||||
([545d22b4](https://github.com/angular/angular.js/commit/545d22b47006c1efa420ba551d4850affdba8016),
|
||||
[#7500](https://github.com/angular/angular.js/issues/7500))
|
||||
- **$parse:** Handle one-time to `null`
|
||||
([600a41a7](https://github.com/angular/angular.js/commit/600a41a7b65f2dd139664fca6331c40451db75be),
|
||||
[#7743](https://github.com/angular/angular.js/issues/7743), [#7787](https://github.com/angular/angular.js/issues/7787))
|
||||
- **NgModel:**
|
||||
- ensure pattern and ngPattern use the same validator
|
||||
([1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440))
|
||||
- make ngMinlength and ngMaxlength as standalone directives
|
||||
([26d91b65](https://github.com/angular/angular.js/commit/26d91b653ac224d9d4166fea855346f5e4c4a7b4),
|
||||
[#6750](https://github.com/angular/angular.js/issues/6750))
|
||||
- make sure the ngMinlength and ngMaxlength validators use the $validators pipeline
|
||||
([5b8e7ecf](https://github.com/angular/angular.js/commit/5b8e7ecfeb722cfc7a5d92f05b57950a2aa6158b),
|
||||
[#6304](https://github.com/angular/angular.js/issues/6304))
|
||||
- make sure the pattern validator uses the $validators pipeline
|
||||
([e63d4253](https://github.com/angular/angular.js/commit/e63d4253d06ed7d344358e2c0b03311c548bc978))
|
||||
- make sure the required validator uses the $validators pipeline
|
||||
([e53554a0](https://github.com/angular/angular.js/commit/e53554a0e238cba7a150fd7ccf61e5e4cc0c0426),
|
||||
[#5164](https://github.com/angular/angular.js/issues/5164))
|
||||
- **jqLite:** data should store data only on Element and Document nodes
|
||||
([a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c))
|
||||
- **ngResource:** don't convert literal values into Resource objects when isArray is true
|
||||
([16dfcb61](https://github.com/angular/angular.js/commit/16dfcb61aed28cdef3bfbed540e2deea6d9e9632),
|
||||
[#6314](https://github.com/angular/angular.js/issues/6314), [#7741](https://github.com/angular/angular.js/issues/7741))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **NgModel:** introduce the $validators pipeline
|
||||
([a8c7cb81](https://github.com/angular/angular.js/commit/a8c7cb81c9e67b52d5c649bf3d8cec06c5976852))
|
||||
- **attrs:** trigger observers for specific ng-attributes
|
||||
([d9b90d7c](https://github.com/angular/angular.js/commit/d9b90d7c10a8e1bacbee0aeb7e86093cca9e8ed2),
|
||||
[#7758](https://github.com/angular/angular.js/issues/7758))
|
||||
- **input:** add $touched and $untouched states
|
||||
([adcc5a00](https://github.com/angular/angular.js/commit/adcc5a00bf582d2b291c18e99093bb0854f7217c))
|
||||
- **ngInclude:** emit $includeContentError when HTTP request fails
|
||||
([e4419daf](https://github.com/angular/angular.js/commit/e4419daf705d6d2d116ced573f72c24b5c53be1f),
|
||||
[#5803](https://github.com/angular/angular.js/issues/5803))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** move ng-binding class stamping for interpolation into compile phase
|
||||
([35358fdd](https://github.com/angular/angular.js/commit/35358fddc10652ef78c72cba7b7c2d5a810631d5))
|
||||
- **$http:** move xsrf cookie check to after cache check in $http
|
||||
([dd1d189e](https://github.com/angular/angular.js/commit/dd1d189ee785a37fe1d9bddf3818152db6aa210a),
|
||||
[#7717](https://github.com/angular/angular.js/issues/7717))
|
||||
- **Scope:** change Scope#id to be a simple number
|
||||
([8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d))
|
||||
- **forEach:** cache array length
|
||||
([55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5))
|
||||
- **isArray:** use native Array.isArray
|
||||
([751ebc17](https://github.com/angular/angular.js/commit/751ebc17f7fc7be26613db0a3cdee05fc401318b),
|
||||
[#7735](https://github.com/angular/angular.js/issues/7735))
|
||||
- **isWindow** optimize internal isWindow call
|
||||
([b68ac4cb](https://github.com/angular/angular.js/commit/b68ac4cb4c172447ba0022fe6e7ce0ca4cb9407e))
|
||||
- **jqLite:**
|
||||
- cache collection length for all methods that work on a single element
|
||||
([41d2eba5](https://github.com/angular/angular.js/commit/41d2eba5f8322903247280000bfc5e5e8a1c1a3e))
|
||||
- improve performance of jqLite#text
|
||||
([92489886](https://github.com/angular/angular.js/commit/92489886dcce3bca00fe827aeb0817297b8a175c))
|
||||
- optimize adding nodes to a jqLite collection
|
||||
([31faeaa7](https://github.com/angular/angular.js/commit/31faeaa7293716251ed437fa54432bb89d9d48de))
|
||||
- optimize element dealocation
|
||||
([e35abc9d](https://github.com/angular/angular.js/commit/e35abc9d2fac0471cbe8089dc0e33a72b8029ada))
|
||||
- don't use reflection to access expandoId
|
||||
([ea9a130a](https://github.com/angular/angular.js/commit/ea9a130a43d165f4f4389d01ac409dd3047efcb4))
|
||||
- **ngBind:** set the ng-binding class during compilation instead of linking
|
||||
([fd5f3896](https://github.com/angular/angular.js/commit/fd5f3896764107635310ae52df1d80a6e08fba31))
|
||||
- **shallowCopy:** use Object.keys to improve performance
|
||||
([04468db4](https://github.com/angular/angular.js/commit/04468db44185e3d7968abdb23d77bf623cb5021b))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
|
||||
|
||||
|
||||
Requesting isolate scope and any other scope on a single element is an error.
|
||||
Before this change, the compiler let two directives request a child scope
|
||||
and an isolate scope if the compiler applied them in the order of non-isolate
|
||||
scope directive followed by isolate scope directive.
|
||||
|
||||
Now the compiler will error regardless of the order.
|
||||
|
||||
If you find that your code is now throwing a `$compile:multidir` error,
|
||||
check that you do not have directives on the same element that are trying
|
||||
to request both an isolate and a non-isolate scope and fix your code.
|
||||
|
||||
Closes #4402
|
||||
Closes #4421
|
||||
- **NgModel:** due to [1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440),
|
||||
|
||||
|
||||
If an expression is used on ng-pattern (such as `ng-pattern="exp"`) or on the
|
||||
pattern attribute (something like on `pattern="{{ exp }}"`) and the expression
|
||||
itself evaluates to a string then the validator will not parse the string as a
|
||||
literal regular expression object (a value like `/abc/i`). Instead, the entire
|
||||
string will be created as the regular expression to test against. This means
|
||||
that any expression flags will not be placed on the RegExp object. To get around
|
||||
this limitation, use a regular expression object as the value for the expression.
|
||||
|
||||
//before
|
||||
$scope.exp = '/abc/i';
|
||||
|
||||
//after
|
||||
$scope.exp = /abc/i;
|
||||
- **Scope:** due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
|
||||
Scope#$id is now of time number rather than string. Since the
|
||||
id is primarily being used for debugging purposes this change should not affect
|
||||
anyone.
|
||||
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
|
||||
forEach will iterate only over the initial number of items in
|
||||
the array. So if items are added to the array during the iteration, these won't
|
||||
be iterated over during the initial forEach call.
|
||||
|
||||
This change also makes our forEach behave more like Array#forEach.
|
||||
- **jqLite:** due to [a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c),
|
||||
previously it was possible to set jqLite data on Text/Comment
|
||||
nodes, but now that is allowed only on Element and Document nodes just like in
|
||||
jQuery. We don't expect that app code actually depends on this accidental feature.
|
||||
|
||||
|
||||
|
||||
<a name="1.2.18"></a>
|
||||
# 1.2.18 ear-extendability (2014-06-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- ensure transclude works at root of templateUrl
|
||||
([fd420c40](https://github.com/angular/angular.js/commit/fd420c40613d02b3a3f7b14d00a98664518c28f0),
|
||||
[#7183](https://github.com/angular/angular.js/issues/7183), [#7772](https://github.com/angular/angular.js/issues/7772))
|
||||
- bound transclusion to correct scope
|
||||
([1382d4e8](https://github.com/angular/angular.js/commit/1382d4e88ec486b7749e45e6ccc864b3ec388cfe))
|
||||
- don't pass transcludes to non-transclude templateUrl directives
|
||||
([b9ddef2a](https://github.com/angular/angular.js/commit/b9ddef2a495b44cb5fe678b8753de0b7a369244d))
|
||||
- don't pass transclude to template of non-transclude directive
|
||||
([eafba9e2](https://github.com/angular/angular.js/commit/eafba9e2e5ddc668c534e930d83031d2e8dc32b9))
|
||||
- fix nested isolated transclude directives
|
||||
([bb931097](https://github.com/angular/angular.js/commit/bb9310974b6765c2b87e74ee7b8485a6e9c24740),
|
||||
[#1809](https://github.com/angular/angular.js/issues/1809), [#7499](https://github.com/angular/angular.js/issues/7499))
|
||||
- pass transcludeFn down to nested transclude directives
|
||||
([8df5f325](https://github.com/angular/angular.js/commit/8df5f3259aa776f28bf3d869fb1c03e10a897c84),
|
||||
[#7240](https://github.com/angular/angular.js/issues/7240), [#7387](https://github.com/angular/angular.js/issues/7387))
|
||||
- **$injector:** report circularity in circular dependency error message
|
||||
([14e797c1](https://github.com/angular/angular.js/commit/14e797c1a10eabd15bf8e845b62213398bcc0f58),
|
||||
[#7500](https://github.com/angular/angular.js/issues/7500))
|
||||
- **ngResource:** don't convert literal values into Resource objects when isArray is true
|
||||
([f0904cf1](https://github.com/angular/angular.js/commit/f0904cf12e4f01daa2d4fcbb20c762050125ca55),
|
||||
[#6314](https://github.com/angular/angular.js/issues/6314), [#7741](https://github.com/angular/angular.js/issues/7741))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** move ng-binding class stamping for interpolation into compile phase
|
||||
([81b7e5ab](https://github.com/angular/angular.js/commit/81b7e5ab0ee3fea410b16b09144359ceb99f5191))
|
||||
- **$http:** move xsrf cookie check to after cache check in $http
|
||||
([8b86d363](https://github.com/angular/angular.js/commit/8b86d363aa252c3264201b54b57c3e34f9632d45),
|
||||
[#7717](https://github.com/angular/angular.js/issues/7717))
|
||||
- **isArray:** use native Array.isArray
|
||||
([6c14fb1e](https://github.com/angular/angular.js/commit/6c14fb1eb61dc0a0552fbcb2ca3ace11c9a2f6a5))
|
||||
- **jqLite:** cache collection length for all methods that work on a single element
|
||||
([6d418ef5](https://github.com/angular/angular.js/commit/6d418ef5e3a775577996caf0709f79f447f77025))
|
||||
- **ngBind:** set the ng-binding class during compilation instead of linking
|
||||
([1b189027](https://github.com/angular/angular.js/commit/1b1890274e5a75553ddf9915bb23da48800275f9))
|
||||
|
||||
|
||||
|
||||
<a name="1.2.17"></a>
|
||||
# 1.2.17 - quantum disentanglement (2014-06-06)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- remove the need to add `display:block!important` for `ngShow`/`ngHide`
|
||||
([55b2f0e8](https://github.com/angular/angular.js/commit/55b2f0e8620465559016b424967d90a86af597c0),
|
||||
[#3813](https://github.com/angular/angular.js/issues/3813))
|
||||
- retain inline styles for property-specific transitions
|
||||
([ad08638c](https://github.com/angular/angular.js/commit/ad08638c0ae61a22ce43d0b40e1220065b867672),
|
||||
[#7503](https://github.com/angular/angular.js/issues/7503))
|
||||
- ensure class-based animations always perform a DOM operation if skipped
|
||||
([34d07403](https://github.com/angular/angular.js/commit/34d0740350a50ff2c3a076eaad1e8122283448c3),
|
||||
[#6957](https://github.com/angular/angular.js/issues/6957))
|
||||
- **$compile:**
|
||||
- do not merge attrs that are the same for replace directives
|
||||
([b635903e](https://github.com/angular/angular.js/commit/b635903ec435ea355b0f3688c7372627d01e23e2),
|
||||
[#7463](https://github.com/angular/angular.js/issues/7463))
|
||||
- pass `transcludeFn` down to nested transclude directives
|
||||
([11385060](https://github.com/angular/angular.js/commit/113850602de2f8bc396df4ffd54bb0f1be565b17),
|
||||
[#7240](https://github.com/angular/angular.js/issues/7240), [#7387](https://github.com/angular/angular.js/issues/7387))
|
||||
- set `$isolateScope` correctly for sync template directives
|
||||
([5319621a](https://github.com/angular/angular.js/commit/5319621afd0edf60aef177a0e98dbb7c282cc418),
|
||||
[#6942](https://github.com/angular/angular.js/issues/6942))
|
||||
- reference correct directive name in `ctreq` error
|
||||
([6bea0591](https://github.com/angular/angular.js/commit/6bea0591095c19f747c08ef24cc60b34d28b2824),
|
||||
[#7062](https://github.com/angular/angular.js/issues/7062), [#7067](https://github.com/angular/angular.js/issues/7067))
|
||||
- fix regression which affected old jQuery releases
|
||||
([a97a172e](https://github.com/angular/angular.js/commit/a97a172ee9f9bcff4d4d84854ded0c72fa0f7e9a))
|
||||
- **$httpBackend:** don't error when JSONP callback is called with no parameter
|
||||
([a7ccb753](https://github.com/angular/angular.js/commit/a7ccb7531c92fb976c6058aef2bb18316075efb2),
|
||||
[#7031](https://github.com/angular/angular.js/issues/7031))
|
||||
- **$location:**
|
||||
- don't clobber path during parsing of path
|
||||
([02058bfb](https://github.com/angular/angular.js/commit/02058bfbe27296c5441fc247e5a451da83c74134),
|
||||
[#7199](https://github.com/angular/angular.js/issues/7199))
|
||||
- fix and test html5Mode url-parsing algorithm for legacy browsers
|
||||
([24f7999b](https://github.com/angular/angular.js/commit/24f7999bc16e347208aa18c418da85489286674b))
|
||||
- make legacy browsers behave like modern ones in html5Mode
|
||||
([e0203660](https://github.com/angular/angular.js/commit/e0203660d3af56c5a94e0a9b69c10fd5dabcf577),
|
||||
[#6162](https://github.com/angular/angular.js/issues/6162), [#6421](https://github.com/angular/angular.js/issues/6421), [#6899](https://github.com/angular/angular.js/issues/6899), [#6832](https://github.com/angular/angular.js/issues/6832), [#6834](https://github.com/angular/angular.js/issues/6834))
|
||||
- **angular.copy:** support circular references in the value being copied
|
||||
([5c997209](https://github.com/angular/angular.js/commit/5c99720934edc35dd462b1ad02c4d0205683d917),
|
||||
[#7618](https://github.com/angular/angular.js/issues/7618))
|
||||
- **grunt-utils:** ensure special inline CSS works when `angular` is not a global
|
||||
([d4231171](https://github.com/angular/angular.js/commit/d4231171582eb41d37bbb908eed23f074ab12f3f),
|
||||
[#7176](https://github.com/angular/angular.js/issues/7176))
|
||||
- **input:**
|
||||
- fix `ReferenceError` in event listener
|
||||
([2d7cb14a](https://github.com/angular/angular.js/commit/2d7cb14a167560edc1356dcec6f9e100ed7ac691))
|
||||
- don't dirty model when input event is triggered due to a placeholder change
|
||||
([109e5d1d](https://github.com/angular/angular.js/commit/109e5d1d39015af8ade1dc2aff31a2355fbab0a6),
|
||||
[#2614](https://github.com/angular/angular.js/issues/2614), [#5960](https://github.com/angular/angular.js/issues/5960))
|
||||
- **jqLite:** use jQuery only if `jQuery.fn.on` is present
|
||||
([fafcd628](https://github.com/angular/angular.js/commit/fafcd6285a6799c4e377ea33011ae3a01aac49a6))
|
||||
- **limitTo:** do not convert `Infinity` to `NaN`
|
||||
([fcdac65a](https://github.com/angular/angular.js/commit/fcdac65aedfdf48dd2e11d6e5850e03ec188f068),
|
||||
[#6771](https://github.com/angular/angular.js/issues/6771), [#7118](https://github.com/angular/angular.js/issues/7118))
|
||||
- **ngAnimate:** `$animate` methods should accept native DOM elements
|
||||
([9227a5db](https://github.com/angular/angular.js/commit/9227a5db947a78e3dbe8b91d5dac5d67444c855c))
|
||||
- **ngClass:**
|
||||
- support multiple classes in key
|
||||
([85ce5d0d](https://github.com/angular/angular.js/commit/85ce5d0db9fc4ee5636015fc042224785f9aa997))
|
||||
- handle index changes when an item is unshifted
|
||||
([a4cc9e19](https://github.com/angular/angular.js/commit/a4cc9e194468573bae5232f63044459d0de6638f),
|
||||
[#7256](https://github.com/angular/angular.js/issues/7256))
|
||||
- **ngLocale:** fix i18n code-generation to support `get_vf_`, `decimals_`, and `get_wt_`
|
||||
([96a31476](https://github.com/angular/angular.js/commit/96a314766c41bbb18bcddeddd25c8e566ab76acd))
|
||||
- **ngSanitize:** encode surrogate pair properly
|
||||
([3d0b49c0](https://github.com/angular/angular.js/commit/3d0b49c07f10c0a723c91629c63705647b690d81),
|
||||
[#5088](https://github.com/angular/angular.js/issues/5088), [#6911](https://github.com/angular/angular.js/issues/6911))
|
||||
- **ngSwitch:** properly support case labels with different numbers of transclude fns
|
||||
([32aa4915](https://github.com/angular/angular.js/commit/32aa491588fe4982d4056e89a5d0dd19cf835e72))
|
||||
- **numberFilter:** fix rounding error edge case
|
||||
([0388eed7](https://github.com/angular/angular.js/commit/0388eed7e52fdbb832a5b4ef466420a128a43800),
|
||||
[#7453](https://github.com/angular/angular.js/issues/7453), [#7478](https://github.com/angular/angular.js/issues/7478))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngMock:** add support of mocha tdd interface
|
||||
([6d1c6772](https://github.com/angular/angular.js/commit/6d1c67727ab872c44addc783ef1406952142d89e),
|
||||
[#7489](https://github.com/angular/angular.js/issues/7489))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$interpolate:** optimize value stringification
|
||||
([9d4fa33e](https://github.com/angular/angular.js/commit/9d4fa33e35d73ab28a8a187e20dfbe1f77055825),
|
||||
[#7501](https://github.com/angular/angular.js/issues/7501))
|
||||
- **scope:** 10x. Share the child scope class.
|
||||
([9ab9bf6b](https://github.com/angular/angular.js/commit/9ab9bf6b415aa216cfbfda040286e5ec99f56ee0))
|
||||
|
||||
|
||||
|
||||
<a name="1.2.16"></a>
|
||||
# 1.2.16 badger-enumeration (2014-04-03)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- ensure the CSS driver properly works with SVG elements
|
||||
([38ea5426](https://github.com/angular/angular.js/commit/38ea542662b2b74703d583e3a637d65369fc26eb),
|
||||
[#6030](https://github.com/angular/angular.js/issues/6030))
|
||||
- prevent cancellation timestamp from being too far in the future
|
||||
([35d635cb](https://github.com/angular/angular.js/commit/35d635cbcbdc20f304781655f3563111afa6567f),
|
||||
[#6748](https://github.com/angular/angular.js/issues/6748))
|
||||
- run CSS animations before JS animations to avoid style inheritance
|
||||
([0e5106ec](https://github.com/angular/angular.js/commit/0e5106ec2ccc8596c589b89074d3b27d27bf395a),
|
||||
[#6675](https://github.com/angular/angular.js/issues/6675))
|
||||
- **$parse:** mark constant unary minus expressions as constant
|
||||
([6e420ff2](https://github.com/angular/angular.js/commit/6e420ff28d9b3e76ac2c3598bf3797540ef8a1d3),
|
||||
[#6932](https://github.com/angular/angular.js/issues/6932))
|
||||
- **Scope:**
|
||||
- revert the __proto__ cleanup as that could cause regressions
|
||||
([2db66f5b](https://github.com/angular/angular.js/commit/2db66f5b695a06cff62a52e55e55d1a0a25eec2f))
|
||||
- more scope clean up on $destroy to minimize leaks
|
||||
([7e4e696e](https://github.com/angular/angular.js/commit/7e4e696ec3adf9d6fc77a7aa7e0909a9675fd43a),
|
||||
[#6794](https://github.com/angular/angular.js/issues/6794), [#6856](https://github.com/angular/angular.js/issues/6856), [#6968](https://github.com/angular/angular.js/issues/6968))
|
||||
- aggressively clean up scope on $destroy to minimize leaks
|
||||
([8d4d437e](https://github.com/angular/angular.js/commit/8d4d437e8cd8d7cebab5d9ae5c8bcfeef2118ce9),
|
||||
[#6794](https://github.com/angular/angular.js/issues/6794), [#6856](https://github.com/angular/angular.js/issues/6856))
|
||||
- **filter.ngdoc:** Check if "input" variable is defined
|
||||
([a275d539](https://github.com/angular/angular.js/commit/a275d539f9631d6ec64d03814b3b09420e6cf1ee),
|
||||
[#6819](https://github.com/angular/angular.js/issues/6819))
|
||||
- **input:** don't perform HTML5 validation on updated model-value
|
||||
([b2363e31](https://github.com/angular/angular.js/commit/b2363e31023df8240113f68b4e01d942f8009b60),
|
||||
[#6796](https://github.com/angular/angular.js/issues/6796), [#6806](https://github.com/angular/angular.js/issues/6806))
|
||||
- **ngClass:** handle ngClassOdd/Even affecting the same classes
|
||||
([55fe6d63](https://github.com/angular/angular.js/commit/55fe6d6331e501325c2658df8995dcc083fc4ffb),
|
||||
[#5271](https://github.com/angular/angular.js/issues/5271))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$http:** add xhr statusText to completeRequest callback
|
||||
([32c09c1d](https://github.com/angular/angular.js/commit/32c09c1d195fcb98f6e29fc7e554a867f4762301),
|
||||
[#2335](https://github.com/angular/angular.js/issues/2335), [#2665](https://github.com/angular/angular.js/issues/2665), [#6713](https://github.com/angular/angular.js/issues/6713))
|
||||
|
||||
|
||||
<a name="v1.2.15"></a>
|
||||
# v1.2.15 beer-underestimating (2014-03-21)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$$RAFProvider:** check for webkitCancelRequestAnimationFrame
|
||||
([e84da228](https://github.com/angular/angular.js/commit/e84da2283c4e195be557f7b06c8783fe502acbbb),
|
||||
[#6526](https://github.com/angular/angular.js/issues/6526))
|
||||
- **$$rAF:** always fallback to a $timeout incase native rAF isn't supported
|
||||
([ee8e4a94](https://github.com/angular/angular.js/commit/ee8e4a946ed8f943e00846b88d8d51c0b2cd1fab),
|
||||
[#6654](https://github.com/angular/angular.js/issues/6654))
|
||||
- **$compile:** support templates with thead and tfoot root elements
|
||||
([ca0ac649](https://github.com/angular/angular.js/commit/ca0ac649971ae4fb50419b38f92a98d2226eb696),
|
||||
[#6289](https://github.com/angular/angular.js/issues/6289))
|
||||
- **$http:**
|
||||
- allow sending Blob data using $http
|
||||
([fbb125a3](https://github.com/angular/angular.js/commit/fbb125a3af164e52af2f8119175b04cbbed2f331),
|
||||
[#5012](https://github.com/angular/angular.js/issues/5012))
|
||||
- don't covert 0 status codes to 404 for non-file protocols
|
||||
([f108a2a9](https://github.com/angular/angular.js/commit/f108a2a994149ecc011e29f327bcb8e11adf72d9),
|
||||
[#6074](https://github.com/angular/angular.js/issues/6074), [#6155](https://github.com/angular/angular.js/issues/6155))
|
||||
- **$rootScope:**
|
||||
- ng-repeat can't handle NaN values. #4605
|
||||
([e48c28fe](https://github.com/angular/angular.js/commit/e48c28fe9292efe7af6205b2be116d2350990c73),
|
||||
[#4605](https://github.com/angular/angular.js/issues/4605))
|
||||
- $watchCollection should call listener with oldValue
|
||||
([3dd95727](https://github.com/angular/angular.js/commit/3dd9572754c7bafec30dd625f5c611346959c969),
|
||||
[#2621](https://github.com/angular/angular.js/issues/2621), [#5661](https://github.com/angular/angular.js/issues/5661), [#5688](https://github.com/angular/angular.js/issues/5688), [#6736](https://github.com/angular/angular.js/issues/6736))
|
||||
- **angular.bootstrap:** only allow angular to load once
|
||||
([0d60f8d3](https://github.com/angular/angular.js/commit/0d60f8d367e38224696749b0f7de04bd60649815),
|
||||
[#5863](https://github.com/angular/angular.js/issues/5863), [#5587](https://github.com/angular/angular.js/issues/5587))
|
||||
- **jqLite:** traverse `host` property for DocumentFragment in inheritedData()
|
||||
([98d825e1](https://github.com/angular/angular.js/commit/98d825e10d3bf76f47e69abba857a8933c8cb7d9),
|
||||
[#6637](https://github.com/angular/angular.js/issues/6637))
|
||||
- **ngAnimate:** setting classNameFilter disables animation inside ng-if
|
||||
([a41a2a1d](https://github.com/angular/angular.js/commit/a41a2a1d2ce20f86ac2709592e4ada527160e580),
|
||||
[#6539](https://github.com/angular/angular.js/issues/6539))
|
||||
- **ngCookie:** convert non-string values to string
|
||||
([93d1c95c](https://github.com/angular/angular.js/commit/93d1c95c61dbfa565333bb64527a103242175af7),
|
||||
[#6151](https://github.com/angular/angular.js/issues/6151), [#6220](https://github.com/angular/angular.js/issues/6220))
|
||||
- **ngTouch:** update workaround for desktop Webkit quirk
|
||||
([01a34f51](https://github.com/angular/angular.js/commit/01a34f513bb567ed6d4c81d00d7c2a777c0dae01),
|
||||
[#6302](https://github.com/angular/angular.js/issues/6302))
|
||||
- **orderBy:** support string predicates containing non-ident characters
|
||||
([10d3e1e4](https://github.com/angular/angular.js/commit/10d3e1e4472ab9f5cf4418b6438ec2e0f2b0b288),
|
||||
[#6143](https://github.com/angular/angular.js/issues/6143), [#6144](https://github.com/angular/angular.js/issues/6144))
|
||||
- **select:** avoid checking option element selected properties in render
|
||||
([dc149de9](https://github.com/angular/angular.js/commit/dc149de9364c66b988f169f67cad39577ba43434),
|
||||
[#2448](https://github.com/angular/angular.js/issues/2448), [#5994](https://github.com/angular/angular.js/issues/5994), [#6769](https://github.com/angular/angular.js/issues/6769))
|
||||
|
||||
|
||||
|
||||
<a name="1.2.14"></a>
|
||||
# 1.2.14 feisty-cryokinesis (2014-03-01)
|
||||
|
||||
@@ -275,26 +1060,26 @@ The animation mock module has been renamed from `mock.animate` to `ngAnimateMock
|
||||
## Breaking Changes
|
||||
|
||||
- **$http:** due to [e1cfb195](https://github.com/angular/angular.js/commit/e1cfb1957feaf89408bccf48fae6f529e57a82fe),
|
||||
it is now necessary to separately specify default HTTP headers for PUT, POST and PATCH requests, as these no longer share a single object.
|
||||
it is now necessary to separately specify default HTTP headers for PUT, POST and PATCH requests, as these no longer share a single object.
|
||||
|
||||
To migrate your code, follow the example below:
|
||||
To migrate your code, follow the example below:
|
||||
|
||||
Before:
|
||||
Before:
|
||||
|
||||
// Will apply to POST, PUT and PATCH methods
|
||||
$httpProvider.defaults.headers.post = {
|
||||
"X-MY-CSRF-HEADER": "..."
|
||||
};
|
||||
// Will apply to POST, PUT and PATCH methods
|
||||
$httpProvider.defaults.headers.post = {
|
||||
"X-MY-CSRF-HEADER": "..."
|
||||
};
|
||||
|
||||
After:
|
||||
After:
|
||||
|
||||
// POST, PUT and PATCH default headers must be specified separately,
|
||||
// as they do not share data.
|
||||
$httpProvider.defaults.headers.post =
|
||||
$httpProvider.defaults.headers.put =
|
||||
$httpProviders.defaults.headers.patch = {
|
||||
"X-MY-CSRF-HEADER": "..."
|
||||
};
|
||||
// POST, PUT and PATCH default headers must be specified separately,
|
||||
// as they do not share data.
|
||||
$httpProvider.defaults.headers.post =
|
||||
$httpProvider.defaults.headers.put =
|
||||
$httpProviders.defaults.headers.patch = {
|
||||
"X-MY-CSRF-HEADER": "..."
|
||||
};
|
||||
|
||||
<a name="1.2.8"></a>
|
||||
# 1.2.8 interdimensional-cartography (2014-01-10)
|
||||
@@ -3609,7 +4394,7 @@ behavior and migrate your controllers one at a time: <https://gist.github.com/16
|
||||
([commit](https://github.com/angular/angular.js/commit/78656fe0dfc99c341ce02d71e7006e9c05b1fe3f))
|
||||
|
||||
- fn signature change for change listener functions registered via `scope.$watch` - this means that
|
||||
the scope object can be listed in the arguments list only if its needed and skipped otherwise.
|
||||
the scope object can be listed in the arguments list only if it's needed and skipped otherwise.
|
||||
([commit](https://github.com/angular/angular.js/commit/0196411dbe179afe24f4faa6d6503ff3f69472da))
|
||||
|
||||
- before: `scope.$watch('someModel', function(scope, newVal, oldVal) {})`
|
||||
@@ -4543,7 +5328,7 @@ with the `$route` service
|
||||
- docs app UI polishing with dual scrolling and other improvements
|
||||
|
||||
### Bug Fixes
|
||||
- `select` widget now behaves correctly when it's `option` items are created via `ng:repeat`
|
||||
- `select` widget now behaves correctly when its `option` items are created via `ng:repeat`
|
||||
(issue #170)
|
||||
- fix for async xhr cache issue #152 by adding `$browser.defer` and `$defer` service
|
||||
|
||||
|
||||
+37
-31
@@ -23,7 +23,7 @@ discussion list or [StackOverflow][stackoverflow]. We are also available on [IRC
|
||||
|
||||
## <a name="issue"></a> Found an Issue?
|
||||
If you find a bug in the source code or a mistake in the documentation, you can help us by
|
||||
submitting and issue to our [GitHub Repository][github]. Even better you can submit a Pull Request
|
||||
submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request
|
||||
with a fix.
|
||||
|
||||
***Localization Issue:*** *Angular.js uses the [Google Closure I18N library], to generate its own I18N files. This means that
|
||||
@@ -50,7 +50,7 @@ Comment on an issue to let others know what you're working on, or create a new i
|
||||
doesn't fit within the scope of any of the existing doc fix projects.
|
||||
|
||||
For large fixes, please build and test the documentation before submitting the PR to be sure you haven't
|
||||
accidentally introduced any layout or formatting issues.You should also make sure that your commit message
|
||||
accidentally introduced any layout or formatting issues. You should also make sure that your commit message
|
||||
is labeled "docs:" and follows the **Git Commit Guidelines** outlined below.
|
||||
|
||||
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly.
|
||||
@@ -92,16 +92,19 @@ Before you submit your pull request consider the following guidelines:
|
||||
git checkout -b my-fix-branch master
|
||||
```
|
||||
|
||||
* Create your patch, including appropriate test cases.
|
||||
* Follow our [Coding Rules](#coding-rules)
|
||||
* Commit your changes and create a descriptive commit message (the
|
||||
commit message is used to generate release notes, please check out our
|
||||
[commit message conventions](#commit-message-format) and our commit message presubmit hook
|
||||
`validate-commit-msg.js`):
|
||||
* Create your patch, **including appropriate test cases**.
|
||||
* Follow our [Coding Rules](#coding-rules).
|
||||
* Run the full Angular test suite, as described in the [developer documentation][dev-doc],
|
||||
and ensure that all tests pass.
|
||||
* Commit your changes using a descriptive commit message that follows our
|
||||
[commit message conventions](#commit-message-format) and passes our commit message presubmit hook
|
||||
`validate-commit-msg.js`. Adherence to the [commit message conventions](#commit-message-format)
|
||||
is required because release notes are automatically generated from these messages.
|
||||
|
||||
```shell
|
||||
git commit -a
|
||||
```
|
||||
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
|
||||
|
||||
* Build your changes locally to ensure all the tests pass
|
||||
|
||||
@@ -109,15 +112,17 @@ Before you submit your pull request consider the following guidelines:
|
||||
grunt test
|
||||
```
|
||||
|
||||
* Push your branch to Github:
|
||||
* Push your branch to GitHub:
|
||||
|
||||
```shell
|
||||
git push origin my-fix-branch
|
||||
```
|
||||
|
||||
* In Github, send a pull request to `angular:master`.
|
||||
* If we suggest changes then you can modify your branch, rebase and force a new push to your GitHub
|
||||
repository to update the Pull Request:
|
||||
* In GitHub, send a pull request to `angular:master`.
|
||||
* If we suggest changes then
|
||||
* Make the required updates.
|
||||
* Re-run the Angular test suite to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```shell
|
||||
git rebase master -i
|
||||
@@ -126,10 +131,12 @@ Before you submit your pull request consider the following guidelines:
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
When the patch is reviewed and merged, you can safely delete your branch and pull the changes
|
||||
#### After your pull request is merged
|
||||
|
||||
After your pull request is merged, you can safely delete your branch and pull the changes
|
||||
from the main (upstream) repository:
|
||||
|
||||
* Delete the remote branch on Github:
|
||||
* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
|
||||
|
||||
```shell
|
||||
git push origin --delete my-fix-branch
|
||||
@@ -245,24 +252,23 @@ You can find out more detailed information about contributing in the
|
||||
|
||||
|
||||
|
||||
[github]: https://github.com/angular/angular.js
|
||||
[Google Closure I18N library]: https://code.google.com/p/closure-library/source/browse/closure/goog/i18n/
|
||||
[list]: https://groups.google.com/forum/?fromgroups#!forum/angular
|
||||
[contribute]: http://docs.angularjs.org/misc/contribute
|
||||
[stackoverflow]: http://stackoverflow.com/questions/tagged/angularjs
|
||||
[groups]: https://groups.google.com/forum/?fromgroups#!forum/angular
|
||||
[Google Closure I18N library]: https://github.com/google/closure-library/tree/master/closure/goog/i18n
|
||||
[angular-dev]: https://groups.google.com/forum/?fromgroups#!forum/angular-dev
|
||||
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
|
||||
[plunker]: http://plnkr.co/edit
|
||||
[jsfiddle]: http://jsfiddle.net/
|
||||
[ngDocs]: https://github.com/angular/angular.js/wiki/Writing-AngularJS-Documentation
|
||||
[unit-testing]: http://docs.angularjs.org/guide/dev_guide.unit-testing
|
||||
[js-style-guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
|
||||
[contributing]: http://docs.angularjs.org/misc/contribute
|
||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
[github-pr-helper]: https://chrome.google.com/webstore/detail/github-pr-helper/mokbklfnaddkkbolfldepnkfmanfhpen
|
||||
[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md
|
||||
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
[contribute]: http://docs.angularjs.org/misc/contribute
|
||||
[contributing]: http://docs.angularjs.org/misc/contribute
|
||||
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
[github]: https://github.com/angular/angular.js
|
||||
[groups]: https://groups.google.com/forum/?fromgroups#!forum/angular
|
||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
|
||||
[js-style-guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
|
||||
[jsfiddle]: http://jsfiddle.net/
|
||||
[list]: https://groups.google.com/forum/?fromgroups#!forum/angular
|
||||
[ngDocs]: https://github.com/angular/angular.js/wiki/Writing-AngularJS-Documentation
|
||||
[plunker]: http://plnkr.co/edit
|
||||
[stackoverflow]: http://stackoverflow.com/questions/tagged/angularjs
|
||||
[unit-testing]: https://docs.angularjs.org/guide/unit-testing
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
+11
-4
@@ -1,5 +1,6 @@
|
||||
var files = require('./angularFiles').files;
|
||||
var util = require('./lib/grunt/utils.js');
|
||||
var versionInfo = require('./lib/versions/version-info');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
@@ -8,10 +9,10 @@ module.exports = function(grunt) {
|
||||
|
||||
grunt.loadTasks('lib/grunt');
|
||||
|
||||
var NG_VERSION = util.getVersion();
|
||||
var NG_VERSION = versionInfo.currentVersion;
|
||||
NG_VERSION.cdn = versionInfo.cdnVersion;
|
||||
var dist = 'angular-'+ NG_VERSION.full;
|
||||
|
||||
|
||||
//global beforeEach
|
||||
util.init();
|
||||
|
||||
@@ -106,6 +107,9 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
jshintrc: true,
|
||||
},
|
||||
tests: {
|
||||
files: { src: 'test/**/*.js' },
|
||||
},
|
||||
ng: {
|
||||
files: { src: files['angularSrc'] },
|
||||
},
|
||||
@@ -219,8 +223,11 @@ module.exports = function(grunt) {
|
||||
|
||||
"ddescribe-iit": {
|
||||
files: [
|
||||
'src/**/*.js',
|
||||
'test/**/*.js',
|
||||
'!test/ngScenario/DescribeSpec.js'
|
||||
'!test/ngScenario/DescribeSpec.js',
|
||||
'!src/ng/directive/booleanAttrs.js', // legitimate xit here
|
||||
'!src/ngScenario/**/*.js'
|
||||
]
|
||||
},
|
||||
|
||||
@@ -278,7 +285,7 @@ module.exports = function(grunt) {
|
||||
|
||||
|
||||
//alias tasks
|
||||
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
|
||||
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
|
||||
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
|
||||
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
|
||||
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['tests:modules']);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
AngularJS [](https://travis-ci.org/angular/angular.js)
|
||||
AngularJS [](https://travis-ci.org/angular/angular.js)
|
||||
=========
|
||||
|
||||
AngularJS lets you write client-side web applications as if you had a smarter browser. It lets you
|
||||
|
||||
+4
-4
@@ -34,7 +34,7 @@ This process based on the idea of minimizing user pain
|
||||
* Check if there are comments that link to a dupe. If so verify that this is indeed a dupe, [close it][], and go to the last step.
|
||||
1. Bugs:
|
||||
* Label `Type: Bug`
|
||||
* Reproducible? - Steps to reproduce the bug are clear. If they are not,
|
||||
* Reproducible? - Steps to reproduce the bug are clear. If they are not, ask for a clarification. If there's no reply after a week, [close it][].
|
||||
* Reproducible on master? - <http://code.angularjs.org/snapshot/>
|
||||
|
||||
1. Non bugs:
|
||||
@@ -61,9 +61,9 @@ This process based on the idea of minimizing user pain
|
||||
1. Label `origin: google` for issues from Google
|
||||
|
||||
1. Assign a milestone:
|
||||
* Current 1.x.y milestone - regressions and urgent bugs only
|
||||
* Backlog - fixes; changes that should go into a patch release
|
||||
* Ice Box - new features; changes that belong inß a major/minor release
|
||||
* Backlog - triaged fixes and features, should be the default choice
|
||||
* Current 1.x.y milestone (e.g. 1.3.0-beta-2) - regressions and urgent bugs only
|
||||
|
||||
|
||||
1. Unassign yourself from the issue
|
||||
|
||||
|
||||
@@ -14,3 +14,9 @@ ng\:form {
|
||||
transition:0s all!important;
|
||||
-webkit-transition:0s all!important;
|
||||
}
|
||||
|
||||
/* show the element during a show/hide animation when the
|
||||
* animation is ongoing, but the .ng-hide class is active */
|
||||
.ng-hide-add-active, .ng-hide-remove {
|
||||
display: block!important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<h1>Oops!</h1>
|
||||
|
||||
<p>The page you requested does not exist. Perhaps you were looking for something else...</p>
|
||||
|
||||
<div ng-controller="Error404SearchCtrl">
|
||||
|
||||
<dl ng-repeat="(key, value) in results" ng-show="value.length" style="float: left; margin-right:20px">
|
||||
<dt>{{ key }}</dt>
|
||||
<dd ng-repeat="item in value"><a ng-href="{{ item.path }}">{{ item.name }}</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
@@ -184,10 +184,8 @@ h1,h2,h3,h4,h5,h6 {
|
||||
}
|
||||
|
||||
pre {
|
||||
padding:15px;
|
||||
border:1px solid #ddd;
|
||||
display:block;
|
||||
border-radius:5px;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.aside-nav a,
|
||||
@@ -213,6 +211,10 @@ code.highlighted {
|
||||
color:maroon;
|
||||
}
|
||||
|
||||
ul + p {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.docs-version-jump {
|
||||
min-width:100%;
|
||||
max-width:100%;
|
||||
@@ -255,6 +257,9 @@ code.highlighted {
|
||||
z-index: 99;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
-moz-appearance: none;
|
||||
text-indent: 0.01px;
|
||||
text-overflow: '';
|
||||
}
|
||||
|
||||
.picker:after {
|
||||
@@ -420,6 +425,7 @@ iframe.example {
|
||||
|
||||
.type-hint {
|
||||
display:inline-block;
|
||||
background: gray;
|
||||
}
|
||||
|
||||
.variables-matrix .type-hint {
|
||||
@@ -464,6 +470,14 @@ iframe.example {
|
||||
background:rgb(189, 63, 66);
|
||||
}
|
||||
|
||||
.type-hint-regexp {
|
||||
background: rgb(90, 84, 189);
|
||||
}
|
||||
|
||||
.type-hint-domelement {
|
||||
background: rgb(95, 158, 160);
|
||||
}
|
||||
|
||||
.runnable-example-frame {
|
||||
width:100%;
|
||||
height:300px;
|
||||
@@ -501,10 +515,6 @@ h4 {
|
||||
padding-top:20px;
|
||||
}
|
||||
|
||||
.improve-docs {
|
||||
float:right;
|
||||
}
|
||||
|
||||
.btn {
|
||||
color:#428bca;
|
||||
position: relative;
|
||||
@@ -538,10 +548,17 @@ h4 {
|
||||
background:white!important;
|
||||
}
|
||||
|
||||
.view-source, .improve-docs {
|
||||
position:relative;
|
||||
z-index:100;
|
||||
}
|
||||
|
||||
.view-source {
|
||||
margin-right:10px;
|
||||
padding-right:10px;
|
||||
border-right:1px solid #999;
|
||||
}
|
||||
|
||||
.improve-docs {
|
||||
float:right;
|
||||
}
|
||||
|
||||
.return-arguments,
|
||||
@@ -554,7 +571,7 @@ h4 {
|
||||
}
|
||||
|
||||
.return-arguments td:first-child {
|
||||
width:100px;
|
||||
width:100px;
|
||||
}
|
||||
|
||||
ul.methods > li,
|
||||
@@ -562,6 +579,15 @@ ul.events > li {
|
||||
margin-bottom:40px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 769px) and (max-width: 991px) {
|
||||
.main-body-grid {
|
||||
margin-top: 160px;
|
||||
}
|
||||
.main-body-grid > .grid-left {
|
||||
top: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width : 768px) {
|
||||
.picker, .picker select {
|
||||
width:auto;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 141 KiB |
@@ -1,9 +1,5 @@
|
||||
angular.module('directives', [])
|
||||
|
||||
.directive('code', function() {
|
||||
return { restrict:'E', terminal: true };
|
||||
})
|
||||
|
||||
/**
|
||||
* backToTop Directive
|
||||
* @param {Function} $anchorScroll
|
||||
@@ -25,7 +21,7 @@ angular.module('directives', [])
|
||||
restrict: 'E',
|
||||
terminal: true,
|
||||
compile: function(element) {
|
||||
var linenums = element.hasClass('linenum') || element.parent()[0].nodeName === 'PRE';
|
||||
var linenums = element.hasClass('linenum');// || element.parent()[0].nodeName === 'PRE';
|
||||
var match = /lang-(\S)+/.exec(element.className);
|
||||
var lang = match && match[1];
|
||||
var html = element.html();
|
||||
|
||||
+12
-3
@@ -1,6 +1,15 @@
|
||||
angular.module('DocsController', [])
|
||||
|
||||
.controller('DocsController', function($scope, $rootScope, $location, $window, $cookies, NG_PAGES, NG_NAVIGATION, NG_VERSION) {
|
||||
.controller('DocsController', [
|
||||
'$scope', '$rootScope', '$location', '$window', '$cookies', 'openPlunkr',
|
||||
'NG_PAGES', 'NG_NAVIGATION', 'NG_VERSION',
|
||||
function($scope, $rootScope, $location, $window, $cookies, openPlunkr,
|
||||
NG_PAGES, NG_NAVIGATION, NG_VERSION) {
|
||||
|
||||
|
||||
$scope.openPlunkr = openPlunkr;
|
||||
|
||||
$scope.docsVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.version;
|
||||
|
||||
$scope.fold = function(url) {
|
||||
if(url) {
|
||||
@@ -87,7 +96,7 @@ angular.module('DocsController', [])
|
||||
breadcrumbPath += '/';
|
||||
});
|
||||
} else {
|
||||
$scope.currentArea = null;
|
||||
$scope.currentArea = NG_NAVIGATION['api'];
|
||||
$scope.breadcrumb = [];
|
||||
}
|
||||
});
|
||||
@@ -118,4 +127,4 @@ angular.module('DocsController', [])
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}]);
|
||||
|
||||
+47
-241
@@ -1,65 +1,6 @@
|
||||
angular.module('examples', [])
|
||||
|
||||
.directive('sourceEdit', function(getEmbeddedTemplate) {
|
||||
return {
|
||||
template: '<div class="btn-group pull-right">' +
|
||||
'<a class="btn dropdown-toggle btn-primary" data-toggle="dropdown" href>' +
|
||||
' <i class="icon-pencil icon-white"></i> Edit<span class="caret"></span>' +
|
||||
'</a>' +
|
||||
'<ul class="dropdown-menu">' +
|
||||
' <li><a ng-click="plunkr($event)" href="">In Plunkr</a></li>' +
|
||||
' <li><a ng-click="fiddle($event)" href="">In JsFiddle</a></li>' +
|
||||
'</ul>' +
|
||||
'</div>',
|
||||
scope: true,
|
||||
controller: function($scope, $attrs, openJsFiddle, openPlunkr) {
|
||||
var sources = {
|
||||
module: $attrs.sourceEdit,
|
||||
deps: read($attrs.sourceEditDeps),
|
||||
html: read($attrs.sourceEditHtml),
|
||||
css: read($attrs.sourceEditCss),
|
||||
js: read($attrs.sourceEditJs),
|
||||
json: read($attrs.sourceEditJson),
|
||||
unit: read($attrs.sourceEditUnit),
|
||||
scenario: read($attrs.sourceEditScenario)
|
||||
};
|
||||
$scope.fiddle = function(e) {
|
||||
e.stopPropagation();
|
||||
openJsFiddle(sources);
|
||||
};
|
||||
$scope.plunkr = function(e) {
|
||||
e.stopPropagation();
|
||||
openPlunkr(sources);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function read(text) {
|
||||
var files = [];
|
||||
angular.forEach(text ? text.split(' ') : [], function(refId) {
|
||||
// refId is index.html-343, so we need to strip the unique ID when exporting the name
|
||||
files.push({name: refId.replace(/-\d+$/, ''), content: getEmbeddedTemplate(refId)});
|
||||
});
|
||||
return files;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
.factory('angularUrls', function($document) {
|
||||
var urls = {};
|
||||
|
||||
angular.forEach($document.find('script'), function(script) {
|
||||
var match = script.src.match(/^.*\/(angular[^\/]*\.js)$/);
|
||||
if (match) {
|
||||
urls[match[1].replace(/(\-\d.*)?(\.min)?\.js$/, '.js')] = match[0];
|
||||
}
|
||||
});
|
||||
|
||||
return urls;
|
||||
})
|
||||
|
||||
|
||||
.factory('formPostData', function($document) {
|
||||
.factory('formPostData', ['$document', function($document) {
|
||||
return function(url, fields) {
|
||||
var form = angular.element('<form style="display: none;" method="post" action="' + url + '" target="_blank"></form>');
|
||||
angular.forEach(fields, function(value, name) {
|
||||
@@ -71,196 +12,61 @@ angular.module('examples', [])
|
||||
form[0].submit();
|
||||
form.remove();
|
||||
};
|
||||
})
|
||||
}])
|
||||
|
||||
|
||||
.factory('prepareDefaultAppModule', function() {
|
||||
return function(content) {
|
||||
var deps = [];
|
||||
angular.forEach(content.deps, function(file) {
|
||||
if(file.name == 'angular-animate.js') {
|
||||
deps.push('ngAnimate');
|
||||
}
|
||||
});
|
||||
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
|
||||
return function(exampleFolder) {
|
||||
|
||||
var moduleName = 'App';
|
||||
return {
|
||||
module : moduleName,
|
||||
script : "angular.module('" + moduleName + "', [" +
|
||||
(deps.length ? "'" + deps.join("','") + "'" : "") + "]);\n\n"
|
||||
};
|
||||
};
|
||||
})
|
||||
var exampleName = 'AngularJS Example';
|
||||
|
||||
.factory('prepareEditorAssetTags', function(angularUrls) {
|
||||
return function(content, options) {
|
||||
options = options || {};
|
||||
var includeLocalFiles = options.includeLocalFiles;
|
||||
var html = makeScriptTag(angularUrls['angular.js']);
|
||||
// Load the manifest for the example
|
||||
$http.get(exampleFolder + '/manifest.json')
|
||||
.then(function(response) {
|
||||
return response.data;
|
||||
})
|
||||
.then(function(manifest) {
|
||||
var filePromises = [];
|
||||
|
||||
var allFiles = [].concat(content.js, content.css, content.html, content.json);
|
||||
angular.forEach(content.deps, function(file) {
|
||||
if (file.name !== 'angular.js') {
|
||||
var isLocal = false;
|
||||
for(var i=0;i<allFiles.length;i++) {
|
||||
if(allFiles[i].name == file.name) {
|
||||
isLocal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!(isLocal && !includeLocalFiles)) {
|
||||
var assetUrl = angularUrls[file.name] || file.name;
|
||||
html += makeScriptTag(assetUrl);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(includeLocalFiles) {
|
||||
angular.forEach(content.css, function(file, index) {
|
||||
html += makeCssLinkTag(file.name);
|
||||
});
|
||||
}
|
||||
|
||||
return html;
|
||||
|
||||
|
||||
function makeScriptTag(src) {
|
||||
return '<script type="text/javascript" src="' + src + '"></script>\n';
|
||||
}
|
||||
|
||||
function makeCssLinkTag(src) {
|
||||
return '<link rel="stylesheet" type="text/css" href="' + src + '" />\n';
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
|
||||
.factory('openPlunkr', function(templateMerge, formPostData, prepareEditorAssetTags, prepareDefaultAppModule) {
|
||||
return function(content) {
|
||||
var hasRouting = false;
|
||||
angular.forEach(content.deps, function(file) {
|
||||
hasRouting = hasRouting || file.name == 'angular-route.js';
|
||||
});
|
||||
var indexHtmlContent = '<!doctype html>\n' +
|
||||
'<html ng-app="{{module}}">\n' +
|
||||
' <head>\n' +
|
||||
'{{scriptDeps}}';
|
||||
|
||||
if(hasRouting) {
|
||||
indexHtmlContent += '<script type="text/javascript">\n' +
|
||||
'//this is here to make plunkr work with AngularJS routing\n' +
|
||||
'angular.element(document.getElementsByTagName(\'head\')).append(' +
|
||||
'angular.element(\'<base href="\' + window.location.pathname + \'" />\')' +
|
||||
');\n' +
|
||||
'</script>\n';
|
||||
}
|
||||
|
||||
indexHtmlContent += '</head>\n' +
|
||||
' <body>\n\n' +
|
||||
'{{indexContents}}\n\n' +
|
||||
' </body>\n' +
|
||||
'</html>\n';
|
||||
|
||||
indexProp = {
|
||||
module: content.module,
|
||||
scriptDeps: prepareEditorAssetTags(content, { includeLocalFiles : true }),
|
||||
indexContents: content.html[0].content
|
||||
};
|
||||
|
||||
var allFiles = [].concat(content.js, content.css, content.html, content.json);
|
||||
|
||||
if(!content.module) {
|
||||
var moduleData = prepareDefaultAppModule(content);
|
||||
indexProp.module = moduleData.module;
|
||||
|
||||
var found = false;
|
||||
angular.forEach(content.js, function(file) {
|
||||
if(file.name == 'script.js') {
|
||||
file.content = moduleData.script + file.content;
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
if(!found) {
|
||||
indexProp.scriptDeps += '<script type="text/javascript" src="script.js"></script>\n';
|
||||
allFiles.push({
|
||||
name : 'script.js',
|
||||
content : moduleData.script
|
||||
// Build a pretty title for the Plunkr
|
||||
var exampleNameParts = manifest.name.split('-');
|
||||
exampleNameParts.unshift('AngularJS');
|
||||
angular.forEach(exampleNameParts, function(part, index) {
|
||||
exampleNameParts[index] = part.charAt(0).toUpperCase() + part.substr(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
exampleName = exampleNameParts.join(' - ');
|
||||
|
||||
var postData = {};
|
||||
angular.forEach(manifest.files, function(filename) {
|
||||
filePromises.push($http.get(exampleFolder + '/' + filename, { transformResponse: [] })
|
||||
.then(function(response) {
|
||||
|
||||
angular.forEach(allFiles, function(file, index) {
|
||||
if (file.content && file.name != 'index.html') {
|
||||
postData['files[' + file.name + ']'] = file.content;
|
||||
}
|
||||
});
|
||||
// The manifests provide the production index file but Plunkr wants
|
||||
// a straight index.html
|
||||
if (filename === "index-production.html") {
|
||||
filename = "index.html"
|
||||
}
|
||||
|
||||
postData['files[index.html]'] = templateMerge(indexHtmlContent, indexProp);
|
||||
postData['tags[]'] = "angularjs";
|
||||
return {
|
||||
name: filename,
|
||||
content: response.data
|
||||
};
|
||||
}));
|
||||
});
|
||||
return $q.all(filePromises);
|
||||
})
|
||||
.then(function(files) {
|
||||
var postData = {};
|
||||
|
||||
postData.private = true;
|
||||
postData.description = 'AngularJS Example Plunkr';
|
||||
angular.forEach(files, function(file) {
|
||||
postData['files[' + file.name + ']'] = file.content;
|
||||
});
|
||||
|
||||
formPostData('http://plnkr.co/edit/?p=preview', postData);
|
||||
postData['tags[0]'] = "angularjs";
|
||||
postData['tags[1]'] = "example";
|
||||
postData.private = true;
|
||||
postData.description = exampleName;
|
||||
|
||||
formPostData('http://plnkr.co/edit/?p=preview', postData);
|
||||
});
|
||||
};
|
||||
})
|
||||
|
||||
.factory('openJsFiddle', function(templateMerge, formPostData, prepareEditorAssetTags, prepareDefaultAppModule) {
|
||||
var HTML = '<div ng-app=\"{{module}}\">\n{{html:2}}</div>',
|
||||
CSS = '</style> <!-- Ugly Hack to make remote files preload in jsFiddle --> \n' +
|
||||
'{{head:0}}<style>{{css}}',
|
||||
SCRIPT = '{{script}}',
|
||||
SCRIPT_CACHE = '\n\n<!-- {{name}} -->\n<script type="text/ng-template" id="{{name}}">\n{{content:2}}</script>',
|
||||
BASE_HREF_TAG = '<!-- Ugly Hack to make AngularJS routing work inside of jsFiddle -->\n' +
|
||||
'<base href="/" />\n\n';
|
||||
|
||||
return function(content) {
|
||||
var prop = {
|
||||
module: content.module,
|
||||
html: '',
|
||||
css: '',
|
||||
script: ''
|
||||
};
|
||||
if(!prop.module) {
|
||||
var moduleData = prepareDefaultAppModule(content);
|
||||
prop.script = moduleData.script;
|
||||
prop.module = moduleData.module;
|
||||
}
|
||||
|
||||
angular.forEach(content.html, function(file, index) {
|
||||
if (index) {
|
||||
prop.html += templateMerge(SCRIPT_CACHE, file);
|
||||
} else {
|
||||
prop.html += file.content;
|
||||
}
|
||||
});
|
||||
|
||||
prop.head = prepareEditorAssetTags(content, { includeLocalFiles : false });
|
||||
|
||||
angular.forEach(content.js, function(file, index) {
|
||||
prop.script += file.content;
|
||||
});
|
||||
|
||||
angular.forEach(content.css, function(file, index) {
|
||||
prop.css += file.content;
|
||||
});
|
||||
|
||||
var hasRouting = false;
|
||||
angular.forEach(content.deps, function(file) {
|
||||
hasRouting = hasRouting || file.name == 'angular-route.js';
|
||||
});
|
||||
|
||||
var compiledHTML = templateMerge(HTML, prop);
|
||||
if(hasRouting) {
|
||||
compiledHTML = BASE_HREF_TAG + compiledHTML;
|
||||
}
|
||||
formPostData("http://jsfiddle.net/api/post/library/pure/", {
|
||||
title: 'AngularJS Example',
|
||||
html: compiledHTML,
|
||||
js: templateMerge(SCRIPT, prop),
|
||||
css: templateMerge(CSS, prop)
|
||||
});
|
||||
};
|
||||
});
|
||||
}]);
|
||||
@@ -8,7 +8,7 @@ angular.module('search', [])
|
||||
}
|
||||
|
||||
$scope.search = function(q) {
|
||||
var MIN_SEARCH_LENGTH = 3;
|
||||
var MIN_SEARCH_LENGTH = 2;
|
||||
if(q.length >= MIN_SEARCH_LENGTH) {
|
||||
var results = docsSearch(q);
|
||||
var totalAreas = 0;
|
||||
@@ -35,7 +35,7 @@ angular.module('search', [])
|
||||
}
|
||||
}
|
||||
if(result) {
|
||||
$location.path(result.url);
|
||||
$location.path(result.path);
|
||||
$scope.hideResults();
|
||||
}
|
||||
};
|
||||
@@ -45,6 +45,10 @@ angular.module('search', [])
|
||||
};
|
||||
}])
|
||||
|
||||
.controller('Error404SearchCtrl', ['$scope', '$location', 'docsSearch', function($scope, $location, docsSearch) {
|
||||
$scope.results = docsSearch($location.path().split(/[\/\.:]/).pop());
|
||||
}])
|
||||
|
||||
.factory('lunrSearch', function() {
|
||||
return function(properties) {
|
||||
if (window.RUNNING_IN_NG_TEST_RUNNER) return null;
|
||||
|
||||
+18
-26
@@ -21,38 +21,30 @@ angular.module('tutorials', [])
|
||||
element.addClass('btn-group');
|
||||
element.addClass('tutorial-nav');
|
||||
element.append(templateMerge(
|
||||
'<a href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="icon-step-backward"></i> Previous</li></a>\n' +
|
||||
'<a href="http://angular.github.com/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="icon-play"></i> Live Demo</li></a>\n' +
|
||||
'<a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="icon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="icon-step-forward"></i></li></a>', props));
|
||||
'<a href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
|
||||
'<a href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
|
||||
'<a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>', props));
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
|
||||
.directive('docTutorialReset', function() {
|
||||
function tab(name, command, id, step) {
|
||||
return '' +
|
||||
' <div class=\'tab-pane well\' title="' + name + '" value="' + id + '">\n' +
|
||||
' <ol>\n' +
|
||||
' <li><p>Reset the workspace to step ' + step + '.</p>' +
|
||||
' <pre>' + command + '</pre></li>\n' +
|
||||
' <li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-' + step + '/app">Angular\'s server</a>.</p></li>\n' +
|
||||
' </ol>\n' +
|
||||
' </div>\n';
|
||||
}
|
||||
|
||||
return {
|
||||
compile: function(element, attrs) {
|
||||
var step = attrs.docTutorialReset;
|
||||
element.html(
|
||||
'<div ng-hide="show">' +
|
||||
'<p><a href="" ng-click="show=true;$event.stopPropagation()">Workspace Reset Instructions ➤</a></p>' +
|
||||
'</div>\n' +
|
||||
'<div class="tabbable" ng-show="show" ng-model="$cookies.platformPreference">\n' +
|
||||
tab('Git on Mac/Linux', 'git checkout -f step-' + step, 'gitUnix', step) +
|
||||
tab('Git on Windows', 'git checkout -f step-' + step, 'gitWin', step) +
|
||||
'</div>\n');
|
||||
}
|
||||
scope: {
|
||||
'step': '@docTutorialReset'
|
||||
},
|
||||
template:
|
||||
'<p><a href="" ng-click="show=!show;$event.stopPropagation()">Workspace Reset Instructions ➤</a></p>\n' +
|
||||
'<div class="alert alert-info" ng-show="show">\n' +
|
||||
' <p>Reset the workspace to step {{step}}.</p>' +
|
||||
' <p><pre>git checkout -f step-{{step}}</pre></p>\n' +
|
||||
' <p>Refresh your browser or check out this step online: '+
|
||||
'<a href="http://angular.github.io/angular-phonecat/step-{{step}}/app">Step {{step}} Live Demo</a>.</p>\n' +
|
||||
'</div>\n' +
|
||||
'<p>The most important changes are listed below. You can see the full diff on ' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}">GitHub</a>\n' +
|
||||
'</p>'
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ describe("DocsController", function() {
|
||||
|
||||
angular.module('fake', [])
|
||||
.value('$cookies', {})
|
||||
.value('openPlunkr', function() {})
|
||||
.value('NG_PAGES', {})
|
||||
.value('NG_NAVIGATION', {})
|
||||
.value('NG_VERSION', {});
|
||||
|
||||
@@ -25,6 +25,14 @@ module.exports = function(config) {
|
||||
require('./tag-defs/tutorial-step')
|
||||
]);
|
||||
|
||||
config.append('processing.defaultTagTransforms', [
|
||||
require('dgeni-packages/jsdoc/tag-defs/transforms/trim-whitespace')
|
||||
]);
|
||||
|
||||
config.append('processing.inlineTagDefinitions', [
|
||||
require('./inline-tag-defs/type')
|
||||
]);
|
||||
|
||||
config.set('processing.search.ignoreWordsFile', path.resolve(packagePath, 'ignore.words'));
|
||||
|
||||
config.prepend('rendering.templateFolders', [
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
var typeClassFilter = require('dgeni-packages/ngdoc/rendering/filters/type-class');
|
||||
var encoder = new require('node-html-encoder').Encoder();
|
||||
|
||||
module.exports = {
|
||||
name: 'type',
|
||||
description: 'Replace with markup that displays a nice type',
|
||||
handlerFactory: function() {
|
||||
return function(doc, tagName, tagDescription) {
|
||||
return '<a href="" class="' + typeClassFilter.process(tagDescription) + '">'+encoder.htmlEncode(tagDescription) + '</a>';
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,26 +1,33 @@
|
||||
var writer = require('dgeni/lib/utils/doc-writer');
|
||||
var fs = require('q-io/fs');
|
||||
var log = require('winston');
|
||||
var util = require("util");
|
||||
|
||||
var filter, outputPath, depth;
|
||||
|
||||
module.exports = {
|
||||
name: 'debug-dump',
|
||||
runBefore: ['write-files'],
|
||||
description: 'This processor dumps docs that match a filter to a file',
|
||||
init: function(config, injectables) {
|
||||
process: function(docs, config) {
|
||||
|
||||
var filter, outputPath, depth;
|
||||
|
||||
filter = config.get('processing.debug-dump.filter');
|
||||
outputPath = config.get('processing.debug-dump.outputPath');
|
||||
depth = config.get('processing.debug-dump.depth', 2);
|
||||
},
|
||||
process: function(docs) {
|
||||
|
||||
|
||||
if ( filter && outputPath ) {
|
||||
log.info('Dumping docs:', filter, outputPath);
|
||||
var filteredDocs = filter(docs);
|
||||
var dumpedDocs = util.inspect(filteredDocs, depth);
|
||||
return writer.writeFile(outputPath, dumpedDocs).then(function() {
|
||||
return writeFile(outputPath, dumpedDocs).then(function() {
|
||||
return docs;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function writeFile(file, content) {
|
||||
return fs.makeTree(fs.directory(file)).then(function() {
|
||||
return fs.write(file, content, 'wb');
|
||||
});
|
||||
}
|
||||
@@ -5,16 +5,18 @@ var path = require('canonical-path');
|
||||
module.exports = {
|
||||
name: 'error-docs',
|
||||
description: 'Compute the various fields for docs in the Error area',
|
||||
runAfter: ['tags-extracted'],
|
||||
init: function(config, injectables) {
|
||||
injectables.value('errorNamespaces', {});
|
||||
|
||||
var minerrInfoPath = config.get('processing.errors.minerrInfoPath');
|
||||
if ( !minerrInfoPath ) {
|
||||
throw new Error('Error in configuration: Please provide a path to the minerr info file (errors.json) ' +
|
||||
'in the `config.processing.errors.minerrInfoPath` property');
|
||||
}
|
||||
injectables.value('minerrInfo', require(minerrInfoPath));
|
||||
runAfter: ['tags-extracted', 'compute-path'],
|
||||
runBefore: ['extra-docs-added'],
|
||||
exports: {
|
||||
errorNamespaces: ['factory', function() { return {}; }],
|
||||
minerrInfo: ['factory', function(config) {
|
||||
var minerrInfoPath = config.get('processing.errors.minerrInfoPath');
|
||||
if ( !minerrInfoPath ) {
|
||||
throw new Error('Error in configuration: Please provide a path to the minerr info file (errors.json) ' +
|
||||
'in the `config.processing.errors.minerrInfoPath` property');
|
||||
}
|
||||
return require(minerrInfoPath);
|
||||
}]
|
||||
},
|
||||
process: function(docs, partialNames, errorNamespaces, minerrInfo) {
|
||||
|
||||
@@ -22,6 +24,12 @@ module.exports = {
|
||||
_.forEach(docs, function(doc) {
|
||||
if ( doc.docType === 'error' ) {
|
||||
|
||||
// Parse out the error info from the id
|
||||
parts = doc.name.split(':');
|
||||
doc.namespace = parts[0];
|
||||
doc.name = parts[1];
|
||||
|
||||
|
||||
var namespaceDoc = errorNamespaces[doc.namespace];
|
||||
if ( !namespaceDoc ) {
|
||||
// First time we came across this namespace, so create a new one
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
var gruntUtils = require('../../../lib/grunt/utils');
|
||||
var versionInfo = require('../../../lib/versions/version-info');
|
||||
|
||||
module.exports = {
|
||||
name: 'git-data',
|
||||
runBefore: ['loading-files'],
|
||||
runBefore: ['reading-files'],
|
||||
description: 'This processor adds information from the local git repository to the extraData injectable',
|
||||
init: function(config, injectables) {
|
||||
injectables.value('gitData', {
|
||||
version: gruntUtils.getVersion(),
|
||||
versions: gruntUtils.getPreviousVersions(),
|
||||
info: gruntUtils.getGitRepoInfo()
|
||||
});
|
||||
exports: {
|
||||
gitData: ['factory', function() {
|
||||
return {
|
||||
version: versionInfo.currentVersion,
|
||||
versions: versionInfo.previousVersions,
|
||||
info: versionInfo.gitRepoInfo
|
||||
};
|
||||
}]
|
||||
},
|
||||
process: function(extraData, gitData) {
|
||||
extraData.git = gitData;
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
var _ = require('lodash');
|
||||
var log = require('winston');
|
||||
var path = require('canonical-path');
|
||||
var deployment;
|
||||
|
||||
module.exports = {
|
||||
name: 'index-page',
|
||||
runAfter: ['adding-extra-docs'],
|
||||
runBefore: ['extra-docs-added'],
|
||||
description: 'This processor creates docs that will be rendered as the index page for the app',
|
||||
init: function(config) {
|
||||
deployment = config.deployment;
|
||||
process: function(docs, config) {
|
||||
|
||||
var deployment = config.deployment;
|
||||
if ( !deployment || !deployment.environments ) {
|
||||
throw new Error('No deployment environments found in the config.');
|
||||
}
|
||||
},
|
||||
process: function(docs) {
|
||||
|
||||
// Collect up all the areas in the docs
|
||||
var areas = {};
|
||||
|
||||
@@ -3,20 +3,20 @@ var log = require('winston');
|
||||
var fs = require('fs');
|
||||
var path = require('canonical-path');
|
||||
|
||||
// Keywords to ignore
|
||||
var wordsToIgnore = [];
|
||||
var propertiesToIgnore;
|
||||
var areasToSearch;
|
||||
|
||||
// Keywords start with "ng:" or one of $, _ or a letter
|
||||
var KEYWORD_REGEX = /^((ng:|[\$_a-z])[\w\-_]+)/;
|
||||
|
||||
module.exports = {
|
||||
name: 'keywords',
|
||||
runAfter: ['docs-processed'],
|
||||
runBefore: ['adding-extra-docs'],
|
||||
description: 'This processor extracts all the keywords from the document',
|
||||
init: function(config) {
|
||||
process: function(docs, config) {
|
||||
|
||||
// Keywords to ignore
|
||||
var wordsToIgnore = [];
|
||||
var propertiesToIgnore;
|
||||
var areasToSearch;
|
||||
|
||||
// Keywords start with "ng:" or one of $, _ or a letter
|
||||
var KEYWORD_REGEX = /^((ng:|[\$_a-z])[\w\-_]+)/;
|
||||
|
||||
// Load up the keywords to ignore, if specified in the config
|
||||
if ( config.processing.search && config.processing.search.ignoreWordsFile ) {
|
||||
@@ -34,9 +34,6 @@ module.exports = {
|
||||
propertiesToIgnore = _.indexBy(config.get('processing.search.propertiesToIgnore', []));
|
||||
log.debug('Properties to ignore', propertiesToIgnore);
|
||||
|
||||
},
|
||||
process: function(docs) {
|
||||
|
||||
var ignoreWordsMap = _.indexBy(wordsToIgnore);
|
||||
|
||||
// If the title contains a name starting with ng, e.g. "ngController", then add the module name
|
||||
|
||||
@@ -11,17 +11,17 @@ var AREA_NAMES = {
|
||||
};
|
||||
|
||||
function getNavGroup(pages, area, pageSorter, pageMapper) {
|
||||
|
||||
|
||||
var navItems = _(pages)
|
||||
// We don't want the child to include the index page as this is already catered for
|
||||
.omit(function(page) { return page.id === 'index'; })
|
||||
|
||||
|
||||
// Apply the supplied sorting function
|
||||
.sortBy(pageSorter)
|
||||
|
||||
|
||||
// Apply the supplied mapping function
|
||||
.map(pageMapper)
|
||||
|
||||
|
||||
.value();
|
||||
|
||||
return {
|
||||
@@ -129,22 +129,22 @@ var navGroupMappers = {
|
||||
}
|
||||
};
|
||||
|
||||
var outputFolder;
|
||||
|
||||
module.exports = {
|
||||
name: 'pages-data',
|
||||
description: 'This plugin will create a new doc that will be rendered as an angularjs module ' +
|
||||
'which will contain meta information about the pages and navigation',
|
||||
runAfter: ['adding-extra-docs', 'component-groups-generate'],
|
||||
runAfter: ['adding-extra-docs', 'component-groups-generate', 'compute-path'],
|
||||
runBefore: ['extra-docs-added'],
|
||||
init: function(config) {
|
||||
outputFolder = config.rendering.outputFolder;
|
||||
},
|
||||
process: function(docs) {
|
||||
process: function(docs, config) {
|
||||
|
||||
var outputFolder = config.rendering.outputFolder;
|
||||
|
||||
_(docs)
|
||||
.filter(function(doc) { return doc.area === 'api'; })
|
||||
.filter(function(doc) { return doc.docType === 'module'; })
|
||||
.forEach(function(doc) { if ( !doc.path ) {
|
||||
log.warn('Missing path property for ', doc.id);
|
||||
}})
|
||||
.map(function(doc) { return _.pick(doc, ['id', 'module', 'docType', 'area']); })
|
||||
.tap(function(docs) {
|
||||
log.debug(docs);
|
||||
@@ -173,7 +173,7 @@ module.exports = {
|
||||
// - ngView
|
||||
// - section "service"
|
||||
// - $route
|
||||
//
|
||||
//
|
||||
var areas = {};
|
||||
_(navPages)
|
||||
.groupBy('area')
|
||||
@@ -188,13 +188,7 @@ module.exports = {
|
||||
area.navGroups = navGroupMapper(pages, area);
|
||||
});
|
||||
|
||||
_.forEach(docs, function(doc) {
|
||||
if ( !doc.path ) {
|
||||
log.warn('Missing path property for ', doc.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Extract a list of basic page information for mapping paths to paritals and for client side searching
|
||||
// Extract a list of basic page information for mapping paths to partials and for client side searching
|
||||
var pages = _(docs)
|
||||
.map(function(doc) {
|
||||
var page = _.pick(doc, [
|
||||
|
||||
@@ -1,45 +1,20 @@
|
||||
var _ = require('lodash');
|
||||
var log = require('winston');
|
||||
var path = require('canonical-path');
|
||||
var trimIndentation = require('dgeni/lib/utils/trim-indentation');
|
||||
var code = require('dgeni/lib/utils/code');
|
||||
var protractorFolder;
|
||||
|
||||
function createProtractorDoc(example, file, env) {
|
||||
var protractorDoc = {
|
||||
docType: 'e2e-test',
|
||||
id: 'protractorTest' + '-' + example.id,
|
||||
template: 'protractorTests.template.js',
|
||||
outputPath: path.join(protractorFolder, example.id, env + '_test.js'),
|
||||
innerTest: file.fileContents,
|
||||
pathPrefix: '.', // Hold for if we test with full jQuery
|
||||
exampleId: example.id,
|
||||
description: example.doc.id
|
||||
};
|
||||
|
||||
if (env === 'jquery') {
|
||||
protractorDoc.examplePath = example.outputFolder + '/index-jquery.html'
|
||||
} else {
|
||||
protractorDoc.examplePath = example.outputFolder + '/index.html'
|
||||
}
|
||||
return protractorDoc;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'protractor-generate',
|
||||
description: 'Generate a protractor test file from the e2e tests in the examples',
|
||||
runAfter: ['adding-extra-docs'],
|
||||
runBefore: ['extra-docs-added'],
|
||||
init: function(config, injectables) {
|
||||
protractorFolder = config.get('rendering.protractor.outputFolder', 'ptore2e');
|
||||
},
|
||||
process: function(docs, examples) {
|
||||
process: function(docs, examples, config) {
|
||||
var protractorFolder = config.get('rendering.protractor.outputFolder', 'ptore2e');
|
||||
|
||||
_.forEach(examples, function(example) {
|
||||
|
||||
_.forEach(example.files, function(file) {
|
||||
|
||||
// Check if it's a Protractor test.
|
||||
if (!(file.type == 'protractor')) {
|
||||
if (file.type !== 'protractor') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,5 +23,27 @@ module.exports = {
|
||||
docs.push(createProtractorDoc(example, file, 'jqlite'));
|
||||
});
|
||||
});
|
||||
|
||||
function createProtractorDoc(example, file, env) {
|
||||
var protractorDoc = {
|
||||
docType: 'e2e-test',
|
||||
id: 'protractorTest' + '-' + example.id,
|
||||
template: 'protractorTests.template.js',
|
||||
outputPath: path.join(protractorFolder, example.id, env + '_test.js'),
|
||||
innerTest: file.fileContents,
|
||||
pathPrefix: '.', // Hold for if we test with full jQuery
|
||||
exampleId: example.id,
|
||||
description: example.doc.id
|
||||
};
|
||||
|
||||
if (env === 'jquery') {
|
||||
protractorDoc.examplePath = example.outputFolder + '/index-jquery.html';
|
||||
} else {
|
||||
protractorDoc.examplePath = example.outputFolder + '/index.html';
|
||||
}
|
||||
return protractorDoc;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module.exports = {
|
||||
name: 'step',
|
||||
transformFn: function(doc, tag) {
|
||||
transforms: function(doc, tag, value) {
|
||||
if ( doc.docType !== 'tutorial' ) {
|
||||
throw new Error('Invalid tag, step. You should only use this tag on tutorial docs');
|
||||
}
|
||||
return parseInt(tag.description,10);
|
||||
return parseInt(value,10);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
<div class="container main-grid main-header-grid">
|
||||
<div class="grid-left">
|
||||
<div ng-controller="DocsVersionsCtrl" class="picker version-picker">
|
||||
<select ng-options="v as ('v' + v.full) group by (v.isStable?'Stable':'Unstable') for v in docs_versions"
|
||||
<select ng-options="v as ('v' + v.version + (v.isSnapshot ? ' (snapshot)' : '')) group by (v.isStable?'Stable':'Unstable') for v in docs_versions"
|
||||
ng-model="docs_version"
|
||||
ng-change="jumpToDocsVersion(docs_version)"
|
||||
class="docs-version-jump">
|
||||
@@ -219,7 +219,7 @@
|
||||
</div>
|
||||
<div class="grid-right">
|
||||
<div id="loading" ng-show="loading">Loading...</div>
|
||||
<div ng-hide="loading" ng-include="currentPage.outputPath" onload="afterPartialLoaded()" autoscroll></div>
|
||||
<div ng-hide="loading" ng-include="currentPage.outputPath || 'Error404.html'" onload="afterPartialLoaded()" autoscroll></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{# Be aware that we need these extra new lines here or marked will not realise that the <div>
|
||||
is HTML and wrap each line in a <p> - thus breaking the HTML #}
|
||||
|
||||
<div>
|
||||
<a ng-click="openPlunkr('{$ doc.example.outputFolder $}')" class="btn pull-right">
|
||||
<i class="glyphicon glyphicon-edit"> </i>
|
||||
Edit in Plunker</a>
|
||||
<div class="runnable-example"
|
||||
path="{$ doc.example.outputFolder $}"
|
||||
{%- for attrName, attrValue in doc.example.attributes %}
|
||||
{$ attrName $}="{$ attrValue $}"{% endfor %}>
|
||||
|
||||
{% for fileName, file in doc.example.files %}
|
||||
<div class="runnable-example-file" {% for attrName, attrValue in file.attributes %}
|
||||
{$ attrName $}="{$ attrValue $}"{% endfor %}>
|
||||
{% code -%}
|
||||
{$ file.fileContents $}
|
||||
{%- endcode %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<iframe class="runnable-example-frame" src="{$ doc.example.outputFolder $}/index.html" name="{$ doc.example.id $}"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Be aware that we need these extra new lines here or marked will not realise that the <div>
|
||||
above is HTML and wrap each line in a <p> - thus breaking the HTML #}
|
||||
@@ -6,7 +6,7 @@
|
||||
Welcome to the AngularJS API docs page. These pages contain the AngularJS reference materials for version <strong ng-bind="version"></strong>.
|
||||
|
||||
The documentation is organized into **{@link guide/module modules}** which contain various components of an AngularJS application.
|
||||
These components are {@link guide/directive directives}, {@link guide/dev_guide.services services}, {@link guide/filter filters}, {@link guide/providers providers}, {@link guide/templates types}, global APIs and testing mocks.
|
||||
These components are {@link guide/directive directives}, {@link guide/services services}, {@link guide/filter filters}, {@link guide/providers providers}, {@link guide/templates templates}, global APIs, and testing mocks.
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Angular Namespaces `$` and `$$`**
|
||||
@@ -212,7 +212,7 @@ Use ngTouch when developing for mobile browsers/devices.
|
||||
{@link ngTouch#service Services / Factories}
|
||||
</td>
|
||||
<td>
|
||||
The {@link ngTouch.$swipe $swipe} service is used to register and manage mobile DOM events.
|
||||
The {@link ngTouch.$swipe $swipe} service is used to register and manage mobile DOM events.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -252,7 +252,7 @@ Use ngSanitize to securely parse and manipulate HTML data in your application.
|
||||
|
||||
## {@link ngMock ngMock}
|
||||
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests
|
||||
|
||||
<div class="alert alert-info">Include the **angular-mocks.js** file into your test runner for this to work.</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ but the required directive controller is not present on the current DOM element
|
||||
|
||||
To resolve this error ensure that there is no typo in the required controller name and that the required directive controller is present on the current element.
|
||||
|
||||
If the required controller is expected to be on a ancestor element, make ensure that you prefix the controller name in the `require` definition with `^`.
|
||||
If the required controller is expected to be on a ancestor element, make sure that you prefix the controller name in the `require` definition with `^`.
|
||||
|
||||
If the required controller is optionally requested, use `?` or `^?` to specify that.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ myModule.directive('myDirective', function factory() {
|
||||
return {
|
||||
...
|
||||
scope: {
|
||||
'bind': '=localValue'
|
||||
localValue: '=bind'
|
||||
}
|
||||
...
|
||||
}
|
||||
@@ -30,6 +30,9 @@ Following are invalid uses of this directive:
|
||||
|
||||
<!-- ERROR because `myFn()=localValue` is an invalid statement -->
|
||||
<my-directive bind="myFn()">
|
||||
|
||||
<!-- ERROR because attribute bind wasn't provided -->
|
||||
<my-directive>
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -37,3 +37,17 @@ elements. For example:
|
||||
```
|
||||
<b>Hello</b> World!
|
||||
```
|
||||
|
||||
Watch out for html comments at the beginning or end of templates, as these can cause this error as
|
||||
well. Consider the following template:
|
||||
|
||||
```
|
||||
<div class='container'>
|
||||
<div class='wrapper>
|
||||
...
|
||||
</div> <!-- wrapper -->
|
||||
</div> <!-- container -->
|
||||
```
|
||||
|
||||
The `<!-- container -->` comment is interpreted as a second root element and causes the template to
|
||||
be invalid.
|
||||
|
||||
@@ -3,24 +3,31 @@
|
||||
@fullName Module Unavailable
|
||||
@description
|
||||
|
||||
This error occurs when trying to "re-open" a module that has not yet been defined.
|
||||
This error occurs when you declare a dependency on a module that isn't defined anywhere or hasn't
|
||||
been loaded in the current browser context.
|
||||
|
||||
When you receive this error, check that the name of the module in question is correct and that the
|
||||
file in which this module is defined has been loaded (either via `<script>` tag, loader like
|
||||
require.js, or testing harness like karma).
|
||||
|
||||
A less common reason for this error is trying to "re-open" a module that has not yet been defined.
|
||||
|
||||
To define a new module, call {@link angular.module angular.module} with a name
|
||||
and an array of dependent modules, like so:
|
||||
|
||||
```
|
||||
```js
|
||||
// When defining a module with no module dependencies,
|
||||
// the requires array should be defined and empty.
|
||||
// the array of dependencies should be defined and empty.
|
||||
var myApp = angular.module('myApp', []);
|
||||
```
|
||||
|
||||
To retrieve a reference to the same module for further configuration, call
|
||||
`angular.module` without the `requires` array.
|
||||
`angular.module` without the array argument.
|
||||
|
||||
```
|
||||
```js
|
||||
var myApp = angular.module('myApp');
|
||||
```
|
||||
|
||||
Calling `angular.module` without the `requires` array when the module has not yet
|
||||
been defined causes this error to be thrown. To fix it, define your module with
|
||||
a name and an empty array, as in the first example above.
|
||||
Calling `angular.module` without the array of dependencies when the module has not yet been defined
|
||||
causes this error to be thrown. To fix it, define your module with a name and an empty array, as in
|
||||
the first example above.
|
||||
|
||||
@@ -9,18 +9,19 @@ correctly. For example:
|
||||
|
||||
```
|
||||
angular.module('myApp', [])
|
||||
.controller('myCtrl', ['myService', function (myService) {
|
||||
.controller('MyController', ['myService', function (myService) {
|
||||
// Do something with myService
|
||||
}]);
|
||||
```
|
||||
|
||||
This code will fail with `$injector:unpr` if `myService` is not defined. Making
|
||||
sure each dependency is defined will fix the problem.
|
||||
The above code will fail with `$injector:unpr` if `myService` is not defined.
|
||||
|
||||
Making sure each dependency is defined will fix the problem, as noted below.
|
||||
|
||||
```
|
||||
angular.module('myApp', [])
|
||||
.service('myService', function () { /* ... */ })
|
||||
.controller('myCtrl', ['myService', function (myService) {
|
||||
.controller('MyController', ['myService', function (myService) {
|
||||
// Do something with myService
|
||||
}]);
|
||||
```
|
||||
```
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecff
|
||||
@fullName Referencing 'call', 'apply' and 'bind' Disallowed
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to invoke Function's 'call', 'apply' or 'bind'.
|
||||
|
||||
Angular bans the invocation of 'call', 'apply' and 'bind' from within expressions
|
||||
since access is a known way to modify the behaviour of existing functions.
|
||||
|
||||
To resolve this error, avoid using these methods in expressions.
|
||||
|
||||
Example expression that would result in this error:
|
||||
|
||||
```
|
||||
<div>{{user.sendInfo.call({}, true)}}</div>
|
||||
```
|
||||
@@ -1,18 +1,27 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecfld
|
||||
@fullName Referencing 'constructor' Field in Expression
|
||||
@fullName Referencing Disallowed Field in Expression
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to access an objects constructor field.
|
||||
Occurs when an expression attempts to access one of the following fields:
|
||||
|
||||
AngularJS bans constructor access from within expressions since constructor
|
||||
access is a known way to execute arbitrary Javascript code.
|
||||
* __proto__
|
||||
* __defineGetter__
|
||||
* __defineSetter__
|
||||
* __lookupGetter__
|
||||
* __lookupSetter__
|
||||
|
||||
To resolve this error, avoid constructor access. As a last resort, alias
|
||||
the constructor and access it through the alias instead.
|
||||
AngularJS bans access to these fields from within expressions since
|
||||
access is a known way to mess with native objects or
|
||||
to execute arbitrary Javascript code.
|
||||
|
||||
Example expression that would result in this error:
|
||||
To resolve this error, avoid using these fields in expressions. As a last resort,
|
||||
alias their value and access them through the alias instead.
|
||||
|
||||
Example expressions that would result in this error:
|
||||
|
||||
```
|
||||
<div>{{user.constructor.name}}</div>
|
||||
```
|
||||
<div>{{user.__proto__.hasOwnProperty = $emit}}</div>
|
||||
|
||||
<div>{{user.__defineGetter__('name', noop)}}</div>
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecobj
|
||||
@fullName Referencing Object Disallowed
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to access the 'Object' object (Root object in JavaScript).
|
||||
|
||||
Angular bans access to Object from within expressions since access is a known way to modify
|
||||
the behaviour of existing objects.
|
||||
|
||||
To resolve this error, avoid Object access.
|
||||
@@ -3,72 +3,310 @@
|
||||
@fullName Action Already In Progress
|
||||
@description
|
||||
|
||||
At any point in time there can be only one `$digest` or $apply operation in progress.
|
||||
The stack trace of this error allows you to trace the origin of the currently executing $apply or $digest call.
|
||||
At any point in time there can be only one `$digest` or `$apply` operation in progress. This is to
|
||||
prevent very hard to detect bugs from entering your application. The stack trace of this error
|
||||
allows you to trace the origin of the currently executing `$apply` or `$digest` call, which caused
|
||||
the error.
|
||||
|
||||
`$digest` or `$apply` are processing operational states of the Scope - data-structure in Angular that provides context for models and enables model mutation observation.
|
||||
## Background
|
||||
|
||||
Trying to reenter a `$digest` or `$apply` while one of them is already in progress is typically a sign of programming error that needs to be fixed.
|
||||
Angular uses a dirty-checking digest mechanism to monitor and update values of the scope during
|
||||
the processing of your application. The digest works by checking all the values that are being
|
||||
watched against their previous value and running any watch handlers that have been defined for those
|
||||
values that have changed.
|
||||
|
||||
This digest mechanism is triggered by calling `$digest` on a scope object. Normally you do not need
|
||||
to trigger a digest manually, because every external action that can trigger changes in your
|
||||
application, such as mouse events, timeouts or server responses, wrap the Angular application code
|
||||
in a block of code that will run `$digest` when the code completes.
|
||||
|
||||
You wrap Angular code in a block that will be followed by a `$digest` by calling `$apply` on a scope
|
||||
object. So, in pseudo-code, the process looks like this:
|
||||
|
||||
```
|
||||
element.on('mouseup', function() {
|
||||
scope.$apply(function() {
|
||||
$scope.doStuff();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
where `$apply()` looks something like:
|
||||
|
||||
```
|
||||
$apply = function(fn) {
|
||||
try {
|
||||
fn();
|
||||
} finally() {
|
||||
$digest();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Digest Phases
|
||||
|
||||
Angular keeps track of what phase of processing we are in, the relevant ones being `$apply` and
|
||||
`$digest`. Trying to reenter a `$digest` or `$apply` while one of them is already in progress is
|
||||
typically a sign of programming error that needs to be fixed. So Angular will throw this error when
|
||||
that occurs.
|
||||
|
||||
In most situations it should be well defined whether a piece of code will be run inside an `$apply`,
|
||||
in which case you should not be calling `$apply` or `$digest`, or it will be run outside, in which
|
||||
case you should wrap any code that will be interacting with Angular scope or services, in a call to
|
||||
`$apply`.
|
||||
|
||||
As an example, all Controller code should expect to be run within Angular, so it should have no need
|
||||
to call `$apply` or `$digest`. Conversely, code that is being trigger directly as a call back to
|
||||
some external event, from the DOM or 3rd party library, should expect that it is never called from
|
||||
within Angular, and so any Angular application code that it calls should first be wrapped in a call
|
||||
to $apply.
|
||||
|
||||
## Common Causes
|
||||
|
||||
Apart from simply incorrect calls to `$apply` or `$digest` there are some cases when you may get
|
||||
this error through no fault of your own.
|
||||
|
||||
### Inconsistent API (Sync/Async)
|
||||
|
||||
This error is often seen when interacting with an API that is sometimes sync and sometimes async.
|
||||
|
||||
For example:
|
||||
For example, imagine a 3rd party library that has a method which will retrieve data for us. Since it
|
||||
may be making an asynchronous call to a server, it accepts a callback function, which will be called
|
||||
when the data arrives.
|
||||
|
||||
```
|
||||
function MyController() {
|
||||
function MyController($scope, thirdPartyComponent) {
|
||||
thirdPartyComponent.getData(function(someData) {
|
||||
scope.$apply(function() {
|
||||
scope.someData = someData;
|
||||
$scope.$apply(function() {
|
||||
$scope.someData = someData;
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The controller constructor is always instantiated from within an $apply cycle, so if the third-party component called our callback synchronously, we'd be trying to enter the $apply again.
|
||||
We expect that our callback will be called asynchronously, and so from outside Angular. Therefore, we
|
||||
correctly wrap our application code that interacts with Angular in a call to `$apply`.
|
||||
|
||||
To resolve this type of issue, either fix the api to be always synchronous or asynchronous or wrap the call to the api with setTimeout call to make it always asynchronous.
|
||||
The problem comes if `getData()` decides to call the callback handler synchronously; perhaps it has
|
||||
the data already cached in memory and so it immediately calls the callback to return the data,
|
||||
synchronously.
|
||||
|
||||
Since, the `MyController` constructor is always instantiated from within an `$apply` call, our
|
||||
handler is trying to enter a new `$apply` block from within one.
|
||||
|
||||
Other situation that leads to this error is when you are trying to reuse a function to by using it as a callback for code that is called by various apis inside and outside of $apply.
|
||||
This is not an ideal design choice on the part of the 3rd party library.
|
||||
|
||||
For example:
|
||||
To resolve this type of issue, either fix the api to be always synchronous or asynchronous or force
|
||||
your callback handler to always run asynchronously by using the `$timeout` service.
|
||||
|
||||
```
|
||||
myApp.directive('myDirective', function() {
|
||||
function MyController($scope, thirdPartyComponent) {
|
||||
thirdPartyComponent.getData(function(someData) {
|
||||
$timeout(function() {
|
||||
$scope.someData = someData;
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Here we have used `$timeout` to schedule the changes to the scope in a future call stack.
|
||||
By providing a timeout period of 0ms, this will occur as soon as possible and `$timeout` will ensure
|
||||
that the code will be called in a single `$apply` block.
|
||||
|
||||
### Triggering Events Programmatically
|
||||
|
||||
The other situation that often leads to this error is when you trigger code (such as a DOM event)
|
||||
programmatically (from within Angular), which is normally called by an external trigger.
|
||||
|
||||
For example, consider a directive that will set focus on an input control when a value in the scope
|
||||
is true:
|
||||
|
||||
```
|
||||
myApp.directive('setFocusIf', function() {
|
||||
return {
|
||||
link: function($scope, $element) {
|
||||
function doSomeWork() {
|
||||
$scope.$apply(function() {
|
||||
// do work here, and update the model
|
||||
};
|
||||
}
|
||||
|
||||
$element.on('click', doSomeWork);
|
||||
doSomeWork(); // << this will throw an exception because templates are compiled within $apply
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
The fix for the example above looks like this:
|
||||
```
|
||||
myApp.directive('myDirective', function() {
|
||||
return {
|
||||
link: function($scope, $element) {
|
||||
function doSomeWork() {
|
||||
// do work here, and update the model
|
||||
}
|
||||
|
||||
$element.on('click', function() {
|
||||
$scope.$apply(doSomeWork); // <<< the $apply call was moved to the callsite that doesn't execute in $apply call already
|
||||
link: function($scope, $element, $attr) {
|
||||
$scope.$watch($attr.setFocusIf, function(value) {
|
||||
if ( value ) { $element[0].focus(); }
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
doSomeWork();
|
||||
If we applied this directive to an input which also used the `ngFocus` directive to trigger some
|
||||
work when the element receives focus we will have a problem:
|
||||
|
||||
```
|
||||
<input set-focus-if="hasFocus" ng-focus="msg='has focus'">
|
||||
<button ng-click="hasFocus = true">Focus</button>
|
||||
```
|
||||
|
||||
In this setup, there are two ways to trigger ngFocus. First from a user interaction:
|
||||
|
||||
* Click on the input control
|
||||
* The input control gets focus
|
||||
* The `ngFocus` directive is triggered, setting `$scope.msg='has focus'` from within a new call to
|
||||
`$apply()`
|
||||
|
||||
Second programmatically:
|
||||
|
||||
* Click the button
|
||||
* The `ngClick` directive sets the value of `$scope.hasFocus` to true inside a call to `$apply`
|
||||
* The `$digest` runs, which triggers the watch inside the `setFocusIf` directive
|
||||
* The watch's handle runs, which gives the focus to the input
|
||||
* The `ngFocus` directive is triggered, setting `$scope.msg='has focus'` from within a new call to
|
||||
`$apply()`
|
||||
|
||||
In this second scenario, we are already inside a `$digest` when the ngFocus directive makes another
|
||||
call to `$apply()`, causing this error to be thrown.
|
||||
|
||||
It is possible to workaround this problem by moving the call to set the focus outside of the digest,
|
||||
by using `$timeout(fn, 0, false)`, where the `false` value tells Angular not to wrap this `fn` in a
|
||||
`$apply` block:
|
||||
|
||||
```
|
||||
myApp.directive('setFocusIf', function($timeout) {
|
||||
return {
|
||||
link: function($scope, $element, $attr) {
|
||||
$scope.$watch($attr.setFocusIf, function(value) {
|
||||
if ( value ) {
|
||||
$timeout(function() {
|
||||
// We must reevaluate the value in case it was changed by a subsequent
|
||||
// watch handler in the digest.
|
||||
if ( $scope.$eval($attr.setFocusIf) ) {
|
||||
$element[0].focus();
|
||||
}
|
||||
}, 0, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
To learn more about Angular processing model please check out the {@link guide/concepts concepts doc} as well as the {@link ng.$rootScope.Scope api} doc.
|
||||
## Diagnosing This Error
|
||||
|
||||
When you get this error it can be rather daunting to diagnose the cause of the issue. The best
|
||||
course of action is to investigate the stack trace from the error. You need to look for places
|
||||
where `$apply` or `$digest` have been called and find the context in which this occurred.
|
||||
|
||||
There should be two calls:
|
||||
|
||||
* The first call is the good `$apply`/`$digest` and would normally be triggered by some event near
|
||||
the top of the call stack.
|
||||
|
||||
* The second call is the bad `$apply`/`$digest` and this is the one to investigate.
|
||||
|
||||
Once you have identified this call you work your way up the stack to see what the problem is.
|
||||
|
||||
* If the second call was made in your application code then you should look at why this code has been
|
||||
called from within a `$apply`/`$digest`. It may be a simple oversight or maybe it fits with the
|
||||
sync/async scenario described earlier.
|
||||
|
||||
* If the second call was made inside an Angular directive then it is likely that it matches the second
|
||||
programmatic event trigger scenario described earlier. In this case you may need to look further up
|
||||
the tree to what triggered the event in the first place.
|
||||
|
||||
### Example Problem
|
||||
|
||||
Let's look at how to investigate this error using the `setFocusIf` example from above. This example
|
||||
defines a new `setFocusIf` directive that sets the focus on the element where it is defined when the
|
||||
value of its attribute becomes true.
|
||||
|
||||
<example name="error-$rootScope-inprog" module="app">
|
||||
<file name="index.html">
|
||||
<button ng-click="focusInput = true">Focus</button>
|
||||
<input ng-focus="count = count + 1" set-focus-if="focusInput" />
|
||||
</file>
|
||||
<file name="app.js">
|
||||
angular.module('app', []).directive('setFocusIf', function() {
|
||||
return function link($scope, $element, $attr) {
|
||||
$scope.$watch($attr.setFocusIf, function(value) {
|
||||
if ( value ) { $element[0].focus(); }
|
||||
});
|
||||
};
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
When you click on the button to cause the focus to occur we get our `$rootScope:inprog` error. The
|
||||
stacktrace looks like this:
|
||||
|
||||
```
|
||||
Error: [$rootScope:inprog]
|
||||
at Error (native)
|
||||
at angular.min.js:6:467
|
||||
at n (angular.min.js:105:60)
|
||||
at g.$get.g.$apply (angular.min.js:113:195)
|
||||
at HTMLInputElement.<anonymous> (angular.min.js:198:401)
|
||||
at angular.min.js:32:32
|
||||
at Array.forEach (native)
|
||||
at q (angular.min.js:7:295)
|
||||
at HTMLInputElement.c (angular.min.js:32:14)
|
||||
at Object.fn (app.js:12:38) angular.js:10111
|
||||
(anonymous function) angular.js:10111
|
||||
$get angular.js:7412
|
||||
$get.g.$apply angular.js:12738 <--- $apply
|
||||
(anonymous function) angular.js:19833 <--- called here
|
||||
(anonymous function) angular.js:2890
|
||||
q angular.js:320
|
||||
c angular.js:2889
|
||||
(anonymous function) app.js:12
|
||||
$get.g.$digest angular.js:12469
|
||||
$get.g.$apply angular.js:12742 <--- $apply
|
||||
(anonymous function) angular.js:19833 <--- called here
|
||||
(anonymous function) angular.js:2890
|
||||
q angular.js:320
|
||||
```
|
||||
|
||||
We can see (even though the Angular code is minified) that there were two calls to `$apply`, first
|
||||
on line `19833`, then on line `12738` of `angular.js`.
|
||||
|
||||
It is this second call that caused the error. If we look at the angular.js code, we can see that
|
||||
this call is made by an Angular directive.
|
||||
|
||||
```
|
||||
var ngEventDirectives = {};
|
||||
forEach(
|
||||
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
|
||||
function(name) {
|
||||
var directiveName = directiveNormalize('ng-' + name);
|
||||
ngEventDirectives[directiveName] = ['$parse', function($parse) {
|
||||
return {
|
||||
compile: function($element, attr) {
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function(scope, element, attr) {
|
||||
element.on(lowercase(name), function(event) {
|
||||
scope.$apply(function() {
|
||||
fn(scope, {$event:event});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
It is not possible to tell which from the stack trace, but we happen to know in this case that it is
|
||||
the `ngFocus` directive.
|
||||
|
||||
Now look up the stack to see that our application code is only entered once in `app.js` at line `12`.
|
||||
This is where our problem is:
|
||||
|
||||
```
|
||||
10: link: function($scope, $element, $attr) {
|
||||
11: $scope.$watch($attr.setFocusIf, function(value) {
|
||||
12: if ( value ) { $element[0].focus(); } <---- This is the source of the problem
|
||||
13: });
|
||||
14: }
|
||||
```
|
||||
|
||||
We can now see that the second `$apply` was caused by us programmatically triggering a DOM event
|
||||
(i.e. focus) to occur. We must fix this by moving the code outside of the $apply block using
|
||||
`$timeout` as described above.
|
||||
|
||||
## Further Reading
|
||||
To learn more about Angular processing model please check out the
|
||||
{@link guide/concepts concepts doc} as well as the {@link ng.$rootScope.Scope api} doc.
|
||||
|
||||
@@ -15,9 +15,9 @@ By default, only URLs that belong to the same origin are trusted. These are urls
|
||||
The {@link ng.directive:ngInclude ngInclude} directive and {@link guide/directive directives} that specify a `templateUrl` require a trusted resource URL.
|
||||
|
||||
To load templates from other domains and/or protocols, either adjust the {@link
|
||||
api/ng.$sceDelegateProvider#resourceUrlWhitelist whitelist}/ {@link
|
||||
api/ng.$sceDelegateProvider#resourceUrlBlacklist blacklist} or wrap the URL with a call to {@link
|
||||
api/ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl}.
|
||||
ng.$sceDelegateProvider#resourceUrlWhitelist whitelist}/ {@link
|
||||
ng.$sceDelegateProvider#resourceUrlBlacklist blacklist} or wrap the URL with a call to {@link
|
||||
ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl}.
|
||||
|
||||
**Note**: The browser's [Same Origin
|
||||
Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) and
|
||||
|
||||
@@ -13,3 +13,4 @@ Angular template from a URL requires that the URL is one considered safe for loa
|
||||
This helps prevent XSS and other security issues. Read more at {@link
|
||||
api/ng.$sce Strict Contextual Escaping (SCE)}
|
||||
|
||||
You may want to include the ngSanitize module to use the automatic sanitizing.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
@name Error Reference
|
||||
@description
|
||||
|
||||
# Error Reference
|
||||
|
||||
Use the Error Reference manual to find information about error conditions in
|
||||
your AngularJS app. Errors thrown in production builds of AngularJS will log
|
||||
links to this site on the console.
|
||||
|
||||
@@ -20,7 +20,7 @@ application.
|
||||
</html>
|
||||
```
|
||||
|
||||
Note that for bootrapping purposes, the `<html>` element is the same as `document`, so the following
|
||||
Note that for bootstrapping purposes, the `<html>` element is the same as `document`, so the following
|
||||
will also throw an error.
|
||||
|
||||
```
|
||||
@@ -38,12 +38,12 @@ You can also get this error if you accidentally load AngularJS itself more than
|
||||
<html ng-app>
|
||||
<head>
|
||||
<script src="angular.js"></script>
|
||||
|
||||
|
||||
...
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
...
|
||||
|
||||
<script src="angular.js"></script>
|
||||
|
||||
@@ -49,7 +49,7 @@ changes to $location are reflected into the browser address bar.
|
||||
<tr>
|
||||
<td class="head">integration with angular application life-cycle</td>
|
||||
<td>none</td>
|
||||
<td>knows about all internal life-cycle phases, integrates with $watch, ...</td>
|
||||
<td>knows about all internal life-cycle phases, integrates with {@link ng.$rootScope.Scope#$watch $watch}, ...</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -60,7 +60,7 @@ changes to $location are reflected into the browser address bar.
|
||||
|
||||
<tr>
|
||||
<td class="head">aware of docroot/context from which the application is loaded</td>
|
||||
<td>no - window.location.path returns "/docroot/actual/path"</td>
|
||||
<td>no - window.location.pathname returns "/docroot/actual/path"</td>
|
||||
<td>yes - $location.path() returns "/actual/path"</td>
|
||||
</tr>
|
||||
|
||||
@@ -237,20 +237,6 @@ it('should show example', inject(
|
||||
));
|
||||
```
|
||||
|
||||
### Crawling your app
|
||||
|
||||
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
|
||||
your document:
|
||||
|
||||
```html
|
||||
<meta name="fragment" content="!" />
|
||||
```
|
||||
|
||||
This will cause crawler bot to request links with `_escaped_fragment_` param so that your server
|
||||
can recognize the crawler and serve a HTML snapshots. For more information about this technique,
|
||||
see [Making AJAX Applications
|
||||
Crawlable](http://code.google.com/web/ajaxcrawling/docs/specification.html).
|
||||
|
||||
## HTML5 mode
|
||||
|
||||
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
|
||||
@@ -344,20 +330,6 @@ are not prefixed with `.` and will not be intercepted by the `otherwise` rule in
|
||||
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)
|
||||
|
||||
### Crawling your app
|
||||
|
||||
If you want your AJAX application to be indexed by web crawlers, you will need to add the following
|
||||
meta tag to the HEAD section of your document:
|
||||
|
||||
```html
|
||||
<meta name="fragment" content="!" />
|
||||
```
|
||||
|
||||
This statement causes a crawler to request links with an empty `_escaped_fragment_` parameter so that
|
||||
your server can recognize the crawler and serve it HTML snapshots. For more information about this
|
||||
technique, see [Making AJAX
|
||||
Applications Crawlable](http://code.google.com/web/ajaxcrawling/docs/specification.html).
|
||||
|
||||
### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. You must either specify the url base in
|
||||
@@ -510,10 +482,12 @@ use a lower level API, {@link ng.$window $window.location.href}.
|
||||
## Using $location outside of the scope life-cycle
|
||||
|
||||
`$location` knows about Angular'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 $watchers / $observers are
|
||||
notified.
|
||||
the browser it updates the `$location` and calls `$apply` so that all
|
||||
{@link ng.$rootScope.Scope#$watch $watchers} /
|
||||
{@link ng.$compile.directive.Attributes#$observe $observers} are notified.
|
||||
When you change the `$location` inside the `$digest` phase everything is ok; `$location` will
|
||||
propagate this change into browser and will notify all the $watchers / $observers.
|
||||
propagate this change into browser and will notify all the {@link ng.$rootScope.Scope#$watch $watchers} /
|
||||
{@link ng.$compile.directive.Attributes#$observe $observers}.
|
||||
When you want to change the `$location` from outside Angular (for example, through a DOM Event or
|
||||
during testing) - you must call `$apply` to propagate the changes.
|
||||
|
||||
@@ -525,6 +499,20 @@ 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
|
||||
|
||||
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
|
||||
your document:
|
||||
|
||||
```html
|
||||
<meta name="fragment" content="!" />
|
||||
```
|
||||
|
||||
This will cause crawler bot to request links with `_escaped_fragment_` param so that your server
|
||||
can recognize the crawler and serve a HTML snapshots. For more information about this technique,
|
||||
see [Making AJAX Applications
|
||||
Crawlable](http://code.google.com/web/ajaxcrawling/docs/specification.html).
|
||||
|
||||
|
||||
# Testing with the $location service
|
||||
|
||||
@@ -632,25 +620,26 @@ then uses the information it obtains to compose hashbang URLs (such as
|
||||
|
||||
The Angular's compiler currently does not support two-way binding for methods (see [issue](https://github.com/angular/angular.js/issues/404)). If you should require two-way binding
|
||||
to the $location object (using {@link input[text] ngModel} directive on an input
|
||||
field), you will need to specify an extra model property (e.g. `locationPath`) with two watchers
|
||||
field), you will need to specify an extra model property (e.g. `locationPath`) with two {@link ng.$rootScope.Scope#$watch $watchers}
|
||||
which push $location updates in both directions. For example:
|
||||
<example>
|
||||
<example module="locationExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="LocationController">
|
||||
<input type="text" ng-model="locationPath" />
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
function LocationController($scope, $location) {
|
||||
$scope.$watch('locationPath', function(path) {
|
||||
$location.path(path);
|
||||
});
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function(path) {
|
||||
$scope.locationPath = path;
|
||||
});
|
||||
}
|
||||
angular.module('locationExample', [])
|
||||
.controller('LocationController', ['$scope', '$location', function ($scope, $location) {
|
||||
$scope.$watch('locationPath', function(path) {
|
||||
$location.path(path);
|
||||
});
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function(path) {
|
||||
$scope.locationPath = path;
|
||||
});
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
|
||||
|
||||
@@ -251,15 +251,18 @@ Although the CSS is a little different then what we saw before, the idea is the
|
||||
A handful of common AngularJS directives support and trigger animation hooks whenever any major event occurs during its life cycle.
|
||||
The table below explains in detail which animation events are triggered
|
||||
|
||||
| Directive | Supported Animations |
|
||||
|-------------------------------------------------------------------------------------|------------------------------------------|
|
||||
| {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave, and move |
|
||||
| {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave |
|
||||
| {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave |
|
||||
| {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave |
|
||||
| {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave |
|
||||
| {@link ng.directive:ngClass#usage_animations ngClass or {{class}}} | add and remove |
|
||||
| {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
|
||||
-| Directive | Supported Animations |
|
||||
-|-----------------------------------------------------------------|--------------------------------------------------------------------------|
|
||||
-| {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave and move |
|
||||
-| {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave |
|
||||
-| {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave |
|
||||
-| {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave |
|
||||
-| {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave |
|
||||
-| {@link ng.directive:ngClass#usage_animations ngClass} | add and remove |
|
||||
-| {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
|
||||
-| {@link ng.directive:form#usage_animations form} | add and remove (dirty, pristine, valid, invalid & all other validations) |
|
||||
-| {@link ng.directive:ngModel#usage_animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
|
||||
|
||||
|
||||
For a full breakdown of the steps involved during each animation event, refer to the {@link ngAnimate.$animate API docs}.
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
This page explains the Angular initialization process and how you can manually initialize Angular
|
||||
if necessary.
|
||||
|
||||
|
||||
## Angular `<script>` Tag
|
||||
|
||||
This example shows the recommended path for integrating Angular with what we call automatic
|
||||
@@ -79,7 +80,6 @@ If the {@link ng.directive:ngApp `ng-app`} directive is found then Angular will:
|
||||
|
||||
## Manual Initialization
|
||||
|
||||
|
||||
If you need to have more control over the initialization process, you can use a manual
|
||||
bootstrapping method instead. Examples of when you'd need to do this include using script loaders
|
||||
or the need to perform an operation before Angular compiles a page.
|
||||
@@ -88,24 +88,36 @@ Here is an example of manually initializing Angular:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<body>
|
||||
Hello {{'World'}}!
|
||||
<script src="http://code.angularjs.org/angular.js"></script>
|
||||
<script>
|
||||
angular.element(document).ready(function() {
|
||||
angular.module('myApp', []);
|
||||
angular.bootstrap(document, ['myApp']);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<body>
|
||||
Hello {{'World'}}!
|
||||
<script src="http://code.angularjs.org/snapshot/angular.js"></script>
|
||||
|
||||
<script>
|
||||
angular.module('myApp', [])
|
||||
.controller('MyController', ['$scope', function ($scope) {
|
||||
$scope.greetMe = 'World';
|
||||
}]);
|
||||
|
||||
angular.element(document).ready(function() {
|
||||
angular.bootstrap(document, ['myApp']);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Note that we have provided the name of our application module to be loaded into the injector as the second
|
||||
Note that we provided the name of our application module to be loaded into the injector as the second
|
||||
parameter of the {@link angular.bootstrap} function. Notice that `angular.bootstrap` will not create modules
|
||||
on the fly. You must create any custom {@link guide/module modules} before you pass them as a parameter.
|
||||
|
||||
You should call `angular.bootstrap()` *after* you've loaded or defined your modules.
|
||||
You cannot add controllers, services, directives, etc after an application bootstraps.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** You should not use the ng-app directive when manually bootstrapping your app.
|
||||
</div>
|
||||
|
||||
This is the sequence that your code should follow:
|
||||
|
||||
1. After the page and all of the code is loaded, find the root element of your AngularJS
|
||||
@@ -114,6 +126,7 @@ This is the sequence that your code should follow:
|
||||
2. Call {@link angular.bootstrap} to {@link compiler compile} the element into an
|
||||
executable, bi-directionally bound application.
|
||||
|
||||
|
||||
## Deferred Bootstrap
|
||||
|
||||
This feature enables tools like Batarang and test runners to
|
||||
|
||||
@@ -226,7 +226,7 @@ moved to the compile function for performance reasons.
|
||||
To understand, let's look at a real-world example with `ngRepeat`:
|
||||
|
||||
```html
|
||||
Hello {{user}}, you have these actions:
|
||||
Hello {{user.name}}, you have these actions:
|
||||
<ul>
|
||||
<li ng-repeat="action in user.actions">
|
||||
{{action.description}}
|
||||
@@ -236,7 +236,7 @@ Hello {{user}}, you have these actions:
|
||||
|
||||
When the above example is compiled, the compiler visits every node and looks for directives.
|
||||
|
||||
`{{user}}` matches the {@link ng.$interpolate interpolation directive}
|
||||
`{{user.name}}` matches the {@link ng.$interpolate interpolation directive}
|
||||
and `ng-repeat` matches the {@link ng.directive:ngRepeat `ngRepeat` directive}.
|
||||
|
||||
But {@link ng.directive:ngRepeat ngRepeat} has a dilemma.
|
||||
@@ -325,7 +325,7 @@ This will not render properly, unless we do some scope magic.
|
||||
|
||||
The first issue we have to solve is that the dialog box template expects `title` to be defined.
|
||||
But we would like the template's scope property `title` to be the result of interpolating the
|
||||
`<dialog>` element's `title` attribute (i.e. `"Hello {{username}}"`. Furthermore, the buttons expect
|
||||
`<dialog>` element's `title` attribute (i.e. `"Hello {{username}}"`). Furthermore, the buttons expect
|
||||
the `onOk` and `onCancel` functions to be present in the scope. This limits the usefulness of the
|
||||
widget. To solve the mapping issue we use the `locals` to create local variables which the template
|
||||
expects as follows:
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
@name Conceptual Overview
|
||||
@description
|
||||
|
||||
There are some concepts within Angular that you should understand before creating your first application.
|
||||
This section touches all important parts of Angular really quickly using a simple example.
|
||||
However, it won't explain all details.
|
||||
For a more in-depth explanation, have a look at the {@link tutorial/ tutorial}.
|
||||
# Conceptual Overview
|
||||
|
||||
This section briefly touches on all of the important parts of AngularJS using a simple example.
|
||||
For a more in-depth explanation, see the {@link tutorial/ tutorial}.
|
||||
|
||||
| Concept | Description |
|
||||
|------------------|------------------------------------------|
|
||||
|{@link concepts#template Template} | HTML with additional markup |
|
||||
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|
||||
|{@link concepts#model Model} | the data that is shown to the user and with which the user interacts |
|
||||
|{@link concepts#model Model} | the data shown to the user in the view and with which the user interacts |
|
||||
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|
||||
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|
||||
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|
||||
@@ -19,22 +19,22 @@ For a more in-depth explanation, have a look at the {@link tutorial/ tutorial}.
|
||||
|{@link concepts#view View} | what the user sees (the DOM) |
|
||||
|{@link concepts#databinding Data Binding} | sync data between the model and the view |
|
||||
|{@link concepts#controller Controller} | the business logic behind views |
|
||||
|{@link concepts#di Dependency Injection} | Creates and wires objects / functions |
|
||||
|{@link concepts#di Dependency Injection} | Creates and wires objects and functions |
|
||||
|{@link concepts#injector Injector} | dependency injection container |
|
||||
|{@link concepts#module Module} | configures the Injector |
|
||||
|{@link concepts#module Module} | a container for the different parts of an app including controllers, services, filters, directives which configures the Injector |
|
||||
|{@link concepts#service Service} | reusable business logic independent of views |
|
||||
|
||||
|
||||
# A first example: Data binding
|
||||
## A first example: Data binding
|
||||
|
||||
In the following example we will build a form to calculate the costs of an invoice in different currencies.
|
||||
|
||||
Let's start with input fields for quantity and cost whose values are multiplied to produce the total of the invoice:
|
||||
|
||||
|
||||
<example>
|
||||
<example name="guide-concepts-1" ng-app-included="true">
|
||||
<file name="index.html">
|
||||
<div ng-init="qty=1;cost=2">
|
||||
<div ng-app ng-init="qty=1;cost=2">
|
||||
<b>Invoice:</b>
|
||||
<div>
|
||||
Quantity: <input type="number" ng-model="qty" required >
|
||||
@@ -97,12 +97,12 @@ recalculated and the DOM is updated with their values.
|
||||
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
|
||||
|
||||
|
||||
# Adding UI logic: Controllers
|
||||
## Adding UI logic: Controllers
|
||||
|
||||
Let's add some more logic to the example that allows us to enter and calculate the costs in
|
||||
different currencies and also pay the invoice.
|
||||
|
||||
<example module="invoice1">
|
||||
<example name="guide-concepts-2" ng-app-included="true" >
|
||||
<file name="invoice1.js">
|
||||
angular.module('invoice1', [])
|
||||
.controller('InvoiceController', function() {
|
||||
@@ -128,7 +128,7 @@ different currencies and also pay the invoice.
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="InvoiceController as invoice">
|
||||
<div ng-app="invoice1" ng-controller="InvoiceController as invoice">
|
||||
<b>Invoice:</b>
|
||||
<div>
|
||||
Quantity: <input type="number" ng-model="invoice.qty" required >
|
||||
@@ -181,7 +181,7 @@ The following graphic shows how everything works together after we introduced th
|
||||
|
||||
<img style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding2.png">
|
||||
|
||||
# View independent business logic: Services
|
||||
## View independent business logic: Services
|
||||
|
||||
Right now, the `InvoiceController` contains all logic of our example. When the application grows it
|
||||
is a good practice to move view independent logic from the controller into a so called
|
||||
@@ -191,7 +191,7 @@ from the web, e.g. by calling the Yahoo Finance API, without changing the contro
|
||||
|
||||
Let's refactor our example and move the currency conversion into a service in another file:
|
||||
|
||||
<example module="invoice2">
|
||||
<example name="guide-concepts-2" ng-app-included="true">
|
||||
<file name="finance2.js">
|
||||
angular.module('finance2', [])
|
||||
.factory('currencyConverter', function() {
|
||||
@@ -228,7 +228,7 @@ Let's refactor our example and move the currency conversion into a service in an
|
||||
}]);
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="InvoiceController as invoice">
|
||||
<div ng-app="invoice2" ng-controller="InvoiceController as invoice">
|
||||
<b>Invoice:</b>
|
||||
<div>
|
||||
Quantity: <input type="number" ng-model="invoice.qty" required >
|
||||
@@ -254,7 +254,7 @@ Let's refactor our example and move the currency conversion into a service in an
|
||||
|
||||
What changed?
|
||||
We moved the `convertCurrency` function and the definition of the existing currencies
|
||||
into the new file `finance.js`. But how does the controller
|
||||
into the new file `finance2.js`. But how does the controller
|
||||
get a hold of the now separated function?
|
||||
|
||||
This is where <a name="di">"{@link di Dependency Injection}"</a> comes into play.
|
||||
@@ -297,12 +297,12 @@ Angular uses this array syntax to define the dependencies so that the DI also wo
|
||||
the code, which will most probably rename the argument name of the controller constructor function
|
||||
to something shorter like `a`.
|
||||
|
||||
# Accessing the backend
|
||||
## Accessing the backend
|
||||
|
||||
Let's finish our example by fetching the exchange rates from the Yahoo Finance API.
|
||||
The following example shows how this is done with Angular:
|
||||
|
||||
<example module="invoice3">
|
||||
<example name="guide-concepts-3" ng-app-included="true">
|
||||
<file name="invoice3.js">
|
||||
angular.module('invoice3', ['finance3'])
|
||||
.controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
|
||||
@@ -356,7 +356,7 @@ The following example shows how this is done with Angular:
|
||||
}]);
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="InvoiceController as invoice">
|
||||
<div ng-app="invoice3" ng-controller="InvoiceController as invoice">
|
||||
<b>Invoice:</b>
|
||||
<div>
|
||||
Quantity: <input type="number" ng-model="invoice.qty" required >
|
||||
@@ -379,8 +379,8 @@ The following example shows how this is done with Angular:
|
||||
</example>
|
||||
|
||||
What changed?
|
||||
Our `currencyConverter` service of the `finance` module now uses the
|
||||
{@link ng.$http $http} service, a builtin service provided by Angular
|
||||
for accessing the backend. It is a wrapper around [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
|
||||
and [JSONP](http://en.wikipedia.org/wiki/JSONP) transports. Details can be found in the api docs of that service.
|
||||
Our `currencyConverter` service of the `finance` module now uses the {@link ng.$http `$http`}, a
|
||||
built-in service provided by Angular for accessing a server backend. `$http` is a wrapper around
|
||||
[`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
|
||||
and [JSONP](http://en.wikipedia.org/wiki/JSONP) transports.
|
||||
|
||||
|
||||
@@ -37,27 +37,8 @@ The properties contain the **view model** (the model that will be presented by t
|
||||
`$scope` properties will be available to the template at the point in the DOM where the Controller
|
||||
is registered.
|
||||
|
||||
The following example shows a very simple constructor function for a Controller, `GreetingController`,
|
||||
which attaches a `greeting` property containing the string `'Hola!'` to the `$scope`:
|
||||
|
||||
```js
|
||||
function GreetingController($scope) {
|
||||
$scope.greeting = 'Hola!';
|
||||
}
|
||||
```
|
||||
|
||||
Once the Controller has been attached to the DOM, the `greeting` property can be data-bound to the
|
||||
template:
|
||||
|
||||
```js
|
||||
<div ng-controller="GreetingController">
|
||||
{{ greeting }}
|
||||
</div>
|
||||
```
|
||||
|
||||
**NOTE**: Although Angular allows you to create Controller functions in the global scope, this is
|
||||
not recommended. In a real application you should use the `.controller` method of your
|
||||
{@link module Angular Module} for your application as follows:
|
||||
The following example demonstrates creating a `GreetingController`, which attaches a `greeting`
|
||||
property containing the string `'Hola!'` to the `$scope`:
|
||||
|
||||
```js
|
||||
var myApp = angular.module('myApp',[]);
|
||||
@@ -67,9 +48,24 @@ myApp.controller('GreetingController', ['$scope', function($scope) {
|
||||
}]);
|
||||
```
|
||||
|
||||
We create an {@link module Angular Module}, `myApp`, for our application. Then we add the controller's
|
||||
constructor function to the module using the `.controller()` method. This keeps the controller's
|
||||
constructor function out of the global scope.
|
||||
|
||||
<div class="alert alert-info">
|
||||
We have used an **inline injection annotation** to explicitly specify the dependency
|
||||
of the Controller on the `$scope` service provided by Angular. See the guide on
|
||||
[Dependency Injection](http://docs.angularjs.org/guide/di) for more information.
|
||||
{@link guide/di Dependency Injection} for more information.
|
||||
</div>
|
||||
|
||||
We attach our controller to the DOM using the `ng-controller` directive. The `greeting` property can
|
||||
now be data-bound to the template:
|
||||
|
||||
```js
|
||||
<div ng-controller="GreetingController">
|
||||
{{ greeting }}
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
# Adding Behavior to a Scope Object
|
||||
@@ -134,7 +130,7 @@ string "very". Depending on which button is clicked, the `spice` model is set to
|
||||
|
||||
<example module="spicyApp1">
|
||||
<file name="index.html">
|
||||
<div ng-controller="SpicyCtrl">
|
||||
<div ng-controller="SpicyController">
|
||||
<button ng-click="chiliSpicy()">Chili</button>
|
||||
<button ng-click="jalapenoSpicy()">Jalapeño</button>
|
||||
<p>The food is {{spice}} spicy!</p>
|
||||
@@ -143,7 +139,7 @@ string "very". Depending on which button is clicked, the `spice` model is set to
|
||||
<file name="app.js">
|
||||
var myApp = angular.module('spicyApp1', []);
|
||||
|
||||
myApp.controller('SpicyCtrl', ['$scope', function($scope){
|
||||
myApp.controller('SpicyController', ['$scope', function($scope) {
|
||||
$scope.spice = 'very';
|
||||
|
||||
$scope.chiliSpicy = function() {
|
||||
@@ -160,9 +156,9 @@ string "very". Depending on which button is clicked, the `spice` model is set to
|
||||
Things to notice in the example above:
|
||||
|
||||
- The `ng-controller` directive is used to (implicitly) create a scope for our template, and the
|
||||
scope is augmented (managed) by the `SpicyCtrl` Controller.
|
||||
- `SpicyCtrl` is just a plain JavaScript function. As an (optional) naming convention the name
|
||||
starts with capital letter and ends with "Ctrl" or "Controller".
|
||||
scope is augmented (managed) by the `SpicyController` Controller.
|
||||
- `SpicyController` is just a plain JavaScript function. As an (optional) naming convention the name
|
||||
starts with capital letter and ends with "Controller" or "Controller".
|
||||
- Assigning a property to `$scope` creates or updates the model.
|
||||
- Controller methods can be created through direct assignment to scope (see the `chiliSpicy` method)
|
||||
- The Controller methods and properties are available in the template (for the `<div>` element and
|
||||
@@ -175,7 +171,7 @@ previous example.
|
||||
|
||||
<example module="spicyApp2">
|
||||
<file name="index.html">
|
||||
<div ng-controller="SpicyCtrl">
|
||||
<div ng-controller="SpicyController">
|
||||
<input ng-model="customSpice">
|
||||
<button ng-click="spicy('chili')">Chili</button>
|
||||
<button ng-click="spicy(customSpice)">Custom spice</button>
|
||||
@@ -185,18 +181,18 @@ previous example.
|
||||
<file name="app.js">
|
||||
var myApp = angular.module('spicyApp2', []);
|
||||
|
||||
myApp.controller('SpicyCtrl', ['$scope', function($scope){
|
||||
myApp.controller('SpicyController', ['$scope', function($scope) {
|
||||
$scope.customSpice = "wasabi";
|
||||
$scope.spice = 'very';
|
||||
|
||||
$scope.spicy = function(spice){
|
||||
$scope.spicy = function(spice) {
|
||||
$scope.spice = spice;
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
|
||||
Notice that the `SpicyCtrl` Controller now defines just one method called `spicy`, which takes one
|
||||
Notice that the `SpicyController` Controller now defines just one method called `spicy`, which takes one
|
||||
argument called `spice`. The template then refers to this Controller method and passes in a string
|
||||
constant `'chili'` in the binding for the first button and a model property `customSpice` (bound to an
|
||||
input box) in the second button.
|
||||
@@ -213,13 +209,13 @@ more information about scope inheritance.
|
||||
<example module="scopeInheritance">
|
||||
<file name="index.html">
|
||||
<div class="spicy">
|
||||
<div ng-controller="MainCtrl">
|
||||
<div ng-controller="MainController">
|
||||
<p>Good {{timeOfDay}}, {{name}}!</p>
|
||||
|
||||
<div ng-controller="ChildCtrl">
|
||||
<div ng-controller="ChildController">
|
||||
<p>Good {{timeOfDay}}, {{name}}!</p>
|
||||
|
||||
<div ng-controller="GrandChildCtrl">
|
||||
<div ng-controller="GrandChildController">
|
||||
<p>Good {{timeOfDay}}, {{name}}!</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -234,16 +230,16 @@ more information about scope inheritance.
|
||||
</file>
|
||||
<file name="app.js">
|
||||
var myApp = angular.module('scopeInheritance', []);
|
||||
myApp.controller('MainCtrl', ['$scope', function($scope){
|
||||
myApp.controller('MainController', ['$scope', function($scope) {
|
||||
$scope.timeOfDay = 'morning';
|
||||
$scope.name = 'Nikki';
|
||||
}]);
|
||||
myApp.controller('ChildCtrl', ['$scope', function($scope){
|
||||
myApp.controller('ChildController', ['$scope', function($scope) {
|
||||
$scope.name = 'Mattie';
|
||||
}]);
|
||||
myApp.controller('GrandChildCtrl', ['$scope', function($scope){
|
||||
myApp.controller('GrandChildController', ['$scope', function($scope) {
|
||||
$scope.timeOfDay = 'evening';
|
||||
$scope.name = 'Gingerbreak Baby';
|
||||
$scope.name = 'Gingerbread Baby';
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
@@ -252,11 +248,11 @@ Notice how we nested three `ng-controller` directives in our template. This will
|
||||
scopes being created for our view:
|
||||
|
||||
- The root scope
|
||||
- The `MainCtrl` scope, which contains `timeOfDay` and `name` properties
|
||||
- The `ChildCtrl` scope, which inherits the `timeOfDay` property but overrides (hides) the `name`
|
||||
- The `MainController` scope, which contains `timeOfDay` and `name` properties
|
||||
- The `ChildController` scope, which inherits the `timeOfDay` property but overrides (hides) the `name`
|
||||
property from the previous
|
||||
- The `GrandChildCtrl` scope, which overrides (hides) both the `timeOfDay` property defined in `MainCtrl`
|
||||
and the `name` property defined in `ChildCtrl`
|
||||
- The `GrandChildController` scope, which overrides (hides) both the `timeOfDay` property defined in `MainController`
|
||||
and the `name` property defined in `ChildController`
|
||||
|
||||
Inheritance works with methods in the same way as it does with properties. So in our previous
|
||||
examples, all of the properties could be replaced with methods that return string values.
|
||||
@@ -273,8 +269,8 @@ involves injecting the {@link ng.$rootScope $rootScope} and {@link ng.$controlle
|
||||
|
||||
myApp.controller('MyController', function($scope) {
|
||||
$scope.spices = [{"name":"pasilla", "spiciness":"mild"},
|
||||
{"name":"jalapeno", "spiceiness":"hot hot hot!"},
|
||||
{"name":"habanero", "spiceness":"LAVA HOT!!"}];
|
||||
{"name":"jalapeno", "spiciness":"hot hot hot!"},
|
||||
{"name":"habanero", "spiciness":"LAVA HOT!!"}];
|
||||
$scope.spice = "habanero";
|
||||
});
|
||||
```
|
||||
@@ -316,11 +312,11 @@ describe('state', function() {
|
||||
|
||||
beforeEach(inject(function($rootScope, $controller) {
|
||||
mainScope = $rootScope.$new();
|
||||
$controller('MainCtrl', {$scope: mainScope});
|
||||
$controller('MainController', {$scope: mainScope});
|
||||
childScope = mainScope.$new();
|
||||
$controller('ChildCtrl', {$scope: childScope});
|
||||
$controller('ChildController', {$scope: childScope});
|
||||
grandChildScope = childScope.$new();
|
||||
$controller('GrandChildCtrl', {$scope: grandChildScope});
|
||||
$controller('GrandChildController', {$scope: grandChildScope});
|
||||
}));
|
||||
|
||||
it('should have over and selected', function() {
|
||||
@@ -333,6 +329,3 @@ describe('state', function() {
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Angular sets these CSS classes. It is up to your application to provide useful s
|
||||
# CSS classes used by angular
|
||||
|
||||
* `ng-scope`
|
||||
- **Usage:** angular applies this class to any element that where a new {@link ng.$rootScope.Scope scope}
|
||||
- **Usage:** angular applies this class to any element for which a new {@link api/ng.$rootScope.Scope scope}
|
||||
is defined. (see {@link guide/scope scope} guide for more information about scopes)
|
||||
|
||||
* `ng-binding`
|
||||
|
||||
@@ -9,7 +9,7 @@ When the model changes, the view reflects the change, and vice versa.
|
||||
|
||||
## Data Binding in Classical Template Systems
|
||||
|
||||
<img class="right" src="img/One_Way_Data_Binding.png"/>
|
||||
<img class="right" src="img/One_Way_Data_Binding.png"/><br />
|
||||
Most templating systems bind data in only one direction: they merge template and model components
|
||||
together into a view. After the merge occurs, changes to the model
|
||||
or related sections of the view are NOT automatically reflected in the view. Worse, any changes
|
||||
@@ -18,7 +18,7 @@ to write code that constantly syncs the view with the model and the model with t
|
||||
|
||||
## Data Binding in Angular Templates
|
||||
|
||||
<img class="right" src="img/Two_Way_Data_Binding.png"/>
|
||||
<img class="right" src="img/Two_Way_Data_Binding.png"/><br />
|
||||
Angular templates work differently. First the template (which is the uncompiled HTML along with
|
||||
any additional markup or directives) is compiled on the browser. The compilation step produces a
|
||||
live view. Any changes to the view are immediately reflected in the model, and any changes in
|
||||
|
||||
+142
-83
@@ -4,30 +4,29 @@
|
||||
|
||||
# Dependency Injection
|
||||
|
||||
Dependency Injection (DI) is a software design pattern that deals with how code gets hold of its
|
||||
dependencies.
|
||||
Dependency Injection (DI) is a software design pattern that deals with how components get hold of
|
||||
their dependencies.
|
||||
|
||||
The Angular injector subsystem is in charge of service instantiation, resolution
|
||||
of dependencies, and provision of dependencies to components as requested.
|
||||
The Angular injector subsystem is in charge of creating components, resolving their dependencies,
|
||||
and providing them to other components as requested.
|
||||
|
||||
For in-depth discussion about DI, see
|
||||
[Dependency Injection](http://en.wikipedia.org/wiki/Dependency_injection) at Wikipedia,
|
||||
[Inversion of Control](http://martinfowler.com/articles/injection.html) by Martin Fowler,
|
||||
or read about DI in your favorite software design pattern book.
|
||||
|
||||
## DI in a nutshell
|
||||
## DI in a Nutshell
|
||||
|
||||
There are only three ways an object or a function can get a hold of its dependencies:
|
||||
|
||||
1. The dependency can be created, typically using the `new` operator.
|
||||
2. The dependency can be looked up by referring to a global variable.
|
||||
3. The dependency can be passed in to where it is needed.
|
||||
There are only three ways a component (object or function) can get a hold of its dependencies:
|
||||
|
||||
1. The component can create the dependency, typically using the `new` operator.
|
||||
2. The component can look up the dependency, by referring to a global variable.
|
||||
3. The component can have the dependency passed to it where it is needed.
|
||||
|
||||
The first two options of creating or looking up dependencies are not optimal because they hard
|
||||
code the dependency. This makes it difficult, if not impossible, to modify the dependencies.
|
||||
This is especially problematic in tests, where it is often desirable to provide mock dependencies
|
||||
for test isolation.
|
||||
code the dependency to the component. This makes it difficult, if not impossible, to modify the
|
||||
dependencies. This is especially problematic in tests, where it is often desirable to provide mock
|
||||
dependencies for test isolation.
|
||||
|
||||
The third option is the most viable, since it removes the responsibility of locating the
|
||||
dependency from the component. The dependency is simply handed to the component.
|
||||
@@ -42,8 +41,8 @@ SomeClass.prototype.doSomething = function(name) {
|
||||
}
|
||||
```
|
||||
|
||||
In the above example `SomeClass` is not concerned with locating the `greeter` dependency, it
|
||||
is simply handed the `greeter` at runtime.
|
||||
In the above example `SomeClass` is not concerned with creating or locating the `greeter`
|
||||
dependency, it is simply handed the `greeter` when it is instantiated.
|
||||
|
||||
This is desirable, but it puts the responsibility of getting hold of the dependency on the
|
||||
code that constructs `SomeClass`.
|
||||
@@ -51,74 +50,90 @@ code that constructs `SomeClass`.
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-module-injector.png">
|
||||
|
||||
To manage the responsibility of dependency creation, each Angular application has an {@link
|
||||
angular.injector injector}. The injector is a service locator that is responsible for
|
||||
angular.injector injector}. The injector is a
|
||||
[service locator](http://en.wikipedia.org/wiki/Service_locator_pattern) that is responsible for
|
||||
construction and lookup of dependencies.
|
||||
|
||||
Here is an example of using the injector service:
|
||||
|
||||
```js
|
||||
// Provide the wiring information in a module
|
||||
angular.module('myModule', []).
|
||||
var myModule = angular.module('myModule', []);
|
||||
```
|
||||
|
||||
// Teach the injector how to build a 'greeter'
|
||||
// Notice that greeter itself is dependent on '$window'
|
||||
factory('greeter', function($window) {
|
||||
// This is a factory function, and is responsible for
|
||||
// creating the 'greet' service.
|
||||
return {
|
||||
greet: function(text) {
|
||||
$window.alert(text);
|
||||
}
|
||||
};
|
||||
});
|
||||
Teach the injector how to build a `greeter` service. Notice that `greeter` is dependent on the
|
||||
`$window` service. The `greeter` service is an object that contains a `greet` method.
|
||||
|
||||
// New injector is created from the module.
|
||||
// (This is usually done automatically by angular bootstrap)
|
||||
```js
|
||||
myModule.factory('greeter', function($window) {
|
||||
return {
|
||||
greet: function(text) {
|
||||
$window.alert(text);
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
Create a new injector that can provide components defined in our `myModule` module and request our
|
||||
`greeter` service from the injector. (This is usually done automatically by angular bootstrap).
|
||||
|
||||
```js
|
||||
var injector = angular.injector(['myModule', 'ng']);
|
||||
|
||||
// Request any dependency from the injector
|
||||
var greeter = injector.get('greeter');
|
||||
```
|
||||
|
||||
Asking for dependencies solves the issue of hard coding, but it also means that the injector needs
|
||||
to be passed throughout the application. Passing the injector breaks the [Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). To remedy this, we turn the
|
||||
dependency lookup responsibility to the injector by declaring the dependencies as in this example:
|
||||
to be passed throughout the application. Passing the injector breaks the
|
||||
[Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). To remedy this, we use a declarative
|
||||
notation in our HTML templates, to hand the responsibility of creating components over to the
|
||||
injector, as in this example:
|
||||
|
||||
```html
|
||||
<!-- Given this HTML -->
|
||||
<div ng-controller="MyController">
|
||||
<button ng-click="sayHello()">Hello</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
```js
|
||||
// And this controller definition
|
||||
function MyController($scope, greeter) {
|
||||
$scope.sayHello = function() {
|
||||
greeter.greet('Hello World');
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
// The 'ng-controller' directive does this behind the scenes
|
||||
When Angular compiles the HTML, it processes the `ng-controller` directive, which in turn
|
||||
asks the injector to create an instance of the controller and its dependencies.
|
||||
|
||||
```js
|
||||
injector.instantiate(MyController);
|
||||
```
|
||||
|
||||
Notice that by having the `ng-controller` instantiate the class, it can satisfy all of the
|
||||
dependencies of `MyController` without the controller ever knowing about the injector. This is
|
||||
the best outcome. The application code simply asks for the dependencies it needs, without having to
|
||||
deal with the injector. This setup does not break the Law of Demeter.
|
||||
This is all done behinds the scenes. Notice that by having the `ng-controller` ask the injector to
|
||||
instantiate the class, it can satisfy all of the dependencies of `MyController` without the
|
||||
controller ever knowing about the injector.
|
||||
|
||||
This is the best outcome. The application code simply declares the dependencies it needs, without
|
||||
having to deal with the injector. This setup does not break the Law of Demeter.
|
||||
|
||||
|
||||
## Dependency Annotation
|
||||
|
||||
How does the injector know what service needs to be injected?
|
||||
**How does the injector know what components need to be injected?**
|
||||
|
||||
The application developer needs to provide annotation information that the injector uses in order
|
||||
to resolve the dependencies. Throughout Angular, certain API functions are invoked using the
|
||||
injector, as per the API documentation. The injector needs to know what services to inject into
|
||||
the function. Below are three equivalent ways of annotating your code with service name
|
||||
information. These can be used interchangeably as you see fit and are equivalent.
|
||||
the function. There are three equivalent ways of annotating your code with service name
|
||||
information:
|
||||
|
||||
### Inferring Dependencies
|
||||
- Implicitly from the function parameter names
|
||||
- Using the `$inject` property annotation
|
||||
- Using the inline array annotation
|
||||
|
||||
These can be used interchangeably as you see fit and are equivalent.
|
||||
|
||||
### Implicit Dependencies
|
||||
|
||||
The simplest way to get hold of the dependencies, is to assume that the function parameter names
|
||||
are the names of the dependencies.
|
||||
@@ -134,11 +149,12 @@ function declaration and extracting the parameter names. In the above example `$
|
||||
`greeter` are two services which need to be injected into the function.
|
||||
|
||||
While straightforward, this method will not work with JavaScript minifiers/obfuscators as they
|
||||
rename the method parameter names. This makes this way of annotating only useful for [pretotyping](http://www.pretotyping.org/), and demo applications.
|
||||
rename the method parameter names. This makes this way of annotating only useful for
|
||||
[pretotyping](http://www.pretotyping.org/), and demo applications.
|
||||
|
||||
### `$inject` Annotation
|
||||
### `$inject` Property Annotation
|
||||
|
||||
To allow the minifers to rename the function parameters and still be able to inject right services
|
||||
To allow the minifiers to rename the function parameters and still be able to inject right services,
|
||||
the function needs to be annotated with the `$inject` property. The `$inject` property is an array
|
||||
of service names to inject.
|
||||
|
||||
@@ -149,18 +165,18 @@ var MyController = function(renamed$scope, renamedGreeter) {
|
||||
MyController['$inject'] = ['$scope', 'greeter'];
|
||||
```
|
||||
|
||||
In this scenario the ordering of the values in the '$inject' array must match the ordering of the arguments to inject.
|
||||
Using above code snippet as an example, '$scope' will be injected into 'renamed$scope' and 'greeter' into 'renamedGreeter'.
|
||||
Care must be taken that the `$inject` annotation is kept in sync with the actual arguments in the
|
||||
function declaration.
|
||||
In this scenario the ordering of the values in the `$inject` array must match the ordering of the
|
||||
arguments to inject. Using above code snippet as an example, `$scope` will be injected into
|
||||
`renamed$scope` and `greeter` into `renamedGreeter`. Care must be taken that the `$inject`
|
||||
annotation is kept in sync with the actual arguments in the function declaration.
|
||||
|
||||
This method of annotation is useful for controller declarations since it assigns the annotation
|
||||
information with the function.
|
||||
|
||||
### Inline Annotation
|
||||
### Inline Array Annotation
|
||||
|
||||
Sometimes using the `$inject` annotation style is not convenient such as when annotating
|
||||
directives.
|
||||
directives or services defined inline by a factory function.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -190,18 +206,76 @@ someModule.factory('greeter', ['$window', function(renamed$window) {
|
||||
}]);
|
||||
```
|
||||
|
||||
Here, instead of simply providing the factory function, we pass an array, whose elements consist of
|
||||
a list of strings (the names of the dependencies) followed by the function itself.
|
||||
|
||||
Keep in mind that all of the annotation styles are equivalent and can be used anywhere in Angular
|
||||
where injection is supported.
|
||||
|
||||
## Where can I use DI?
|
||||
## Where Can I Use DI?
|
||||
|
||||
DI is pervasive throughout Angular. You can use it in controllers, services, directives, filters,
|
||||
animations, and `run` and `config` blocks.
|
||||
DI is pervasive throughout Angular. You can use it when defining components or when providing `run`
|
||||
and `config` blocks for a module.
|
||||
|
||||
### DI in controllers
|
||||
- Components such as services, directives, filters and animations are defined by an injectable factory
|
||||
method or constructor function. These components can be injected with "service" and "value"
|
||||
components as dependencies.
|
||||
|
||||
Controllers are classes which are responsible for application behavior. The recommended way of
|
||||
declaring controllers is using the array notation:
|
||||
- The `run` method accepts a function, which can be injected with "service", "value" and "constant"
|
||||
components as dependencies. Note that you cannot inject "providers" into `run` blocks.
|
||||
|
||||
- The `config` method accepts a function, which can be injected with "provider" and "constant"
|
||||
components as dependencies. Note that you cannot inject "service" or "value" components into
|
||||
configuration
|
||||
|
||||
- Controllers are defined by a constructor function, which can be injected with any of the "service"
|
||||
and "value" components as dependencies, but they can also be provided with special dependencies. See
|
||||
{@link di#controllers Controllers} below for a list of these special dependencies.
|
||||
|
||||
See {@link module#module-loading-dependencies Modules} for more details about injecting dependencies
|
||||
into `run` and `config` blocks.
|
||||
|
||||
|
||||
### Factory Methods
|
||||
|
||||
Factory methods are responsible for creating most objects in Angular. Examples are directives,
|
||||
services, and filters. The factory methods are registered with the module, and the recommended way
|
||||
of declaring factories is:
|
||||
|
||||
```js
|
||||
angular.module('myModule', [])
|
||||
.factory('serviceId', ['depService', function(depService) {
|
||||
...
|
||||
}])
|
||||
.directive('directiveName', ['depService', function(depService) {
|
||||
...
|
||||
}])
|
||||
.filter('filterName', ['depService', function(depService) {
|
||||
...
|
||||
}]);
|
||||
```
|
||||
|
||||
### Module Methods
|
||||
|
||||
We can specify functions to run at configuration and run time for a module by calling the `run` and
|
||||
`config` methods. These functions are injectable with dependencies just like the factory functions
|
||||
above.
|
||||
|
||||
```js
|
||||
angular.module('myModule', [])
|
||||
.config(['depProvider', function(depProvider){
|
||||
...
|
||||
}])
|
||||
.run(['depService', function(depService) {
|
||||
...
|
||||
}]);
|
||||
```
|
||||
|
||||
### Controllers
|
||||
|
||||
Controllers are "classes" or "constructor functions" that are responsible for providing the
|
||||
application behavior that supports the declarative markup in the template. The recommended way of
|
||||
declaring Controllers is using the array notation:
|
||||
|
||||
```js
|
||||
someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
|
||||
@@ -215,28 +289,13 @@ someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope
|
||||
|
||||
This avoids the creation of global functions for controllers and also protects against minification.
|
||||
|
||||
Controllers are special in that, unlike services, there can be many instances of them in the
|
||||
application. For example, there would be one instance for every `ng-controller` directive in the template.
|
||||
|
||||
### Factory methods
|
||||
Moreover, additional dependencies are made available to Controllers:
|
||||
|
||||
Factory methods are responsible for creating most objects in Angular. Examples are directives,
|
||||
services, and filters. The factory methods are registered with the module, and the recommended way
|
||||
of declaring factories is:
|
||||
|
||||
```js
|
||||
angular.module('myModule', []).
|
||||
config(['depProvider', function(depProvider){
|
||||
...
|
||||
}]).
|
||||
factory('serviceId', ['depService', function(depService) {
|
||||
...
|
||||
}]).
|
||||
directive('directiveName', ['depService', function(depService) {
|
||||
...
|
||||
}]).
|
||||
filter('filterName', ['depService', function(depService) {
|
||||
...
|
||||
}]).
|
||||
run(['depService', function(depService) {
|
||||
...
|
||||
}]);
|
||||
```
|
||||
* {@link scope `$scope`}: Controllers are always associated with a point in the DOM and so are provided with
|
||||
access to the {@link scope scope} at that point. Other components, such as services only have access to the
|
||||
singleton {@link $rootScope} service.
|
||||
* {@link $route} resolves: If a controller is instantiated as part of a route, then any values that
|
||||
are resolved as part of the route are made available for injection into the controller.
|
||||
|
||||
@@ -18,7 +18,7 @@ how to implement them.
|
||||
## What are Directives?
|
||||
|
||||
At a high level, directives are markers on a DOM element (such as an attribute, element
|
||||
name, or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to
|
||||
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to
|
||||
attach a specified behavior to that DOM element or even transform the DOM element and its children.
|
||||
|
||||
Angular comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngView`.
|
||||
@@ -43,13 +43,13 @@ determines when to use a given directive.
|
||||
|
||||
In the following example, we say that the `<input>` element **matches** the `ngModel` directive.
|
||||
|
||||
```javascript
|
||||
```html
|
||||
<input ng-model="foo">
|
||||
```
|
||||
|
||||
The following also **matches** `ngModel`:
|
||||
|
||||
```javascript
|
||||
```html
|
||||
<input data-ng:model="foo">
|
||||
```
|
||||
|
||||
@@ -70,12 +70,12 @@ Here are some equivalent examples of elements that match `ngBind`:
|
||||
<example module="docsBindExample">
|
||||
<file name="script.js">
|
||||
angular.module('docsBindExample', [])
|
||||
.controller('Ctrl1', function Ctrl1($scope) {
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)';
|
||||
});
|
||||
}]);
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl1">
|
||||
<div ng-controller="Controller">
|
||||
Hello <input ng-model='name'> <hr/>
|
||||
<span ng-bind="name"></span> <br/>
|
||||
<span ng:bind="name"></span> <br/>
|
||||
@@ -86,7 +86,7 @@ Here are some equivalent examples of elements that match `ngBind`:
|
||||
</file>
|
||||
<file name="protractorTest.js">
|
||||
it('should show off bindings', function() {
|
||||
expect(element(by.css('div[ng-controller="Ctrl1"] span[ng-bind]')).getText())
|
||||
expect(element(by.css('div[ng-controller="Controller"] span[ng-bind]')).getText())
|
||||
.toBe('Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)');
|
||||
});
|
||||
</file>
|
||||
@@ -218,12 +218,12 @@ Let's create a directive that simply replaces its contents with a static templat
|
||||
<example module="docsSimpleDirective">
|
||||
<file name="script.js">
|
||||
angular.module('docsSimpleDirective', [])
|
||||
.controller('Ctrl', function($scope) {
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.customer = {
|
||||
name: 'Naomi',
|
||||
address: '1600 Amphitheatre'
|
||||
};
|
||||
})
|
||||
}])
|
||||
.directive('myCustomer', function() {
|
||||
return {
|
||||
template: 'Name: {{customer.name}} Address: {{customer.address}}'
|
||||
@@ -231,7 +231,7 @@ Let's create a directive that simply replaces its contents with a static templat
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div ng-controller="Controller">
|
||||
<div my-customer></div>
|
||||
</div>
|
||||
</file>
|
||||
@@ -257,12 +257,12 @@ using `templateUrl` instead:
|
||||
<example module="docsTemplateUrlDirective">
|
||||
<file name="script.js">
|
||||
angular.module('docsTemplateUrlDirective', [])
|
||||
.controller('Ctrl', function($scope) {
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.customer = {
|
||||
name: 'Naomi',
|
||||
address: '1600 Amphitheatre'
|
||||
};
|
||||
})
|
||||
}])
|
||||
.directive('myCustomer', function() {
|
||||
return {
|
||||
templateUrl: 'my-customer.html'
|
||||
@@ -270,7 +270,7 @@ using `templateUrl` instead:
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div ng-controller="Controller">
|
||||
<div my-customer></div>
|
||||
</div>
|
||||
</file>
|
||||
@@ -282,7 +282,7 @@ using `templateUrl` instead:
|
||||
Great! But what if we wanted to have our directive match the tag name `<my-customer>` instead?
|
||||
If we simply put a `<my-customer>` element into the HTML, it doesn't work.
|
||||
|
||||
<div class="alert alert-waring">
|
||||
<div class="alert alert-warning">
|
||||
**Note:** When you create a directive, it is restricted to attribute only by default. In order to
|
||||
create directives that are triggered by element or class name, you need to use the `restrict` option.
|
||||
</div>
|
||||
@@ -293,21 +293,21 @@ The `restrict` option is typically set to:
|
||||
* `'E'` - only matches element name
|
||||
* `'C'` - only matches class name
|
||||
|
||||
These restictions can all be combined as needed:
|
||||
These restrictions can all be combined as needed:
|
||||
|
||||
* `'AEC'` - matches either attribure or element or class name
|
||||
* `'AEC'` - matches either attribute or element or class name
|
||||
|
||||
Let's change our directive to use `restrict: 'E'`:
|
||||
|
||||
<example module="docsRestrictDirective">
|
||||
<file name="script.js">
|
||||
angular.module('docsRestrictDirective', [])
|
||||
.controller('Ctrl', function($scope) {
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.customer = {
|
||||
name: 'Naomi',
|
||||
address: '1600 Amphitheatre'
|
||||
};
|
||||
})
|
||||
}])
|
||||
.directive('myCustomer', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -317,7 +317,7 @@ Let's change our directive to use `restrict: 'E'`:
|
||||
</file>
|
||||
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div ng-controller="Controller">
|
||||
<my-customer></my-customer>
|
||||
</div>
|
||||
</file>
|
||||
@@ -358,18 +358,18 @@ re-use such a directive:
|
||||
<example module="docsScopeProblemExample">
|
||||
<file name="script.js">
|
||||
angular.module('docsScopeProblemExample', [])
|
||||
.controller('NaomiCtrl', function($scope) {
|
||||
.controller('NaomiController', ['$scope', function($scope) {
|
||||
$scope.customer = {
|
||||
name: 'Naomi',
|
||||
address: '1600 Amphitheatre'
|
||||
};
|
||||
})
|
||||
.controller('IgorCtrl', function($scope) {
|
||||
}])
|
||||
.controller('IgorController', ['$scope', function($scope) {
|
||||
$scope.customer = {
|
||||
name: 'Igor',
|
||||
address: '123 Somewhere'
|
||||
};
|
||||
})
|
||||
}])
|
||||
.directive('myCustomer', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -378,11 +378,11 @@ re-use such a directive:
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="NaomiCtrl">
|
||||
<div ng-controller="NaomiController">
|
||||
<my-customer></my-customer>
|
||||
</div>
|
||||
<hr>
|
||||
<div ng-controller="IgorCtrl">
|
||||
<div ng-controller="IgorController">
|
||||
<my-customer></my-customer>
|
||||
</div>
|
||||
</file>
|
||||
@@ -400,10 +400,10 @@ we call an **isolate scope**. To do this, we can use a directive's `scope` optio
|
||||
<example module="docsIsolateScopeDirective">
|
||||
<file name="script.js">
|
||||
angular.module('docsIsolateScopeDirective', [])
|
||||
.controller('Ctrl', function($scope) {
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
|
||||
$scope.igor = { name: 'Igor', address: '123 Somewhere' };
|
||||
})
|
||||
}])
|
||||
.directive('myCustomer', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -415,7 +415,7 @@ we call an **isolate scope**. To do this, we can use a directive's `scope` optio
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div ng-controller="Controller">
|
||||
<my-customer info="naomi"></my-customer>
|
||||
<hr>
|
||||
<my-customer info="igor"></my-customer>
|
||||
@@ -473,11 +473,11 @@ within our directive's template:
|
||||
<example module="docsIsolationExample">
|
||||
<file name="script.js">
|
||||
angular.module('docsIsolationExample', [])
|
||||
.controller('Ctrl', function($scope) {
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
|
||||
|
||||
$scope.vojta = { name: 'Vojta', address: '3456 Somewhere Else' };
|
||||
})
|
||||
}])
|
||||
.directive('myCustomer', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -489,7 +489,7 @@ within our directive's template:
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div ng-controller="Controller">
|
||||
<my-customer info="naomi"></my-customer>
|
||||
</div>
|
||||
</file>
|
||||
@@ -510,8 +510,8 @@ that you explicitly pass in.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** Normally, a scope prototypically inherits from its parent. An isolated scope does not.
|
||||
See the {@link guide/directive#creating-custom-directives_demo_isolating-the-scope-of-a-directive
|
||||
"Isolating the Scope of a Directive"} section for more information about isolate scopes.
|
||||
See the {@link api/ng/service/$compile#directive-definition-object
|
||||
"Directive Definition Object - scope"} section for more information about isolate scopes.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success">
|
||||
@@ -537,16 +537,16 @@ where:
|
||||
In our `link` function, we want to update the displayed time once a second, or whenever a user
|
||||
changes the time formatting string that our directive binds to. We will use the `$interval` service
|
||||
to call a handler on a regular basis. This is easier than using `$timeout` but also works better with
|
||||
end 2 end testing, where we want to ensure that all $timeouts have completed before completing the test.
|
||||
end-to-end testing, where we want to ensure that all $timeouts have completed before completing the test.
|
||||
We also want to remove the `$interval` if the directive is deleted so we don't introduce a memory leak.
|
||||
|
||||
<example module="docsTimeDirective">
|
||||
<file name="script.js">
|
||||
angular.module('docsTimeDirective', [])
|
||||
.controller('Ctrl2', function($scope) {
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.format = 'M/d/yy h:mm:ss a';
|
||||
})
|
||||
.directive('myCurrentTime', function($interval, dateFilter) {
|
||||
}])
|
||||
.directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) {
|
||||
|
||||
function link(scope, element, attrs) {
|
||||
var format,
|
||||
@@ -574,10 +574,10 @@ We also want to remove the `$interval` if the directive is deleted so we don't i
|
||||
return {
|
||||
link: link
|
||||
};
|
||||
});
|
||||
}]);
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl2">
|
||||
<div ng-controller="Controller">
|
||||
Date format: <input ng-model="format"> <hr/>
|
||||
Current time is: <span my-current-time="format"></span>
|
||||
</div>
|
||||
@@ -619,9 +619,9 @@ To do this, we need to use the `transclude` option.
|
||||
<example module="docsTransclusionDirective">
|
||||
<file name="script.js">
|
||||
angular.module('docsTransclusionDirective', [])
|
||||
.controller('Ctrl', function($scope) {
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.name = 'Tobias';
|
||||
})
|
||||
}])
|
||||
.directive('myDialog', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -631,7 +631,7 @@ To do this, we need to use the `transclude` option.
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div ng-controller="Controller">
|
||||
<my-dialog>Check out the contents, {{name}}!</my-dialog>
|
||||
</div>
|
||||
</file>
|
||||
@@ -650,9 +650,9 @@ that redefines `name` as `Jeff`. What do you think the `{{name}}` binding will r
|
||||
<example module="docsTransclusionExample">
|
||||
<file name="script.js">
|
||||
angular.module('docsTransclusionExample', [])
|
||||
.controller('Ctrl', function($scope) {
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.name = 'Tobias';
|
||||
})
|
||||
}])
|
||||
.directive('myDialog', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -666,7 +666,7 @@ that redefines `name` as `Jeff`. What do you think the `{{name}}` binding will r
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div ng-controller="Controller">
|
||||
<my-dialog>Check out the contents, {{name}}!</my-dialog>
|
||||
</div>
|
||||
</file>
|
||||
@@ -701,7 +701,7 @@ own behavior to it.
|
||||
<example module="docsIsoFnBindExample">
|
||||
<file name="script.js">
|
||||
angular.module('docsIsoFnBindExample', [])
|
||||
.controller('Ctrl', function($scope, $timeout) {
|
||||
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
|
||||
$scope.name = 'Tobias';
|
||||
$scope.hideDialog = function () {
|
||||
$scope.dialogIsHidden = true;
|
||||
@@ -709,7 +709,7 @@ own behavior to it.
|
||||
$scope.dialogIsHidden = false;
|
||||
}, 2000);
|
||||
};
|
||||
})
|
||||
}])
|
||||
.directive('myDialog', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -722,7 +722,7 @@ own behavior to it.
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div ng-controller="Controller">
|
||||
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
|
||||
Check out the contents, {{name}}!
|
||||
</my-dialog>
|
||||
@@ -737,7 +737,7 @@ own behavior to it.
|
||||
</example>
|
||||
|
||||
We want to run the function we pass by invoking it from the directive's scope, but have it run
|
||||
in the context of the scope where its registered.
|
||||
in the context of the scope where it's registered.
|
||||
|
||||
We saw earlier how to use `=attr` in the `scope` option, but in the above example, we're using
|
||||
`&attr` instead. The `&` binding allows a directive to trigger evaluation of an expression in
|
||||
@@ -747,7 +747,8 @@ callback functions to directive behaviors.
|
||||
|
||||
When the user clicks the `x` in the dialog, the directive's `close` function is called, thanks to
|
||||
`ng-click.` This call to `close` on the isolated scope actually evaluates the expression
|
||||
`hideDialog()` in the context of the original scope, thus running `Ctrl`'s `hideDialog` function.
|
||||
`hideDialog()` in the context of the original scope, thus running `Controller`'s `hideDialog`
|
||||
function.
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** use `&attr` in the `scope` option when you want your directive
|
||||
@@ -766,8 +767,8 @@ element?
|
||||
|
||||
<example module="dragModule">
|
||||
<file name="script.js">
|
||||
angular.module('dragModule', []).
|
||||
directive('myDraggable', function($document) {
|
||||
angular.module('dragModule', [])
|
||||
.directive('myDraggable', ['$document', function($document) {
|
||||
return function(scope, element, attr) {
|
||||
var startX = 0, startY = 0, x = 0, y = 0;
|
||||
|
||||
@@ -801,7 +802,7 @@ element?
|
||||
$document.unbind('mouseup', mouseup);
|
||||
}
|
||||
};
|
||||
});
|
||||
}]);
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<span my-draggable>Drag ME</span>
|
||||
@@ -900,6 +901,11 @@ So where does this `myTabs` controller come from? Directives can specify control
|
||||
the unsurprisingly named `controller` option. As you can see, the `myTabs` directive uses this
|
||||
option. Just like `ngController`, this option attaches a controller to the template of the directive.
|
||||
|
||||
If it is necessary to reference the controller or any functions bound to the controller's scope in
|
||||
the template, you can use the option `controllerAs` to specify the name of the controller as an alias.
|
||||
The directive needs to define a scope for this configuration to be used. This is particularly useful
|
||||
in the case when the directive is used as a component.
|
||||
|
||||
Looking back at `myPane`'s definition, notice the last argument in its `link` function: `tabsCtrl`.
|
||||
When a directive requires a controller, it receives that controller as the fourth argument of its
|
||||
`link` function. Taking advantage of this, `myPane` can call the `addPane` function of `myTabs`.
|
||||
|
||||
@@ -3,311 +3,83 @@
|
||||
@name E2E Testing
|
||||
@description
|
||||
|
||||
**Angular Scenario Runner is in maintenance mode - If you're starting a new Angular project,
|
||||
consider using [Protractor](https://github.com/angular/protractor).**
|
||||
|
||||
# E2E Testing
|
||||
|
||||
<div class="alert alert-danger">
|
||||
**Note:** In the past, end to end testing could be done with a deprecated tool called
|
||||
[Angular Scenario Runner](http://code.angularjs.org/1.2.16/docs/guide/e2e-testing). That tool
|
||||
is now in maintenance mode.
|
||||
</div>
|
||||
|
||||
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
|
||||
verify the correctness of new features, catch bugs and notice regressions.
|
||||
verify the correctness of new features, catch bugs and notice regressions. End to end tests
|
||||
are the first line of defense for catching bugs, but sometimes issues come up with integration
|
||||
between components which can't be captured in a unit test. End to end tests are made to find
|
||||
these problems.
|
||||
|
||||
To solve this problem, we have built an Angular Scenario Runner which simulates user interactions
|
||||
that will help you verify the health of your Angular application.
|
||||
We have built [Protractor](https://github.com/angular/protractor), an end
|
||||
to end test runner which simulates user interactions that will help you verify the health of your
|
||||
Angular application.
|
||||
|
||||
# Overview
|
||||
You write scenario tests in JavaScript. These tests describe how your application should behave
|
||||
given a certain interaction in a specific state. A scenario is comprised of one or more `it` blocks
|
||||
(you can think of these as the requirements of your application), which in turn are made of
|
||||
**commands** and **expectations**. Commands tell the Runner to do something with the application
|
||||
(such as navigate to a page or click on a button), and expectations tell the Runner to assert
|
||||
something about the state (such as the value of a field or the current URL). If any expectation
|
||||
fails, the runner marks the `it` as "failed" and continues on to the next one. Scenarios may also
|
||||
have **beforeEach** and **afterEach** blocks, which will be run before (or after) each `it` block,
|
||||
regardless of whether they pass or fail.
|
||||
## Using Protractor
|
||||
|
||||
Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests that are also
|
||||
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
|
||||
to control browsers and simulate user actions.
|
||||
|
||||
For more information on Protractor, view [getting started](https://github.com/angular/protractor/blob/master/docs/getting-started.md)
|
||||
or the [api docs](https://github.com/angular/protractor/blob/master/docs/api.md).
|
||||
|
||||
Protractor uses [Jasmine](http://jasmine.github.io/1.3/introduction.html) for its test syntax.
|
||||
As in unit testing, a test file is comprised of one or
|
||||
more `it` blocks that describe the requirements of your application. `it` blocks are made of
|
||||
**commands** and **expectations**. Commands tell Protractor to do something with the application
|
||||
such as navigate to a page or click on a button. Expectations tell Protractor to assert something
|
||||
about the application's state, such as the value of a field or the current URL.
|
||||
|
||||
If any expectation within an `it` block fails, the runner marks the `it` as "failed" and continues
|
||||
on to the next block.
|
||||
|
||||
Test files may also have `beforeEach` and `afterEach` blocks, which will be run before or after
|
||||
each `it` block regardless of whether the block passes or fails.
|
||||
|
||||
<img src="img/guide/scenario_runner.png">
|
||||
|
||||
In addition to the above elements, scenarios may also contain helper functions to avoid duplicating
|
||||
In addition to the above elements, tests may also contain helper functions to avoid duplicating
|
||||
code in the `it` blocks.
|
||||
|
||||
Here is an example of a simple scenario:
|
||||
Here is an example of a simple test:
|
||||
```js
|
||||
describe('Buzz Client', function() {
|
||||
it('should filter results', function() {
|
||||
input('user').enter('jacksparrow');
|
||||
element(':button').click();
|
||||
expect(repeater('ul li').count()).toEqual(10);
|
||||
input('filterText').enter('Bees');
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
});
|
||||
describe('TODO list', function() {
|
||||
it('should filter results', function() {
|
||||
|
||||
// Find the element with ng-model="user" and type "jacksparrow" into it
|
||||
element(by.model('user')).sendKeys('jacksparrow');
|
||||
|
||||
// Find the first (and only) button on the page and click it
|
||||
element(by.css(':button')).click();
|
||||
|
||||
// Verify that there are 10 tasks
|
||||
expect(element.all(by.repeater('task in tasks')).count()).toEqual(10);
|
||||
|
||||
// Enter 'groceries' into the element with ng-model="filterText"
|
||||
element(by.model('filterText')).sendKeys('groceries');
|
||||
|
||||
// Verify that now there is only one item in the task list
|
||||
expect(element.all(by.repeater('task in tasks')).count()).toEqual(1);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Note that
|
||||
[`input('user')`](https://github.com/angular/angular.js/blob/master/docs/content/guide/dev_guide.e2e-testing.ngdoc#L119)
|
||||
finds the `<input>` element with `ng-model="user"` not `name="user"`.
|
||||
This test describes the requirements of a ToDo list, specifically, that it should be able to
|
||||
filter the list of items.
|
||||
|
||||
This scenario describes the requirements of a Buzz Client, specifically, that it should be able to
|
||||
filter the stream of the user. It starts by entering a value in the input field with ng-model="user", clicking
|
||||
the only button on the page, and then it verifies that there are 10 items listed. It then enters
|
||||
'Bees' in the input field with ng-model='filterText' and verifies that the list is reduced to a single item.
|
||||
## Example
|
||||
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples, or look
|
||||
at the embedded examples in the Angular documentation (For example, [$http](http://docs.angularjs.org/api/ng/service/$http)
|
||||
has an end to end test in the example under the `protractor.js` tag).
|
||||
|
||||
The API section below lists the available commands and expectations for the Runner.
|
||||
## Caveats
|
||||
|
||||
# API
|
||||
Source: https://github.com/angular/angular.js/blob/master/src/ngScenario/dsl.js
|
||||
|
||||
## pause()
|
||||
Pauses the execution of the tests until you call `resume()` in the console (or click the resume
|
||||
link in the Runner UI).
|
||||
|
||||
## sleep(seconds)
|
||||
Pauses the execution of the tests for the specified number of `seconds`.
|
||||
|
||||
## browser().navigateTo(url)
|
||||
Loads the `url` into the test frame.
|
||||
|
||||
## browser().navigateTo(url, fn)
|
||||
Loads the URL returned by `fn` into the testing frame. The given `url` is only used for the test
|
||||
output. Use this when the destination URL is dynamic (that is, the destination is unknown when you
|
||||
write the test).
|
||||
|
||||
## browser().reload()
|
||||
Refreshes the currently loaded page in the test frame.
|
||||
|
||||
## browser().window().href()
|
||||
Returns the window.location.href of the currently loaded page in the test frame.
|
||||
|
||||
## browser().window().path()
|
||||
Returns the window.location.pathname of the currently loaded page in the test frame.
|
||||
|
||||
## browser().window().search()
|
||||
Returns the window.location.search of the currently loaded page in the test frame.
|
||||
|
||||
## browser().window().hash()
|
||||
Returns the window.location.hash (without `#`) of the currently loaded page in the test frame.
|
||||
|
||||
## browser().location().url()
|
||||
Returns the {@link ng.$location $location.url()} of the currently loaded page in
|
||||
the test frame.
|
||||
|
||||
## browser().location().path()
|
||||
Returns the {@link ng.$location $location.path()} of the currently loaded page in
|
||||
the test frame.
|
||||
|
||||
## browser().location().search()
|
||||
Returns the {@link ng.$location $location.search()} of the currently loaded page
|
||||
in the test frame.
|
||||
|
||||
## browser().location().hash()
|
||||
Returns the {@link ng.$location $location.hash()} of the currently loaded page in
|
||||
the test frame.
|
||||
|
||||
## expect(future).{matcher}
|
||||
Asserts the value of the given `future` satisfies the `matcher`. All API statements return a
|
||||
`future` object, which get a `value` assigned after they are executed. Matchers are defined using
|
||||
`angular.scenario.matcher`, and they use the value of futures to run the expectation. For example:
|
||||
`expect(browser().location().href()).toEqual('http://www.google.com')`. Available matchers
|
||||
are presented further down this document.
|
||||
|
||||
## expect(future).not().{matcher}
|
||||
Asserts the value of the given `future` satisfies the negation of the `matcher`.
|
||||
|
||||
## using(selector, label)
|
||||
Scopes the next DSL element selection.
|
||||
|
||||
## binding(name)
|
||||
Returns the value of the first binding matching the given `name`.
|
||||
|
||||
## input(name).enter(value)
|
||||
Enters the given `value` in the text field with the corresponding ng-model `name`.
|
||||
|
||||
## input(name).check()
|
||||
Checks/unchecks the checkbox with the corresponding ng-model `name`.
|
||||
|
||||
## input(name).select(value)
|
||||
Selects the given `value` in the radio button with the corresponding ng-model `name`.
|
||||
|
||||
## input(name).val()
|
||||
Returns the current value of an input field with the corresponding ng-model `name`.
|
||||
|
||||
## repeater(selector, label).count()
|
||||
Returns the number of rows in the repeater matching the given jQuery `selector`. The `label` is
|
||||
used for test output.
|
||||
|
||||
## repeater(selector, label).row(index)
|
||||
Returns an array with the bindings in the row at the given `index` in the repeater matching the
|
||||
given jQuery `selector`. The `label` is used for test output.
|
||||
|
||||
## repeater(selector, label).column(binding)
|
||||
Returns an array with the values in the column with the given `binding` in the repeater matching
|
||||
the given jQuery `selector`. The `label` is used for test output.
|
||||
|
||||
## select(name).option(value)
|
||||
Picks the option with the given `value` on the select with the given ng-model `name`.
|
||||
|
||||
## select(name).options(value1, value2...)
|
||||
Picks the options with the given `values` on the multi select with the given ng-model `name`.
|
||||
|
||||
## element(selector, label).count()
|
||||
Returns the number of elements that match the given jQuery `selector`. The `label` is used for test
|
||||
output.
|
||||
|
||||
## element(selector, label).click()
|
||||
Clicks on the element matching the given jQuery `selector`. The `label` is used for test output.
|
||||
|
||||
## element(selector, label).query(fn)
|
||||
Executes the function `fn(selectedElements, done)`, where selectedElements are the elements that
|
||||
match the given jQuery `selector` and `done` is a function that is called at the end of the `fn`
|
||||
function. The `label` is used for test output.
|
||||
|
||||
## element(selector, label).{method}()
|
||||
Returns the result of calling `method` on the element matching the given jQuery `selector`, where
|
||||
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
|
||||
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
|
||||
`scrollTop`, `offset`. The `label` is used for test output.
|
||||
|
||||
## element(selector, label).{method}(value)
|
||||
Executes the `method` passing in `value` on the element matching the given jQuery `selector`, where
|
||||
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
|
||||
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
|
||||
`scrollTop`, `offset`. The `label` is used for test output.
|
||||
|
||||
## element(selector, label).{method}(key)
|
||||
Returns the result of calling `method` passing in `key` on the element matching the given jQuery
|
||||
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
|
||||
`label` is used for test output.
|
||||
|
||||
## element(selector, label).{method}(key, value)
|
||||
Executes the `method` passing in `key` and `value` on the element matching the given jQuery
|
||||
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
|
||||
`label` is used for test output.
|
||||
|
||||
# Matchers
|
||||
|
||||
Matchers are used in combination with the `expect(...)` function as described above and can
|
||||
be negated with `not()`. For instance: `expect(element('h1').text()).not().toEqual('Error')`.
|
||||
|
||||
Source: https://github.com/angular/angular.js/blob/master/src/ngScenario/matchers.js
|
||||
|
||||
```js
|
||||
// value and Object comparison following the rules of angular.equals().
|
||||
expect(value).toEqual(value)
|
||||
|
||||
// a simpler value comparison using ===
|
||||
expect(value).toBe(value)
|
||||
|
||||
// checks that the value is defined by checking its type.
|
||||
expect(value).toBeDefined()
|
||||
|
||||
// the following two matchers are using JavaScript's standard truthiness rules
|
||||
expect(value).toBeTruthy()
|
||||
expect(value).toBeFalsy()
|
||||
|
||||
// verify that the value matches the given regular expression. The regular
|
||||
// expression may be passed in form of a string or a regular expression
|
||||
// object.
|
||||
expect(value).toMatch(expectedRegExp)
|
||||
|
||||
// a check for null using ===
|
||||
expect(value).toBeNull()
|
||||
|
||||
// Array.indexOf(...) is used internally to check whether the element is
|
||||
// contained within the array.
|
||||
expect(value).toContain(expected)
|
||||
|
||||
// number comparison using < and >
|
||||
expect(value).toBeLessThan(expected)
|
||||
expect(value).toBeGreaterThan(expected)
|
||||
```
|
||||
|
||||
# Example
|
||||
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples.
|
||||
|
||||
## Conditional actions with element(...).query(fn)
|
||||
|
||||
E2E testing with angular scenario is highly asynchronous and hides a lot of complexity by
|
||||
queueing actions and expectations that can handle futures. From time to time, you might need
|
||||
conditional assertions or element selection. Even though you should generally try to avoid this
|
||||
(as it is can be sign for unstable tests), you can add conditional behavior with
|
||||
`element(...).query(fn)`. The following code listing shows how this function can be used to delete
|
||||
added entries (where an entry is some domain object) using the application's web interface.
|
||||
|
||||
Imagine the application to be structured into two views:
|
||||
|
||||
1. *Overview view* which lists all the added entries in a table and
|
||||
2. a *detail view* which shows the entries' details and contains a delete button. When clicking the
|
||||
delete button, the user is redirected back to the *overview page*.
|
||||
|
||||
```js
|
||||
beforeEach(function () {
|
||||
var deleteEntry = function () {
|
||||
browser().navigateTo('/entries');
|
||||
|
||||
// we need to select the <tbody> element as it might be the case that there
|
||||
// are no entries (and therefore no rows). When the selector does not
|
||||
// result in a match, the test would be marked as a failure.
|
||||
element('table tbody').query(function (tbody, done) {
|
||||
// ngScenario gives us a jQuery lite wrapped element. We call the
|
||||
// `children()` function to retrieve the table body's rows
|
||||
var children = tbody.children();
|
||||
|
||||
if (children.length > 0) {
|
||||
// if there is at least one entry in the table, click on the link to
|
||||
// the entry's detail view
|
||||
element('table tbody a').click();
|
||||
// and, after a route change, click the delete button
|
||||
element('.btn-danger').click();
|
||||
}
|
||||
|
||||
// if there is more than one entry shown in the table, queue another
|
||||
// delete action.
|
||||
if (children.length > 1) {
|
||||
deleteEntry();
|
||||
}
|
||||
|
||||
// remember to call `done()` so that ngScenario can continue
|
||||
// test execution.
|
||||
done();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// start deleting entries
|
||||
deleteEntry();
|
||||
});
|
||||
```
|
||||
|
||||
In order to understand what is happening, we should emphasize that ngScenario calls are not
|
||||
immediately executed, but queued (in ngScenario terms, we would be talking about adding
|
||||
future actions). If we had only one entry in our table, then the following future actions
|
||||
would be queued:
|
||||
|
||||
```js
|
||||
// delete entry 1
|
||||
browser().navigateTo('/entries');
|
||||
element('table tbody').query(function (tbody, done) { ... });
|
||||
element('table tbody a');
|
||||
element('.btn-danger').click();
|
||||
```
|
||||
|
||||
For two entries, ngScenario would have to work on the following queue:
|
||||
|
||||
```js
|
||||
// delete entry 1
|
||||
browser().navigateTo('/entries');
|
||||
element('table tbody').query(function (tbody, done) { ... });
|
||||
element('table tbody a');
|
||||
element('.btn-danger').click();
|
||||
|
||||
// delete entry 2
|
||||
// indented to represent "recursion depth"
|
||||
browser().navigateTo('/entries');
|
||||
element('table tbody').query(function (tbody, done) { ... });
|
||||
element('table tbody a');
|
||||
element('.btn-danger').click();
|
||||
```
|
||||
|
||||
# Caveats
|
||||
|
||||
`ngScenario` does not work with apps that manually bootstrap using `angular.bootstrap`. You must use the `ng-app` directive.
|
||||
Protractor does not work out-of-the-box with apps that bootstrap manually using
|
||||
`angular.bootstrap`. You must use the `ng-app` directive.
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
@name Expressions
|
||||
@description
|
||||
|
||||
Expressions are JavaScript-like code snippets that are usually placed in bindings such as
|
||||
# Angular Expressions
|
||||
|
||||
Angular expressions are JavaScript-like code snippets that are usually placed in bindings such as
|
||||
`{{ expression }}`.
|
||||
|
||||
For example, these are valid expressions in Angular:
|
||||
@@ -48,9 +50,9 @@ the method from your view. If you want to `eval()` an Angular expression yoursel
|
||||
|
||||
You can try evaluating different expressions here:
|
||||
|
||||
<example>
|
||||
<example module="expressionExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="Cntl2" class="expressions">
|
||||
<div ng-controller="ExampleController" class="expressions">
|
||||
Expression:
|
||||
<input type='text' ng-model="expr" size="80"/>
|
||||
<button ng-click="addExp(expr)">Evaluate</button>
|
||||
@@ -64,23 +66,24 @@ You can try evaluating different expressions here:
|
||||
</file>
|
||||
|
||||
<file name="script.js">
|
||||
function Cntl2($scope) {
|
||||
var exprs = $scope.exprs = [];
|
||||
$scope.expr = '3*10|currency';
|
||||
$scope.addExp = function(expr) {
|
||||
exprs.push(expr);
|
||||
};
|
||||
angular.module('expressionExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
var exprs = $scope.exprs = [];
|
||||
$scope.expr = '3*10|currency';
|
||||
$scope.addExp = function(expr) {
|
||||
exprs.push(expr);
|
||||
};
|
||||
|
||||
$scope.removeExp = function(index) {
|
||||
exprs.splice(index, 1);
|
||||
};
|
||||
}
|
||||
$scope.removeExp = function(index) {
|
||||
exprs.splice(index, 1);
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should allow user expression testing', function() {
|
||||
element(by.css('.expressions button')).click();
|
||||
var lis = element(by.css('.expressions ul')).element.all(by.repeater('expr in exprs'));
|
||||
var lis = element(by.css('.expressions ul')).all(by.repeater('expr in exprs'));
|
||||
expect(lis.count()).toBe(1);
|
||||
expect(lis.get(0).getText()).toEqual('[ X ] 3*10|currency => $30.00');
|
||||
});
|
||||
@@ -88,32 +91,35 @@ You can try evaluating different expressions here:
|
||||
</example>
|
||||
|
||||
|
||||
# Context
|
||||
## Context
|
||||
|
||||
Angular does not use JavaScript's `eval()` to evaluate expressions. Instead Angular's
|
||||
{@link ng.$parse $parse} service processes these expressions.
|
||||
|
||||
Unlike JavaScript, where names default to global `window` properties, Angular expressions must use
|
||||
{@link ng.$window `$window`} explicitly to refer to the global `window` object. For example, if you
|
||||
want to call `alert()` in an expression you must use `$window.alert()`. This restriction is
|
||||
intentional. It prevents accidental access to the global state – a common source of subtle bugs.
|
||||
Angular expressions do not have access to global variables like `window`, `document` or `location`.
|
||||
This restriction is intentional. It prevents accidental access to the global state – a common source of subtle bugs.
|
||||
|
||||
<example>
|
||||
Instead use services like `$window` and `$location` in functions called from expressions. Such services
|
||||
provide mockable access to globals.
|
||||
|
||||
<example module="expressionExample">
|
||||
<file name="index.html">
|
||||
<div class="example2" ng-controller="Cntl1">
|
||||
<div class="example2" ng-controller="ExampleController">
|
||||
Name: <input ng-model="name" type="text"/>
|
||||
<button ng-click="greet()">Greet</button>
|
||||
<button ng-click="window.alert('Should not see me')">Won't greet</button>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="script.js">
|
||||
function Cntl1($window, $scope){
|
||||
$scope.name = 'World';
|
||||
angular.module('expressionExample', [])
|
||||
.controller('ExampleController', ['$window', '$scope', function($window, $scope) {
|
||||
$scope.name = 'World';
|
||||
|
||||
$scope.greet = function() {
|
||||
$window.alert('Hello ' + $scope.name);
|
||||
};
|
||||
}
|
||||
$scope.greet = function() {
|
||||
$window.alert('Hello ' + $scope.name);
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
@@ -150,7 +156,49 @@ Similarly, invoking a function `a.b.c()` on `undefined` or `null` simply returns
|
||||
|
||||
## No Control Flow Statements
|
||||
|
||||
You cannot write a control flow statement in an expression. The reason behind this is core to the
|
||||
Angular philosophy that application logic should be in controllers, not the views. If you need a
|
||||
conditional, loop, or to throw from a view expression, delegate to a JavaScript method instead.
|
||||
Apart from the ternary operator (`a ? b : c`), you cannot write a control flow statement in an
|
||||
expression. The reason behind this is core to the Angular philosophy that application logic should
|
||||
be in controllers, not the views. If you need a real conditional, loop, or to throw from a view
|
||||
expression, delegate to a JavaScript method instead.
|
||||
|
||||
## `$event`
|
||||
|
||||
Directives like {@link ng.directive:ngClick `ngClick`} and {@link ng.directive:ngFocus `ngFocus`}
|
||||
expose a `$event` object within the scope of that expression.
|
||||
|
||||
<example module="eventExampleApp">
|
||||
<file name="index.html">
|
||||
<div ng-controller="EventController">
|
||||
<button ng-click="clickMe($event)">Event</button>
|
||||
<p><code>$event</code>: <pre> {{$event | json}}</pre></p>
|
||||
<p><code>clickEvent</code>: <pre>{{clickEvent | json}}</pre></p>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="script.js">
|
||||
angular.module('eventExampleApp', []).
|
||||
controller('EventController', ['$scope', function($scope) {
|
||||
/*
|
||||
* expose the event object to the scope
|
||||
*/
|
||||
$scope.clickMe = function(clickEvent) {
|
||||
$scope.clickEvent = simpleKeys(clickEvent);
|
||||
console.log(clickEvent);
|
||||
};
|
||||
|
||||
/*
|
||||
* return a copy of an object with only non-object keys
|
||||
* we need this to avoid circular references
|
||||
*/
|
||||
function simpleKeys (original) {
|
||||
return Object.keys(original).reduce(function (obj, key) {
|
||||
obj[key] = typeof original[key] === 'object' ? '{ ... }' : original[key];
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
|
||||
Note in the example above how we can pass in `$event` to `clickMe`, but how it does not show up
|
||||
in `{{$event}}`. This is because `$event` is outside the scope of that binding.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
A filter formats the value of an expression for display to the user. They can be used in view templates,
|
||||
controllers or services and it is easy to define your own filter.
|
||||
|
||||
The underlying API is the {@link ng.$filterProvider filterProvider}.
|
||||
The underlying API is the {@link ng.$filterProvider `filterProvider`}.
|
||||
|
||||
## Using filters in view templates
|
||||
|
||||
@@ -29,12 +29,12 @@ E.g. the markup `{{ 1234 | number:2 }}` formats the number 1234 with 2 decimal p
|
||||
{@link ng.filter:number `number`} filter. The resulting value is `1,234.00`.
|
||||
|
||||
|
||||
## Using filters in controllers and services
|
||||
## Using filters in controllers, services, and directives
|
||||
|
||||
You can also use filters in controllers and services. For this, add a dependency with the name `<filterName>Filter`
|
||||
to your controller or service. E.g. using the dependency `numberFilter` will inject the number filter.
|
||||
The injected argument is a function that takes the value to format as first argument and filter parameters
|
||||
starting with the second argument.
|
||||
You can also use filters in controllers, services, and directives. For this, inject a dependency
|
||||
with the name `<filterName>Filter` to your controller/service/directive. E.g. using the dependency
|
||||
`numberFilter` will inject the number filter. The injected argument is a function that takes the
|
||||
value to format as first argument and filter parameters starting with the second argument.
|
||||
|
||||
The example below uses the filter called {@link ng.filter:filter `filter`}.
|
||||
This filter reduces arrays into sub arrays based on
|
||||
@@ -89,9 +89,9 @@ function.
|
||||
The following sample filter reverses a text string. In addition, it conditionally makes the
|
||||
text upper-case.
|
||||
|
||||
<example module="MyReverseModule">
|
||||
<example module="myReverseModule">
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div ng-controller="Controller">
|
||||
<input ng-model="greeting" type="text"><br>
|
||||
No filter: {{greeting}}<br>
|
||||
Reverse: {{greeting|reverse}}<br>
|
||||
@@ -100,9 +100,10 @@ text upper-case.
|
||||
</file>
|
||||
|
||||
<file name="script.js">
|
||||
angular.module('MyReverseModule', []).
|
||||
filter('reverse', function() {
|
||||
angular.module('myReverseModule', [])
|
||||
.filter('reverse', function() {
|
||||
return function(input, uppercase) {
|
||||
input = input || '';
|
||||
var out = "";
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
out = input.charAt(i) + out;
|
||||
@@ -113,11 +114,10 @@ text upper-case.
|
||||
}
|
||||
return out;
|
||||
};
|
||||
});
|
||||
|
||||
function Ctrl($scope) {
|
||||
$scope.greeting = 'hello';
|
||||
}
|
||||
})
|
||||
.controller('Controller', ['$scope', function($scope) {
|
||||
$scope.greeting = 'hello';
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ The key directive in understanding two-way data-binding is {@link ng.directive:n
|
||||
The `ngModel` directive provides the two-way data-binding by synchronizing the model to the view, as well as view to the model.
|
||||
In addition it provides an {@link ngModel.NgModelController API} for other directives to augment its behavior.
|
||||
|
||||
<example>
|
||||
<example module="formExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="Controller">
|
||||
<div ng-controller="ExampleController">
|
||||
<form novalidate class="simple-form">
|
||||
Name: <input type="text" ng-model="user.name" /><br />
|
||||
E-mail: <input type="email" ng-model="user.email" /><br />
|
||||
@@ -32,19 +32,20 @@ In addition it provides an {@link ngModel.NgModelController API} for other direc
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function Controller($scope) {
|
||||
$scope.master = {};
|
||||
angular.module('formExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.master = {};
|
||||
|
||||
$scope.update = function(user) {
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
$scope.update = function(user) {
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.user = angular.copy($scope.master);
|
||||
};
|
||||
$scope.reset = function() {
|
||||
$scope.user = angular.copy($scope.master);
|
||||
};
|
||||
|
||||
$scope.reset();
|
||||
}
|
||||
$scope.reset();
|
||||
}]);
|
||||
</script>
|
||||
</file>
|
||||
</example>
|
||||
@@ -56,7 +57,7 @@ Note that `novalidate` is used to disable browser's native form validation.
|
||||
|
||||
# Using CSS classes
|
||||
|
||||
To allow styling of form as well as controls, `ngModel` add these CSS classes:
|
||||
To allow styling of form as well as controls, `ngModel` adds these CSS classes:
|
||||
|
||||
- `ng-valid`
|
||||
- `ng-invalid`
|
||||
@@ -67,9 +68,9 @@ The following example uses the CSS to display validity of each form control.
|
||||
In the example both `user.name` and `user.email` are required, but are rendered with red background only when they are dirty.
|
||||
This ensures that the user is not distracted with an error until after interacting with the control, and failing to satisfy its validity.
|
||||
|
||||
<example>
|
||||
<example module="formExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="Controller">
|
||||
<div ng-controller="ExampleController">
|
||||
<form novalidate class="css-form">
|
||||
Name:
|
||||
<input type="text" ng-model="user.name" required /><br />
|
||||
@@ -92,19 +93,20 @@ This ensures that the user is not distracted with an error until after interacti
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function Controller($scope) {
|
||||
$scope.master = {};
|
||||
angular.module('formExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.master = {};
|
||||
|
||||
$scope.update = function(user) {
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
$scope.update = function(user) {
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.user = angular.copy($scope.master);
|
||||
};
|
||||
$scope.reset = function() {
|
||||
$scope.user = angular.copy($scope.master);
|
||||
};
|
||||
|
||||
$scope.reset();
|
||||
}
|
||||
$scope.reset();
|
||||
}]);
|
||||
</script>
|
||||
</file>
|
||||
</example>
|
||||
@@ -130,7 +132,7 @@ This allows us to extend the above example with these features:
|
||||
- SAVE button is enabled only if form has some changes and is valid
|
||||
- custom error messages for `user.email` and `user.agree`
|
||||
|
||||
<example>
|
||||
<example module="formExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="Controller">
|
||||
<form name="form" class="css-form" novalidate>
|
||||
@@ -159,23 +161,24 @@ This allows us to extend the above example with these features:
|
||||
</file>
|
||||
|
||||
<file name="script.js">
|
||||
function Controller($scope) {
|
||||
$scope.master = {};
|
||||
angular.module('formExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.master = {};
|
||||
|
||||
$scope.update = function(user) {
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
$scope.update = function(user) {
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.user = angular.copy($scope.master);
|
||||
};
|
||||
$scope.reset = function() {
|
||||
$scope.user = angular.copy($scope.master);
|
||||
};
|
||||
|
||||
$scope.isUnchanged = function(user) {
|
||||
return angular.equals(user, $scope.master);
|
||||
};
|
||||
$scope.isUnchanged = function(user) {
|
||||
return angular.equals(user, $scope.master);
|
||||
};
|
||||
|
||||
$scope.reset();
|
||||
}
|
||||
$scope.reset();
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
|
||||
@@ -211,26 +214,24 @@ In the following example we create two directives.
|
||||
|
||||
<example module="form-example1">
|
||||
<file name="index.html">
|
||||
<div ng-controller="Controller">
|
||||
<form name="form" class="css-form" novalidate>
|
||||
<div>
|
||||
Size (integer 0 - 10):
|
||||
<input type="number" ng-model="size" name="size"
|
||||
min="0" max="10" integer />{{size}}<br />
|
||||
<span ng-show="form.size.$error.integer">This is not valid integer!</span>
|
||||
<span ng-show="form.size.$error.min || form.size.$error.max">
|
||||
The value must be in range 0 to 10!</span>
|
||||
</div>
|
||||
<form name="form" class="css-form" novalidate>
|
||||
<div>
|
||||
Size (integer 0 - 10):
|
||||
<input type="number" ng-model="size" name="size"
|
||||
min="0" max="10" integer />{{size}}<br />
|
||||
<span ng-show="form.size.$error.integer">This is not valid integer!</span>
|
||||
<span ng-show="form.size.$error.min || form.size.$error.max">
|
||||
The value must be in range 0 to 10!</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Length (float):
|
||||
<input type="text" ng-model="length" name="length" smart-float />
|
||||
{{length}}<br />
|
||||
<span ng-show="form.length.$error.float">
|
||||
This is not a valid float number!</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
Length (float):
|
||||
<input type="text" ng-model="length" name="length" smart-float />
|
||||
{{length}}<br />
|
||||
<span ng-show="form.length.$error.float">
|
||||
This is not a valid float number!</span>
|
||||
</div>
|
||||
</form>
|
||||
</file>
|
||||
|
||||
<file name="script.js">
|
||||
|
||||
@@ -2,53 +2,54 @@
|
||||
@name i18n and l10n
|
||||
@description
|
||||
|
||||
# I18n and L10n in AngularJS
|
||||
# i18n and l10n
|
||||
|
||||
**What is i18n and l10n?**
|
||||
Internationalization (i18n) is the process of developing products in such a way that they can be
|
||||
localized for languages and cultures easily. Localization (l10n), is the process of adapting
|
||||
applications and text to enable their usability in a particular cultural or linguistic market. For
|
||||
application developers, internationalizing an application means abstracting all of the strings and
|
||||
other locale-specific bits (such as date or currency formats) out of the application. Localizing an
|
||||
application means providing translations and localized formats for the abstracted bits.
|
||||
|
||||
Internationalization, abbreviated i18n, is the process of developing products in such a way that
|
||||
they can be localized for languages and cultures easily. Localization, abbreviated l10n, is the
|
||||
process of adapting applications and text to enable their usability in a particular cultural or
|
||||
linguistic market. For application developers, internationalizing an application means abstracting
|
||||
all of the strings and other locale-specific bits (such as date or currency formats) out of the
|
||||
application. Localizing an application means providing translations and localized formats for the
|
||||
abstracted bits.
|
||||
|
||||
**What level of support for i18n/l10n is currently in Angular?**
|
||||
## How does Angular support i18n/l10n?
|
||||
|
||||
Currently, Angular supports i18n/l10n for
|
||||
[datetime](http://docs.angularjs.org/#!/api/ng.filter:date),
|
||||
[number](http://docs.angularjs.org/#!/api/ng.filter:number) and
|
||||
[currency](http://docs.angularjs.org/#!/api/ng.filter:currency) filters.
|
||||
Angular supports i18n/l10n for {@link ng.filter:date date}, {@link ng.filter:number number} and
|
||||
{@link ng.filter:currency currency} filters.
|
||||
|
||||
Additionally, Angular supports localizable pluralization support provided by the {@link
|
||||
ng.directive:ngPluralize ngPluralize directive}.
|
||||
Additionally, Angular supports localizable pluralization support through the {@link
|
||||
ng.directive:ngPluralize `ngPluralize` directive}.
|
||||
|
||||
All localizable Angular components depend on locale-specific rule sets managed by the {@link
|
||||
ng.$locale $locale service}.
|
||||
ng.$locale `$locale` service}.
|
||||
|
||||
For readers who want to jump straight into examples, we have a few web pages that showcase how to
|
||||
use Angular filters with various locale rule sets. You can find these examples either on
|
||||
[Github](https://github.com/angular/angular.js/tree/master/i18n/e2e) or in the i18n/e2e folder of
|
||||
Angular development package.
|
||||
There a few examples that showcase how to use Angular filters with various locale rule sets in the
|
||||
[`i18n/e2e` directory](https://github.com/angular/angular.js/tree/master/i18n/e2e) of the Angular
|
||||
source code.
|
||||
|
||||
**What is a locale id?**
|
||||
|
||||
## What is a locale ID?
|
||||
|
||||
A locale is a specific geographical, political, or cultural region. The most commonly used locale
|
||||
ID consists of two parts: language code and country code. For example, en-US, en-AU, zh-CN are all
|
||||
valid locale IDs that have both language codes and country codes. Because specifying a country code
|
||||
in locale ID is optional, locale IDs such as en, zh, and sk are also valid. See the
|
||||
[ICU ](http://userguide.icu-project.org/locale) website for more information about using locale IDs.
|
||||
ID consists of two parts: language code and country code. For example, `en-US`, `en-AU`, and
|
||||
`zh-CN` are all valid locale IDs that have both language codes and country codes. Because
|
||||
specifying a country code in locale ID is optional, locale IDs such as `en`, `zh`, and `sk` are
|
||||
also valid. See the [ICU](http://userguide.icu-project.org/locale) website for more information
|
||||
about using locale IDs.
|
||||
|
||||
|
||||
## Supported locales in Angular
|
||||
|
||||
**Supported locales in Angular**
|
||||
Angular separates number and datetime format rule sets into different files, each file for a
|
||||
particular locale. You can find a list of currently supported locales
|
||||
[here](https://github.com/angular/angular.js/tree/master/src/ngLocale)
|
||||
# Providing locale rules to Angular
|
||||
|
||||
|
||||
## Providing locale rules to Angular
|
||||
|
||||
There are two approaches to providing locale rules to Angular:
|
||||
|
||||
**1. Pre-bundled rule sets**
|
||||
### 1. Pre-bundled rule sets
|
||||
|
||||
You can pre-bundle the desired locale file with Angular by concatenating the content of the
|
||||
locale-specific file to the end of `angular.js` or `angular.min.js` file.
|
||||
@@ -61,7 +62,7 @@ locale, you can do the following:
|
||||
When the application containing `angular_de-de.js` script instead of the generic angular.js script
|
||||
starts, Angular is automatically pre-configured with localization rules for the german locale.
|
||||
|
||||
**2. Including locale js script in index.html page**
|
||||
### 2. Including a locale script in `index.html`
|
||||
|
||||
You can also include the locale specific js file in the index.html page. For example, if one client
|
||||
requires German locale, you would serve index_de-de.html which will look something like this:
|
||||
@@ -77,48 +78,61 @@ requires German locale, you would serve index_de-de.html which will look somethi
|
||||
</html>
|
||||
```
|
||||
|
||||
**Comparison of the two approaches**
|
||||
Both approaches described above requires you to prepare different index.html pages or js files for
|
||||
each locale that your app may be localized into. You also need to configure your server to serve
|
||||
### Comparison of the two approaches
|
||||
|
||||
Both approaches described above require you to prepare different `index.html` pages or JavaScript
|
||||
files for each locale that your app may use. You also need to configure your server to serve
|
||||
the correct file that correspond to the desired locale.
|
||||
|
||||
However, the second approach (Including locale js script in index.html page) is likely to be slower
|
||||
because an extra script needs to be loaded.
|
||||
The second approach (including the locale JavaScript file in `index.html`) may be slower because
|
||||
an extra script needs to be loaded.
|
||||
|
||||
|
||||
# "Gotchas"
|
||||
## Caveats
|
||||
|
||||
**Currency symbol "gotcha"**
|
||||
Although Angular makes i18n convenient, there are several things you need to be conscious of as you
|
||||
develop your app.
|
||||
|
||||
Angular's [currency filter](http://docs.angularjs.org/#!/api/ng.filter:currency) allows
|
||||
you to use the default currency symbol from the {@link ng.$locale locale service},
|
||||
or you can provide the filter with a custom currency symbol. If your app will be used only in one
|
||||
locale, it is fine to rely on the default currency symbol. However, if you anticipate that viewers
|
||||
in other locales might use your app, you should provide your own currency symbol to make sure the
|
||||
actual value is understood.
|
||||
### Currency symbol
|
||||
|
||||
For example, if you want to display an account balance of 1000 dollars with the following binding
|
||||
containing currency filter: `{{ 1000 | currency }}`, and your app is currently in en-US locale.
|
||||
'$1000.00' will be shown. However, if someone in a different local (say, Japan) views your app, their
|
||||
browser will specify the locale as ja, and the balance of '¥1000.00' will be shown instead. This
|
||||
will really upset your client.
|
||||
Angular's {@link ng.filter:currency currency filter} allows you to use the default currency symbol
|
||||
from the {@link ng.$locale locale service}, or you can provide the filter with a custom currency
|
||||
symbol.
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** If your app will be used only in one locale, it is fine to rely on the default
|
||||
currency symbol. If you anticipate that viewers in other locales might use your app, you should
|
||||
explicitly provide a currency symbol.
|
||||
</div>
|
||||
|
||||
Let's say you are writing a banking app and you want to display an account balance of 1000 dollars.
|
||||
You write the following binding using the currency filter:
|
||||
|
||||
```html
|
||||
{{ 1000 | currency }}
|
||||
```
|
||||
|
||||
If your app is currently in the `en-US` locale, the browser will show `$1000.00`. If someone in the
|
||||
Japanese locale (`ja`) views your app, their browser will show a balance of `¥1000.00` instead.
|
||||
This is problematic because $1000 is not the same as ¥1000.
|
||||
|
||||
In this case, you need to override the default currency symbol by providing the
|
||||
[currency filter](http://docs.angularjs.org/#!/api/ng.filter:currency) with a currency symbol as
|
||||
a parameter when you configure the filter, for example, {{ 1000 | currency:"USD$"}}. This way,
|
||||
Angular will always show a balance of 'USD$1000' and disregard any locale changes.
|
||||
{@link ng.filter:currency} currency filter with a currency symbol as a parameter.
|
||||
|
||||
**Translation length "gotcha"**
|
||||
If we change the above to `{{ 1000 | currency:"USD$"}}`, Angular will always show a balance of
|
||||
`USD$1000` regardless of locale.
|
||||
|
||||
Keep in mind that translated strings/datetime formats can vary greatly in length. For example,
|
||||
`June 3, 1977` will be translated to Spanish as `3 de junio de 1977`. There are bound to be other
|
||||
more extreme cases. Hence, when internationalizing your apps, you need to apply CSS rules
|
||||
accordingly and do thorough testing to make sure UI components do not overlap.
|
||||
### Translation length
|
||||
|
||||
Translated strings/datetime formats can vary greatly in length. For example, `June 3, 1977` will be
|
||||
translated to Spanish as `3 de junio de 1977`.
|
||||
|
||||
**Timezones**
|
||||
When internationalizing your app, you need to do thorough testing to make sure UI components behave
|
||||
as expected even when their contents vary greatly in content size.
|
||||
|
||||
Keep in mind that Angular datetime filter uses the time zone settings of the browser. So the same
|
||||
### Timezones
|
||||
|
||||
The Angular datetime filter uses the time zone settings of the browser. The same
|
||||
application will show different time information depending on the time zone settings of the
|
||||
computer that the application is running on. Neither Javascript nor Angular currently supports
|
||||
computer that the application is running on. Neither JavaScript nor Angular currently supports
|
||||
displaying the date with a timezone specified by the developer.
|
||||
|
||||
+24
-19
@@ -1,32 +1,37 @@
|
||||
@ngdoc overview
|
||||
@name Internet Explorer Compatibility
|
||||
@name Internet Explorer Compatibility
|
||||
@description
|
||||
|
||||
# Overview
|
||||
# Internet Explorer Compatibility
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** AngularJS 1.3 is dropping support for IE8. Read more about it on
|
||||
[our blog](http://blog.angularjs.org/2013/12/angularjs-13-new-release-approaches.html).
|
||||
AngularJS 1.2 will continue to support IE8, but the core team does not plan to spend time
|
||||
addressing issues specific to IE8 or earlier.
|
||||
</div>
|
||||
|
||||
This document describes the Internet Explorer (IE) idiosyncrasies when dealing with custom HTML
|
||||
attributes and tags. Read this document if you are planning on deploying your Angular application
|
||||
on IE v8.0 or earlier.
|
||||
on IE8 or earlier.
|
||||
|
||||
The project currently supports and will attempt to fix bugs for IE8 and above. The continuous
|
||||
integration server runs all the tests against IE8. See http://ci.angularjs.org.
|
||||
The project currently supports and will attempt to fix bugs for IE9 and above. The continuous
|
||||
integration server runs all the tests against IE9, IE10, and IE11. See
|
||||
[Travis CI](https://travis-ci.org/angular/angular.js) and
|
||||
[ci.angularjs.org](http://ci.angularjs.org).
|
||||
|
||||
IE7 and below are not tested and the project makes no guarantee that Angular will work on it.
|
||||
A subset of the AngularJS functionality may work. It is up to you to test and decide whether
|
||||
it works for your particular app.
|
||||
|
||||
It is very unlikely that issues specific to IE7 or earlier will be given any time by the core team.
|
||||
[GitHub](https://github.com/angular/angular.js/issues/4974)
|
||||
We do not run tests on IE8 and below. A subset of the AngularJS functionality may work on these
|
||||
browsers, but it is up to you to test and decide whether it works for your particular app.
|
||||
|
||||
|
||||
# Short Version
|
||||
## Short Version
|
||||
|
||||
To make your Angular application work on IE please make sure that:
|
||||
|
||||
1. You polyfill JSON.stringify for IE7 and below. You can use
|
||||
[JSON2](https://github.com/douglascrockford/JSON-js) or
|
||||
[JSON3](http://bestiejs.github.com/json3/) polyfills for this.
|
||||
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
@@ -42,7 +47,7 @@ To make your Angular application work on IE please make sure that:
|
||||
```
|
||||
|
||||
2. add `id="ng-app"` to the root element in conjunction with `ng-app` attribute
|
||||
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
@@ -54,7 +59,7 @@ To make your Angular application work on IE please make sure that:
|
||||
`<div ng-view>` instead), or
|
||||
|
||||
4. if you **do use** custom element tags, then you must take these steps to make IE 8 and below happy:
|
||||
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
@@ -64,7 +69,7 @@ To make your Angular application work on IE please make sure that:
|
||||
document.createElement('ng-include');
|
||||
document.createElement('ng-pluralize');
|
||||
document.createElement('ng-view');
|
||||
|
||||
|
||||
// Optionally these for CSS
|
||||
document.createElement('ng:include');
|
||||
document.createElement('ng:pluralize');
|
||||
@@ -79,7 +84,7 @@ To make your Angular application work on IE please make sure that:
|
||||
```
|
||||
5. Use `ng-style` tags instead of `style="{{ someCss }}"`. The later works in Chrome and Firefox
|
||||
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
|
||||
|
||||
|
||||
|
||||
The **important** parts are:
|
||||
|
||||
@@ -92,7 +97,7 @@ The **important** parts are:
|
||||
happy.
|
||||
|
||||
|
||||
# Long Version
|
||||
## Long Version
|
||||
|
||||
IE has issues with element tag names which are not standard HTML tag names. These fall into two
|
||||
categories, and each category has its own fix.
|
||||
@@ -165,7 +170,7 @@ In IE, the behavior is that the `BODY` element has three children:
|
||||
|
||||
## CSS Styling of Custom Tag Names
|
||||
|
||||
To make CSS selectors work with custom elements, the custom element name must be pre-created with
|
||||
To make CSS selectors work with custom elements, the custom element name must be pre-created with
|
||||
`document.createElement('my-tag')` regardless of XML namespace.
|
||||
|
||||
```html
|
||||
|
||||
@@ -57,6 +57,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
* **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
|
||||
* **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
|
||||
* **Visualization:** [SVG](http://gaslight.co/blog/angular-backed-svgs), [D3.js](http://www.ng-newsletter.com/posts/d3-on-angular.html)
|
||||
* **Local Storage and session:** [ngStorage](https://github.com/gsklee/ngStorage)
|
||||
|
||||
## Tools
|
||||
|
||||
@@ -69,17 +70,19 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
This is a short list of libraries with specific support and documentation for working with Angular. You can find a full list of all known Angular external libraries at [ngmodules.org](http://ngmodules.org/).
|
||||
|
||||
* **Internationalization:** [angular-translate](http://pascalprecht.github.io/angular-translate/), [angular-gettext](http://angular-gettext.rocketeer.be/)
|
||||
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/)
|
||||
* **RESTful services:** [Restangular](https://github.com/mgonto/restangular)
|
||||
* **SQL and NoSQL backends:** [BreezeJS](http://www.breezejs.com/), [AngularFire](http://angularfire.com/)
|
||||
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/)
|
||||
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/), [ngTagsInput](https://github.com/mbenford/ngTagsInput)
|
||||
* **Advanced Routing:** [UI-Router](https://github.com/angular-ui/ui-router)
|
||||
* **Maps:** [UI-Map (Google Maps)](https://github.com/angular-ui/ui-map)
|
||||
|
||||
## Deployment
|
||||
|
||||
### General
|
||||
|
||||
* **Javascript minification: **[Background](http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/), [ngmin automation tool](http://www.thinkster.io/pick/XlWneEZCqY/angularjs-ngmin)
|
||||
* **Tracking:** [Angularyitcs (Google Analytics)](http://ngmodules.org/modules/angularytics), [Logging Client-Side Errors](http://www.bennadel.com/blog/2542-Logging-Client-Side-Errors-With-AngularJS-And-Stacktrace-js.htm)
|
||||
* **Analytics and Logging:** [Angularyitcs (Google Analytics)](http://ngmodules.org/modules/angularytics), [Angulartics (Analytics)](https://github.com/luisfarzati/angulartics), [Logging Client-Side Errors](http://www.bennadel.com/blog/2542-Logging-Client-Side-Errors-With-AngularJS-And-Stacktrace-js.htm)
|
||||
* **SEO:** [By hand](http://www.yearofmoo.com/2012/11/angularjs-and-seo.html), [prerender.io](http://prerender.io/), [Brombone](http://www.brombone.com/), [SEO.js](http://getseojs.com/), [SEO4Ajax](http://www.seo4ajax.com/)
|
||||
|
||||
### Server-Specific
|
||||
@@ -110,6 +113,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* **Free online:**
|
||||
[thinkster.io](http://thinkster.io),
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
|
||||
[CodeSchool](https://www.codeschool.com/courses/shaping-up-with-angular-js)
|
||||
* **Paid online:**
|
||||
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
|
||||
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
|
||||
|
||||
@@ -22,7 +22,7 @@ The impedance mismatch between dynamic applications and static documents is ofte
|
||||
in charge and it calls into the library when it sees fit. E.g., `jQuery`.
|
||||
* **frameworks** - a particular implementation of a web application, where your code fills in
|
||||
the details. The framework is in charge and it calls into your code when it needs something
|
||||
app specific. E.g., `knockout`, `ember`, etc.
|
||||
app specific. E.g., `durandal`, `ember`, etc.
|
||||
|
||||
|
||||
Angular takes another approach. It attempts to minimize the impedance mismatch between document
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
@name Migrating from 1.0 to 1.2
|
||||
@description
|
||||
|
||||
# Migrating from 1.0 to 1.2
|
||||
|
||||
AngularJS version 1.2 introduces several breaking changes that may require changes to your
|
||||
application's source code.
|
||||
|
||||
Although we try to avoid breaking changes, there are some cases where it is unavoidable.
|
||||
AngularJS 1.2 has undergone a thourough security review to make applications safer by default,
|
||||
AngularJS 1.2 has undergone a thorough security review to make applications safer by default,
|
||||
which has driven many of these changes. Several new features, especially animations, would not
|
||||
be possible without a few changes. Finally, some outstanding bugs were best fixed by changing
|
||||
an existing API.
|
||||
@@ -26,7 +27,7 @@ below should still apply, but you may want to consult the
|
||||
<li>{@link guide/migration#ngroute-has-been-moved-into-its-own-module ngRoute has been moved into its own module}</li>
|
||||
<li>{@link guide/migration#templates-no-longer-automatically-unwrap-promises Templates no longer automatically unwrap promises}</li>
|
||||
<li>{@link guide/migration#syntax-for-named-wildcard-parameters-changed-in Syntax for named wildcard parameters changed in <code>$route</code>}</li>
|
||||
<li>{@link guide/migration#you-can-only-bind-one-expression-to You can only bind one expression to <code>*[src]</code> or <code>*[ng-src]</code>}</li>
|
||||
<li>{@link guide/migration#you-can-only-bind-one-expression-to You can only bind one expression to <code>*[src]</code>, <code>*[ng-src]</code> or <code>action</code>}</li>
|
||||
<li>{@link guide/migration#interpolations-inside-dom-event-handlers-are-now-disallowed Interpolations inside DOM event handlers are now disallowed}</li>
|
||||
<li>{@link guide/migration#directives-cannot-end-with--start-or--end Directives cannot end with -start or -end}</li>
|
||||
<li>{@link guide/migration#in-$q,-promisealways-has-been-renamed-promisefinally In $q, promise.always has been renamed promise.finally}</li>
|
||||
@@ -43,11 +44,13 @@ below should still apply, but you may want to consult the
|
||||
<li>{@link guide/migration#ngscenario ngScenario}</li>
|
||||
<li>{@link guide/migration#nginclude-and-ngview-replace-its-entire-element-on-update ngInclude and ngView replace its entire element on update}</li>
|
||||
<li>{@link guide/migration#urls-are-now-sanitized-against-a-whitelist URLs are now sanitized against a whitelist}</li>
|
||||
<li>{@link guide/migration#isolate-scope-only-exposed-to-directives-with-property Isolate scope only exposed to directives with <code>scope</code> property}</li>
|
||||
<li>{@link guide/migration#isolate-scope-only-exposed-to-directives-with-scope-property Isolate scope only exposed to directives with <code>scope</code> property}</li>
|
||||
<li>{@link guide/migration#change-to-interpolation-priority Change to interpolation priority}</li>
|
||||
<li>{@link guide/migration#underscore-prefixed/suffixed-properties-are-non-bindable Underscore-prefixed/suffixed properties are non-bindable}</li>
|
||||
<li>{@link guide/migration#you-cannot-bind-to-select[multiple] You cannot bind to select[multiple]}</li>
|
||||
<li>{@link guide/migration#uncommon-region-specific-local-files-were-removed-from-i18n Uncommon region-specific local files were removed from i18n}</li>
|
||||
<li>{@link guide/migration#services-can-now-return-functions Services can now return functions}</li>
|
||||
<li>{@link guide/migration#modifying-the-dom-outside-digest-cycle Modifying the DOM outside digest cycle}</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -137,17 +140,18 @@ $routeProvider.when('/Book1/:book/Chapter/:chapter/:highlight*/edit',
|
||||
See [04cebcc1](https://github.com/angular/angular.js/commit/04cebcc133c8b433a3ac5f72ed19f3631778142b).
|
||||
|
||||
|
||||
## You can only bind one expression to `*[src]` or `*[ng-src]`
|
||||
## You can only bind one expression to `*[src]`, `*[ng-src]` or `action`
|
||||
|
||||
With the exception of `<a>` and `<img>` elements, you cannot bind more than one expression to the
|
||||
`src` attribute of elements.
|
||||
`src` or `action` attribute of elements.
|
||||
|
||||
This is one of several improvements to security introduces by Angular 1.2.
|
||||
|
||||
Concatenating expressions makes it hard to understand whether some combination of concatenated
|
||||
values are unsafe to use and potentially subject to XSS vulnerabilities. To simplify the task of
|
||||
auditing for XSS issues, we now require that a single expression be used for `*[src/ng-src]`
|
||||
bindings such as bindings for `iframe[src]`, `object[src]`, etc.
|
||||
bindings such as bindings for `iframe[src]`, `object[src]`, etc. In addition, this requirement is
|
||||
enforced for `form` tags with `action` attributes.
|
||||
|
||||
<table class="table table-bordered code-table">
|
||||
<thead>
|
||||
@@ -493,7 +497,7 @@ See [31f190d4](https://github.com/angular/angular.js/commit/31f190d4d53921d32253
|
||||
|
||||
the priority of ngRepeat, ngSwitchWhen, ngIf, ngInclude and ngView has changed. This could affect directives that explicitly specify their priority.
|
||||
|
||||
In order to make ngRepeat, ngSwitchWhen, ngIf, ngInclude and ngView work together in all common scenarios their directives are being adjusted to achieve the following precendence:
|
||||
In order to make ngRepeat, ngSwitchWhen, ngIf, ngInclude and ngView work together in all common scenarios their directives are being adjusted to achieve the following precedence:
|
||||
|
||||
|
||||
Directive | Old Priority | New Priority
|
||||
@@ -532,7 +536,7 @@ See [7d69d52a](https://github.com/angular/angular.js/commit/7d69d52acff8578e0f7d
|
||||
|
||||
A whitelist configured via `$compileProvider` can be used to configure what URLs are considered safe.
|
||||
By default all common protocol prefixes are whitelisted including `data:` URIs with mime types `image/*`.
|
||||
This change sholdn't impact apps that don't contain malicious image links.
|
||||
This change shouldn't impact apps that don't contain malicious image links.
|
||||
|
||||
See [1adf29af](https://github.com/angular/angular.js/commit/1adf29af13890d61286840177607edd552a9df97),
|
||||
[3e39ac7e](https://github.com/angular/angular.js/commit/3e39ac7e1b10d4812a44dad2f959a93361cd823b).
|
||||
@@ -540,9 +544,45 @@ See [1adf29af](https://github.com/angular/angular.js/commit/1adf29af13890d612868
|
||||
|
||||
## Isolate scope only exposed to directives with `scope` property
|
||||
|
||||
Directives without isolate scope do not get the isolate scope from an isolate directive on the
|
||||
same element. If your code depends on this behavior (non-isolate directive needs to access state
|
||||
from within the isolate scope), change the isolate directive to use scope locals to pass these explicitly.
|
||||
If you declare a scope option on a directive, that directive will have an
|
||||
[isolate scope](https://github.com/angular/angular.js/wiki/Understanding-Scopes). In Angular 1.0, if a
|
||||
directive with an isolate scope is used on an element, all directives on that same element have access
|
||||
to the same isolate scope. For example, say we have the following directives:
|
||||
|
||||
```
|
||||
// This directive declares an isolate scope.
|
||||
.directive('isolateScope', function() {
|
||||
return {
|
||||
scope: {},
|
||||
link: function($scope) {
|
||||
console.log('one = ' + $scope.$id);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
// This directive does not.
|
||||
.directive('nonIsolateScope', function() {
|
||||
return {
|
||||
link: function($scope) {
|
||||
console.log('two = ' + $scope.$id);
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
Now what happens if we use both directives on the same element?
|
||||
|
||||
```
|
||||
<div isolate-scope non-isolate-scope></div>
|
||||
```
|
||||
|
||||
In Angular 1.0, the nonIsolateScope directive will have access to the isolateScope directive’s scope. The
|
||||
log statements will print the same id, because the scope is the same. But in Angular 1.2, the nonIsolateScope
|
||||
will not use the same scope as isolateScope. Instead, it will inherit the parent scope. The log statements
|
||||
will print different id’s.
|
||||
|
||||
If your code depends on the Angular 1.0 behavior (non-isolate directive needs to access state
|
||||
from within the isolate scope), change the isolate directive to use scope locals to pass these explicitly:
|
||||
|
||||
**Before**
|
||||
|
||||
@@ -613,7 +653,7 @@ controller.) That's easier said that done for two reasons:
|
||||
someone on the scope chain for JavaScript use, you also expose it to
|
||||
Angular expressions
|
||||
2. The new `controller as` syntax that's now in increased usage exposes the
|
||||
entire controller on the scope chain greatly increaing the exposed surface.
|
||||
entire controller on the scope chain greatly increasing the exposed surface.
|
||||
|
||||
Though Angular expressions are written and controlled by the developer, they:
|
||||
|
||||
@@ -653,3 +693,39 @@ load and use your copy of the locale file provided that you maintain it yourself
|
||||
|
||||
See [6382e21f](https://github.com/angular/angular.js/commit/6382e21fb28541a2484ac1a241d41cf9fbbe9d2c).
|
||||
|
||||
## Services can now return functions
|
||||
|
||||
Previously, the service constructor only returned objects regardless of whether a function was returned.
|
||||
|
||||
Now, `$injector.instantiate` (and thus `$provide.service`) behaves the same as the native
|
||||
`new` operator and allows functions to be returned as a service.
|
||||
|
||||
If using a JavaScript preprocessor it's quite possible when upgrading that services could start behaving incorrectly.
|
||||
Make sure your services return the correct type wanted.
|
||||
|
||||
**Coffeescript example**
|
||||
|
||||
```
|
||||
myApp.service 'applicationSrvc', ->
|
||||
@something = "value"
|
||||
@someFunct = ->
|
||||
"something else"
|
||||
```
|
||||
|
||||
pre 1.2 this service would return the whole object as the service.
|
||||
|
||||
post 1.2 this service returns `someFunct` as the value of the service
|
||||
|
||||
you would need to change this services to
|
||||
|
||||
```
|
||||
myApp.service 'applicationSrvc', ->
|
||||
@something = "value"
|
||||
@someFunct = ->
|
||||
"something else"
|
||||
return
|
||||
```
|
||||
|
||||
to continue to return the complete instance.
|
||||
|
||||
See [c22adbf1](https://github.com/angular/angular.js/commit/c22adbf160f32c1839fbb35382b7a8c6bcec2927).
|
||||
|
||||
@@ -66,8 +66,9 @@ that you break your application to multiple modules like this:
|
||||
* And an application level module which depends on the above modules and contains any
|
||||
initialization code.
|
||||
|
||||
We've also written a document on how we organize large apps at Google and on how to write
|
||||
reusable components.
|
||||
We've also
|
||||
[written a document](http://blog.angularjs.org/2014/02/an-angularjs-style-guide-and-best.html)
|
||||
on how we organize large apps at Google.
|
||||
|
||||
The above is a suggestion. Tailor it to your needs.
|
||||
|
||||
@@ -172,7 +173,7 @@ angular.module('myModule', []).
|
||||
|
||||
<div class="alert alert-info">
|
||||
When bootstrapping, first Angular applies all constant definitions.
|
||||
Then Angular applies configuration blocks in the order same order they were registered.
|
||||
Then Angular applies configuration blocks in the same order they were registered.
|
||||
</div>
|
||||
|
||||
## Run Blocks
|
||||
|
||||
@@ -83,7 +83,7 @@ On to more complex examples!
|
||||
## Factory Recipe
|
||||
|
||||
The Value recipe is very simple to write, but lacks some important features we often need when
|
||||
creating services. Let's now look at the Value recipe's more powerful sibling, the Factory.The
|
||||
creating services. Let's now look at the Value recipe's more powerful sibling, the Factory. The
|
||||
Factory recipe adds the following abilities:
|
||||
|
||||
* ability to use other services (have dependencies)
|
||||
@@ -97,7 +97,7 @@ created by this recipe.
|
||||
Note: All services in Angular are singletons. That means that the injector uses each recipe at most
|
||||
once to create the object. The injector then caches the reference for all future needs.
|
||||
|
||||
Since Factory is more powerful version of Value recipe, you can construct the same service with it.
|
||||
Since Factory is more a powerful version of the Value recipe, you can construct the same service with it.
|
||||
Using our previous `clientId` Value recipe example, we can rewrite it as a Factory recipe like
|
||||
this:
|
||||
|
||||
@@ -111,8 +111,8 @@ But given that the token is just a string literal, sticking with the Value recip
|
||||
appropriate as it makes the code easier to follow.
|
||||
|
||||
Let's say, however, that we would also like to create a service that computes a token used for
|
||||
authentication against a remote API. This token will be called 'apiToken' and will be computed
|
||||
based on the `clientId` value and a secret stored in browser's local storage:
|
||||
authentication against a remote API. This token will be called `apiToken` and will be computed
|
||||
based on the `clientId` value and a secret stored in the browser's local storage:
|
||||
|
||||
```javascript
|
||||
myApp.factory('apiToken', ['clientId', function apiTokenFactory(clientId) {
|
||||
@@ -129,11 +129,11 @@ myApp.factory('apiToken', ['clientId', function apiTokenFactory(clientId) {
|
||||
```
|
||||
|
||||
In the code above, we see how the `apiToken` service is defined via the Factory recipe that depends
|
||||
on `clientId` service. The factory service then uses NSA-proof encryption to produce an authentication
|
||||
on the `clientId` service. The factory service then uses NSA-proof encryption to produce an authentication
|
||||
token.
|
||||
|
||||
Note: It is a best practice to name the factory functions as "<serviceId>Factory"
|
||||
(e.g. apiTokenFactory). While this names are not required, they help when navigating the code base
|
||||
Note: It is best practice to name the factory functions as `<serviceId>Factory`
|
||||
(e.g. apiTokenFactory). While this naming convention is not required, it helps when navigating the code base
|
||||
or looking at stack traces in the debugger.
|
||||
|
||||
Just like with Value recipe, Factory recipe can create a service of any type, whether it be a
|
||||
@@ -143,7 +143,7 @@ primitive, object literal, function, or even an instance of a custom type.
|
||||
## Service Recipe
|
||||
|
||||
JavaScript developers often use custom types to write object-oriented code. Let's explore how we
|
||||
could launch a unicorn into the space via our `unicornLauncher` service that is an instance of
|
||||
could launch a unicorn into space via our `unicornLauncher` service which is an instance of a
|
||||
custom type:
|
||||
|
||||
```javascript
|
||||
@@ -187,7 +187,7 @@ myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);
|
||||
Much simpler!
|
||||
|
||||
Note: Yes, we have called one of our service recipes 'Service'. We regret this and know that we'll
|
||||
be somehow punished for our mis-deed. It's like we named one of our offspring 'Children'. Boy,
|
||||
be somehow punished for our mis-deed. It's like we named one of our offspring 'Child'. Boy,
|
||||
that would mess with the teachers.
|
||||
|
||||
|
||||
|
||||
@@ -42,15 +42,16 @@ arrangement isolates the controller from the directive as well as from DOM. This
|
||||
point since it makes the controllers view agnostic, which greatly improves the testing story of
|
||||
the applications.
|
||||
|
||||
<example>
|
||||
<example module="scopeExample">
|
||||
<file name="script.js">
|
||||
function MyController($scope) {
|
||||
$scope.username = 'World';
|
||||
angular.module('scopeExample', [])
|
||||
.controller('MyController', ['$scope', function($scope) {
|
||||
$scope.username = 'World';
|
||||
|
||||
$scope.sayHello = function() {
|
||||
$scope.greeting = 'Hello ' + $scope.username + '!';
|
||||
};
|
||||
}
|
||||
$scope.sayHello = function() {
|
||||
$scope.greeting = 'Hello ' + $scope.username + '!';
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="MyController">
|
||||
@@ -122,13 +123,13 @@ inheritance, and child scopes prototypically inherit from their parents.
|
||||
This example illustrates scopes in application, and prototypical inheritance of properties. The example is followed by
|
||||
a diagram depicting the scope boundaries.
|
||||
|
||||
<example>
|
||||
<example module="scopeExample">
|
||||
<file name="index.html">
|
||||
<div class="show-scope-demo">
|
||||
<div ng-controller="GreetCtrl">
|
||||
<div ng-controller="GreetController">
|
||||
Hello {{name}}!
|
||||
</div>
|
||||
<div ng-controller="ListCtrl">
|
||||
<div ng-controller="ListController">
|
||||
<ol>
|
||||
<li ng-repeat="name in names">{{name}} from {{department}}</li>
|
||||
</ol>
|
||||
@@ -136,14 +137,14 @@ a diagram depicting the scope boundaries.
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
function GreetCtrl($scope, $rootScope) {
|
||||
$scope.name = 'World';
|
||||
$rootScope.department = 'Angular';
|
||||
}
|
||||
|
||||
function ListCtrl($scope) {
|
||||
$scope.names = ['Igor', 'Misko', 'Vojta'];
|
||||
}
|
||||
angular.module('scopeExample', [])
|
||||
.controller('GreetController', ['$scope', '$rootScope', function($scope, $rootScope) {
|
||||
$scope.name = 'World';
|
||||
$rootScope.department = 'Angular';
|
||||
}])
|
||||
.controller('ListController', ['$scope', function($scope) {
|
||||
$scope.names = ['Igor', 'Misko', 'Vojta'];
|
||||
}]);
|
||||
</file>
|
||||
<file name="style.css">
|
||||
.show-scope-demo.ng-scope,
|
||||
@@ -190,14 +191,15 @@ Scopes can propagate events in similar fashion to DOM events. The event can be {
|
||||
ng.$rootScope.Scope#$broadcast broadcasted} to the scope children or {@link
|
||||
ng.$rootScope.Scope#$emit emitted} to scope parents.
|
||||
|
||||
<example>
|
||||
<example module="eventExample">
|
||||
<file name="script.js">
|
||||
function EventController($scope) {
|
||||
$scope.count = 0;
|
||||
$scope.$on('MyEvent', function() {
|
||||
$scope.count++;
|
||||
});
|
||||
}
|
||||
angular.module('eventExample', [])
|
||||
.controller('EventController', ['$scope', function($scope) {
|
||||
$scope.count = 0;
|
||||
$scope.$on('MyEvent', function() {
|
||||
$scope.count++;
|
||||
});
|
||||
}]);
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="EventController">
|
||||
@@ -268,7 +270,7 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
|
||||
|
||||
4. **Mutation observation**
|
||||
|
||||
At the end `$apply`, Angular performs a {@link ng.$rootScope.Scope#$digest
|
||||
At the end of `$apply`, Angular performs a {@link ng.$rootScope.Scope#$digest
|
||||
$digest} cycle on the root scope, which then propagates throughout all child scopes. During
|
||||
the `$digest` cycle, all `$watch`ed expressions or functions are checked for model mutation
|
||||
and if a mutation is detected, the `$watch` listener is called.
|
||||
@@ -350,15 +352,15 @@ The diagram and the example below describe how Angular interacts with the browse
|
||||
|
||||
Angular modifies the normal JavaScript flow by providing its own event processing loop. This
|
||||
splits the JavaScript into classical and Angular execution context. Only operations which are
|
||||
applied in Angular execution context will benefit from Angular data-binding, exception handling,
|
||||
property watching, etc... You can also use $apply() to enter Angular execution context from JavaScript. Keep in
|
||||
applied in the Angular execution context will benefit from Angular data-binding, exception handling,
|
||||
property watching, etc... You can also use $apply() to enter the Angular execution context from JavaScript. Keep in
|
||||
mind that in most places (controllers, services) $apply has already been called for you by the
|
||||
directive which is handling the event. An explicit call to $apply is needed only when
|
||||
implementing custom event callbacks, or when working with third-party library callbacks.
|
||||
|
||||
1. Enter Angular execution context by calling {@link guide/scope scope}`.`{@link
|
||||
ng.$rootScope.Scope#$apply $apply}`(stimulusFn)`. Where `stimulusFn` is
|
||||
the work you wish to do in Angular execution context.
|
||||
1. Enter the Angular execution context by calling {@link guide/scope scope}`.`{@link
|
||||
ng.$rootScope.Scope#$apply $apply}`(stimulusFn)`, where `stimulusFn` is
|
||||
the work you wish to do in the Angular execution context.
|
||||
2. Angular executes the `stimulusFn()`, which typically modifies application state.
|
||||
3. Angular enters the {@link ng.$rootScope.Scope#$digest $digest} loop. The
|
||||
loop is made up of two smaller loops which process {@link
|
||||
@@ -388,7 +390,7 @@ user enters text into the text field.
|
||||
1. the {@link ng.directive:ngModel ng-model} and {@link
|
||||
ng.directive:input input} {@link guide/directive
|
||||
directive} set up a `keydown` listener on the `<input>` control.
|
||||
2. the {@link ng.$interpolate {{name}} } interpolation
|
||||
2. the {@link ng.$interpolate interpolation}
|
||||
sets up a {@link ng.$rootScope.Scope#$watch $watch} to be notified of
|
||||
`name` changes.
|
||||
2. During the runtime phase:
|
||||
@@ -400,8 +402,8 @@ user enters text into the text field.
|
||||
3. Angular applies the `name = 'X';` to the model.
|
||||
4. The {@link ng.$rootScope.Scope#$digest $digest} loop begins
|
||||
5. The {@link ng.$rootScope.Scope#$watch $watch} list detects a change
|
||||
on the `name` property and notifies the {@link ng.$interpolate
|
||||
{{name}} } interpolation, which in turn updates the DOM.
|
||||
on the `name` property and notifies the {@link ng.$interpolate interpolation},
|
||||
which in turn updates the DOM.
|
||||
6. Angular exits the execution context, which in turn exits the `keydown` event and with it
|
||||
the JavaScript execution context.
|
||||
7. The browser re-renders the view with update text.
|
||||
|
||||
@@ -11,15 +11,15 @@ Angular services are:
|
||||
|
||||
* Lazily instantiated – Angular only instantiates a service when an application component depends
|
||||
on it.
|
||||
* Singletons – Each component is dependent on a service gets a reference to the single instance
|
||||
* Singletons – Each component dependent on a service gets a reference to the single instance
|
||||
generated by the service factory.
|
||||
|
||||
Angular offers several useful services (like {@link ng.$http `$http`}) but for most applications
|
||||
Angular offers several useful services (like {@link ng.$http `$http`}), but for most applications
|
||||
you'll also want to {@link services#creating-services create your own}.
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Note:** Like other core Angular identifiers built-in services always start with `$`
|
||||
(i.e. `$http`).
|
||||
(e.g. `$http`).
|
||||
</div>
|
||||
|
||||
|
||||
@@ -129,9 +129,15 @@ injection of `$window`, `$scope`, and our `notify` service:
|
||||
</file>
|
||||
</example>
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Careful:** If you plan to [minify](http://en.wikipedia.org/wiki/Minification_(programming) your code,
|
||||
your variable names will get renamed unless you use one of the annotation techniques above.
|
||||
<div class="alert alert-danger">
|
||||
**Careful:** If you plan to [minify](http://en.wikipedia.org/wiki/Minification_(programming)) your
|
||||
code, your variable names will get renamed unless you use one of the annotation techniques above.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
If you use a tool like [ngmin](https://github.com/btford/ngmin#ngmin) in your workflow you can
|
||||
use implicit dependency notation within your codebase and let **ngmin** automatically convert such
|
||||
injectable functions to the array notation prior to minifying.
|
||||
</div>
|
||||
|
||||
|
||||
@@ -215,8 +221,8 @@ In the example, note that:
|
||||
{@link ng.$log `$log`} services.
|
||||
* The `routeTemplateMonitor` service depends on the built-in {@link ngRoute.$route `$route`}
|
||||
service and our custom `batchLog` service.
|
||||
* Both services use the and array notation to declare their dependencies.
|
||||
* That the order of identifiers in the array is the same as the order of argument
|
||||
* Both services use the array notation to declare their dependencies.
|
||||
* The order of identifiers in the array is the same as the order of argument
|
||||
names in the factory function.
|
||||
|
||||
### Registering a Service with `$provide`
|
||||
@@ -234,7 +240,7 @@ angular.module('myModule', []).config(function($provide) {
|
||||
});
|
||||
```
|
||||
|
||||
This is technique is often used in unit tests to mock out a service's dependencies.
|
||||
This technique is often used in unit tests to mock out a service's dependencies.
|
||||
|
||||
|
||||
## Unit Testing
|
||||
@@ -293,5 +299,5 @@ it('should clear messages after alert', function() {
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link ./ng Angular Service API}
|
||||
* {@link ./api/ng/service Angular Service API}
|
||||
* {@link angular.injector Injector API}
|
||||
|
||||
@@ -49,7 +49,7 @@ Out of the four options in the list above, only the last one is testable. Let's
|
||||
### Using the `new` operator
|
||||
|
||||
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new`
|
||||
on a constructor. This permanently binds the call site to the type. For example, lets say that we try to
|
||||
on a constructor. This permanently binds the call site to the type. For example, let's say that we try to
|
||||
instantiate an `XHR` that will retrieve data from the server.
|
||||
|
||||
```js
|
||||
@@ -65,7 +65,7 @@ function MyClass() {
|
||||
|
||||
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would
|
||||
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
|
||||
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey
|
||||
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey
|
||||
patch, but that is a bad idea for many reasons which are outside the scope of this document.
|
||||
|
||||
Here's an example of how the class above becomes hard to test when resorting to monkey patching:
|
||||
@@ -133,7 +133,7 @@ function MyClass() {
|
||||
|
||||
However, where does the serviceRegistry come from? If it is:
|
||||
* `new`-ed up, the test has no chance to reset the services for testing.
|
||||
* a global look-up then the service returned is global as well (but resetting is easier, since
|
||||
* a global look-up then the service returned is global as well (but resetting is easier, since
|
||||
only one global variable exists to be reset).
|
||||
|
||||
The class above is hard to test since we have to change the global state:
|
||||
@@ -258,7 +258,7 @@ expect($scope.strength).toEqual('weak');
|
||||
```
|
||||
|
||||
Notice that the test is not only much shorter, it is also easier to follow what is happening. We say
|
||||
that such a test tells a story, rather then asserting random bits which don't seem to be related.
|
||||
that such a test tells a story, rather than asserting random bits which don't seem to be related.
|
||||
|
||||
## Filters
|
||||
{@link ng.$filterProvider Filters} are functions which transform the data into a user readable
|
||||
@@ -296,7 +296,7 @@ Now we can add a directive to our app.
|
||||
app.directive('aGreatEye', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
replace: true,
|
||||
template: '<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>'
|
||||
};
|
||||
});
|
||||
@@ -337,6 +337,12 @@ We inject the $compile service and $rootScope before each jasmine test. The $com
|
||||
to render the aGreatEye directive. After rendering the directive we ensure that the directive has
|
||||
replaced the content and "lidless, wreathed in flame, 2 times" is present.
|
||||
|
||||
### Testing Directives With External Templates
|
||||
|
||||
If your directive uses `templateUrl`, consider using
|
||||
[karma-ng-html2js-preprocessor](https://github.com/karma-runner/karma-ng-html2js-preprocessor)
|
||||
to pre-compile HTML templates and thus avoid having to load them over HTTP during test execution.
|
||||
Otherwise you may run into issues if the test directory hierarchy differs from the application's.
|
||||
|
||||
## Sample project
|
||||
See the [angular-seed](https://github.com/angular/angular-seed) project for an example.
|
||||
|
||||
@@ -11,12 +11,12 @@ See the [contributing guidelines](https://github.com/angular/angular.js/blob/mas
|
||||
for how to contribute your own code to AngularJS.
|
||||
|
||||
|
||||
1. {@link #building-and-testing-angularjs_installing-dependencies Installing Dependencies}
|
||||
2. {@link #building-and-testing-angularjs_forking-angular-on-github Forking Angular on Github}
|
||||
3. {@link #building-and-testing-angularjs_building-angularjs Building AngularJS}
|
||||
4. {@link #building-and-testing-angularjs_running-a-local-development-web-server Running a Local Development Web Server}
|
||||
5. {@link #building-and-testing-angularjs_running-the-unit-test-suite Running the Unit Test Suite}
|
||||
6. {@link #building-and-testing-angularjs_running-the-end-to-end-test-suite Running the End-to-end Test Suite}
|
||||
1. {@link misc/contribute#installing-dependencies Installing Dependencies}
|
||||
2. {@link misc/contribute#forking-angular-on-github Forking Angular on Github}
|
||||
3. {@link misc/contribute#building-angularjs Building AngularJS}
|
||||
4. {@link misc/contribute#running-a-local-development-web-server Running a Local Development Web Server}
|
||||
5. {@link misc/contribute#running-the-unit-test-suite Running the Unit Test Suite}
|
||||
6. {@link misc/contribute#running-the-end-to-end-test-suite Running the End-to-end Test Suite}
|
||||
|
||||
## Installing Dependencies
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ production.
|
||||
To point your code to an angular script on the Google CDN server, use the following template. This
|
||||
example points to the minified version 1.2.0:
|
||||
|
||||
<pre>
|
||||
```
|
||||
<!doctype html>
|
||||
<html ng-app>
|
||||
<head>
|
||||
@@ -27,7 +27,7 @@ example points to the minified version 1.2.0:
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
```
|
||||
|
||||
Note that only versions 1.0.1 and above are available on the CDN, if you need an earlier version
|
||||
you can use the <http://code.angularjs.org/> URL which was the previous recommended location for
|
||||
|
||||
@@ -72,7 +72,7 @@ The size of the file is < 36KB compressed and minified.
|
||||
|
||||
### Can I use the open-source Closure Library with Angular?
|
||||
|
||||
Yes, you can use widgets from the [Closure Library](http://code.google.com/closure/library)
|
||||
Yes, you can use widgets from the [Closure Library](https://developers.google.com/closure/library/)
|
||||
in Angular.
|
||||
|
||||
### Does Angular use the jQuery library?
|
||||
@@ -82,7 +82,7 @@ application is being bootstrapped. If jQuery is not present in your script path,
|
||||
to its own implementation of the subset of jQuery that we call {@link angular.element jQLite}.
|
||||
|
||||
Due to a change to use `on()`/`off()` rather than `bind()`/`unbind()`, Angular 1.2 only operates with
|
||||
jQuery 1.7.1 or above.
|
||||
jQuery 1.7.1 or above. However, Angular does not currently support jQuery 2.x or above.
|
||||
|
||||
|
||||
### What is testability like in Angular?
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
@ngdoc overview
|
||||
@name Miscellaneous
|
||||
@description
|
||||
|
||||
# Miscellaneous Links
|
||||
|
||||
- {@link misc/started Getting Started}
|
||||
- {@link misc/downloading Downloading AngularJS}
|
||||
- {@link misc/faq Frequently Asked Questions}
|
||||
- {@link misc/contribute Building AngularJS}
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
@name Getting Started
|
||||
@description
|
||||
|
||||
# Getting Started
|
||||
|
||||
We want you to have an easy time while starting to use Angular. We've put together the following steps on your path to
|
||||
becoming an Angular expert.
|
||||
|
||||
1. Read the {@link guide/concepts conceptual overview}.<br/>Understand Angular's vocabulary and how all the Angular
|
||||
components work together.
|
||||
1. Do the {@link tutorial/ AngularJS Tutorial}.<br/>Walk end-to-end through building an application complete with tests
|
||||
on top of a node.js web server. Covers every major AngularJS feature and show you how to set up your development
|
||||
on top of a node.js web server. Covers every major AngularJS feature and shows you how to set up your development
|
||||
environment.
|
||||
1. Download or clone the [Seed App project template](https://github.com/angular/angular-seed).<br/>Gives you a
|
||||
starter app with a directory layout, test harness, and scripts to begin building your application.
|
||||
|
||||
@@ -3,33 +3,35 @@
|
||||
@step -1
|
||||
@description
|
||||
|
||||
# PhoneCat Tutorial App
|
||||
|
||||
A great way to get introduced to AngularJS is to work through this tutorial, which walks you through
|
||||
the construction of an AngularJS web app. The app you will build is a catalog that displays a list
|
||||
of Android devices, lets you filter the list to see only devices that interest you, and then view
|
||||
details for any device.
|
||||
|
||||
<img class="diagram" src="img/tutorial/catalog_screen.png" width="488" height="413">
|
||||
<img class="diagram" src="img/tutorial/catalog_screen.png" width="488" height="413" alt="demo
|
||||
application running in the browser">
|
||||
|
||||
Work through the tutorial to see how Angular makes browsers smarter — without the use of extensions
|
||||
or plug-ins. As you work through the tutorial, you will:
|
||||
Follow the tutorial to see how Angular makes browsers smarter — without the use of native
|
||||
extensions or plug-ins:
|
||||
|
||||
* See examples of how to use client-side data binding and dependency injection to build dynamic
|
||||
views of data that change immediately in response to user actions.
|
||||
* See how Angular creates listeners on your data without the need for DOM manipulation.
|
||||
* Learn a better, easier way to test your web apps.
|
||||
* Learn how to use Angular services to make common web tasks, such as getting data into your app,
|
||||
easier.
|
||||
|
||||
And all of this works in any browser without modification to the browser!
|
||||
* See examples of how to use client-side data binding to build dynamic views of data that change
|
||||
immediately in response to user actions.
|
||||
* See how Angular keeps your views in sync with your data without the need for DOM manipulation.
|
||||
* Learn a better, easier way to test your web apps, with Karma and Protractor.
|
||||
* Learn how to use dependency injection and services to make common web tasks, such as getting data
|
||||
into your app, easier.
|
||||
|
||||
When you finish the tutorial you will be able to:
|
||||
|
||||
* Create a dynamic application that works in any browser.
|
||||
* Define the differences between Angular and common JavaScript frameworks.
|
||||
* Understand how data binding works in AngularJS.
|
||||
* Use the angular-seed project to quickly boot-strap your own projects.
|
||||
* Create and run tests.
|
||||
* Create a dynamic application that works in all modern browsers.
|
||||
* Use data binding to wire up your data model to your views.
|
||||
* Create and run unit tests, with Karma.
|
||||
* Create and run end to end tests, with Protractor.
|
||||
* Move application logic out of the template and into Controllers.
|
||||
* Get data from a server using Angular services.
|
||||
* Apply animations to your application, using ngAnimate.
|
||||
* Identify resources for learning more about AngularJS.
|
||||
|
||||
The tutorial guides you through the entire process of building a simple application, including
|
||||
@@ -40,80 +42,233 @@ You can go through the whole tutorial in a couple of hours or you may want to sp
|
||||
really digging into it. If you're looking for a shorter introduction to AngularJS, check out the
|
||||
{@link misc/started Getting Started} document.
|
||||
|
||||
# Get Started
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
The rest of this page explains how you can set up your machine to work with the code on your local
|
||||
machine. If you just want to read the tutorial then you can just go straight to the first step:
|
||||
[Step 0 - Bootstrapping](tutorial/step_00).
|
||||
|
||||
# Working with the code
|
||||
|
||||
You can follow this tutorial and hack on the code in either the Mac/Linux or the Windows
|
||||
environment. The tutorial relies on the use of Git versioning system for source code management.
|
||||
You don't need to know anything about Git to follow the tutorial. Select one of the tabs below
|
||||
and follow the instructions for setting up your computer.
|
||||
You can follow along with this tutorial and hack on the code in the comfort of your own computer.
|
||||
In this way you can get hands-on practice of really writing AngularJS code and also on using the
|
||||
recommended testing tools.
|
||||
|
||||
<div class="tabbable" show="true">
|
||||
<div class="tab-pane well" id="git-mac" title="Git on Mac/Linux">
|
||||
<ol>
|
||||
<li><p>You'll need Git, which you can get from
|
||||
<a href="http://git-scm.com/download">the Git site</a>.</p></li>
|
||||
<li><p>Clone the angular-phonecat repository located at
|
||||
<a href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
|
||||
<pre>git clone https://github.com/angular/angular-phonecat.git</pre>
|
||||
<p>This command creates the <code>angular-phonecat</code> directory in your current
|
||||
directory.</p></li>
|
||||
<li><p>Change your current directory to <code>angular-phonecat</code>:</p>
|
||||
<pre>cd angular-phonecat</pre>
|
||||
<p>The tutorial instructions, from now on, assume you are running all commands from the <code>angular-phonecat</code>
|
||||
directory.</p></li>
|
||||
<li><p>You will also need Node.js and Karma to run unit tests, so please verify that you have
|
||||
<a href="http://nodejs.org/">Node.js</a> v0.10 or better installed
|
||||
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
|
||||
command in a terminal window:</p></li>
|
||||
<pre>node --version</pre>
|
||||
<p>Additionally install <a href="http://karma-runner.github.io/">Karma</a> and its plugins if you
|
||||
don't have it already:</p>
|
||||
<pre>
|
||||
npm install
|
||||
</pre></li>
|
||||
<li><p>You will need an http server running on your system. Mac and Linux machines typically
|
||||
have Apache pre-installed, but If you don't already have one installed, you can use <code>node</code>
|
||||
to run a simple bundled http server: <code>node scripts/web-server.js</code>.</p></li>
|
||||
</ol>
|
||||
</div>
|
||||
The tutorial relies on the use of the [Git][git] versioning system for source code management.
|
||||
You don't need to know anything about Git to follow the tutorial other than how to install and run
|
||||
a few git commands.
|
||||
|
||||
<div class="tab-pane well" id="git-win" title="Git on Windows">
|
||||
<ol>
|
||||
<li><p>You will need Node.js and Karma to run unit tests, so please verify that you have
|
||||
<a href="http://nodejs.org/">Node.js</a> v0.10 or better installed
|
||||
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
|
||||
command in a terminal window:</p>
|
||||
<pre>node --version</pre>
|
||||
<p>Additionally install <a href="http://karma-runner.github.io/">Karma</a> if you
|
||||
don't have it already:</p>
|
||||
<pre>npm install -g karma</pre>
|
||||
</li>
|
||||
<li><p>You'll also need Git, which you can get from
|
||||
<a href="http://git-scm.com/download">the Git site</a>.</p></li>
|
||||
<li><p>Clone the angular-phonecat repository located at <a
|
||||
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
|
||||
<pre>git clone https://github.com/angular/angular-phonecat.git</pre>
|
||||
<p>This command creates the <code>angular-phonecat</code> directory in your current directory.</p></li>
|
||||
<li><p>Change your current directory to <code>angular-phonecat</code>:</p>
|
||||
<pre>cd angular-phonecat</pre>
|
||||
<p>The tutorial instructions assume you are running all commands from the <code>angular-phonecat</code>
|
||||
directory.</p>
|
||||
<p>You should run all <code>git</code> commands from Git bash.</p>
|
||||
<p>Other commands like <code>test.bat</code> or <code>e2e-test.bat</code> should be
|
||||
executed from the Windows command line.</li>
|
||||
<li><p>You need an http server running on your system, but if you don't already have one
|
||||
already installed, you can use <code>node</code> to run a simple
|
||||
bundled http server: <code>node scripts\web-server.js</code>.</p></li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
The last thing to do is to make sure your computer has a web browser and a good text editor
|
||||
installed. Now, let's get some cool stuff done!
|
||||
### Install Git
|
||||
|
||||
{@link step_00 <span class="btn btn-primary">Get Started!</span>}
|
||||
You can download and install Git from http://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
|
||||
- `git checkout ...` : check out a particular branch or a tagged version of the code to hack on
|
||||
|
||||
### Download angular-phonecat
|
||||
|
||||
Clone the [angular-phonecat repository][angular-phonecat] located at GitHub by running the following
|
||||
command:
|
||||
|
||||
```
|
||||
git clone --depth=14 https://github.com/angular/angular-phonecat.git
|
||||
```
|
||||
|
||||
This command creates the `angular-phonecat` directory in your current directory.
|
||||
|
||||
<div class="alert alert-info">The `--depth=14` option just tells Git to pull down only the last 14 commits. This makes the
|
||||
download much smaller and faster.
|
||||
</div>
|
||||
|
||||
Change your current directory to `angular-phonecat`.
|
||||
|
||||
```
|
||||
cd angular-phonecat
|
||||
```
|
||||
|
||||
The tutorial instructions, from now on, assume you are running all commands from the
|
||||
`angular-phonecat` directory.
|
||||
|
||||
|
||||
### Install Node.js
|
||||
|
||||
If you want to run the preconfigured local web-server and the test tools then you will also need
|
||||
[Node.js v0.10.27+][node].
|
||||
|
||||
You can download a Node.js installer for your operating system from http://nodejs.org/download/.
|
||||
|
||||
Check the version of Node.js that you have installed by running the following command:
|
||||
|
||||
```
|
||||
node --version
|
||||
```
|
||||
|
||||
In Debian based distributions, there is a name clash with another utility called `node`. The
|
||||
suggested solution is to also install the `nodejs-legacy` apt package, which renames `node` to
|
||||
`nodejs`.
|
||||
|
||||
```
|
||||
apt-get install nodejs-legacy
|
||||
nodejs --version
|
||||
```
|
||||
|
||||
|
||||
<div class="alert alert-info">If you need to run a different versions of node.js
|
||||
in your local environment, consider installing
|
||||
<a href="https://github.com/creationix/nvm" title="Node Version Manager Github Repo link">
|
||||
Node Version Manager (nvm)
|
||||
</a>.
|
||||
</div>
|
||||
|
||||
Once you have Node.js installed on your machine you can download the tool dependencies by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
This command will download the following tools, 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 Angular framework into the
|
||||
`app/bower_components` directory.
|
||||
|
||||
<div class="alert alert-info">
|
||||
Note the angular-phonecat project is setup to install and run these utilities via npm scripts.
|
||||
This means that you do not have to have any of these utilities installed globally on your system
|
||||
to follow the tutorial. See **Installing Helper Tools** below for more information.
|
||||
</div>
|
||||
|
||||
The project is preconfigured with a number of npm helper scripts to make it easy to run the common
|
||||
tasks that you will need while developing:
|
||||
|
||||
- `npm start` : start a local development web-server
|
||||
- `npm test` : start the Karma unit test runner
|
||||
- `npm run protractor` : run the Protractor end to end (E2E) tests
|
||||
- `npm run update-webdriver` : install the drivers needed by Protractor
|
||||
|
||||
### 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 ...`.
|
||||
|
||||
For instance to install the Bower command line executable you would do:
|
||||
|
||||
```
|
||||
sudo npm install -g bower
|
||||
```
|
||||
|
||||
*(Omit the sudo if running on Windows)*
|
||||
|
||||
Then you can run the bower tool directly, such as:
|
||||
|
||||
```
|
||||
bower install
|
||||
```
|
||||
|
||||
|
||||
### Running Development Web Server
|
||||
|
||||
While Angular applications are purely client-side code, and it is possible to open them in a web
|
||||
browser directly from the file system, it is better to serve them from a HTTP web server. In
|
||||
particular, for security reasons, most modern browsers will not allow JavaScript to make server
|
||||
requests if the page is loaded directly from the file system.
|
||||
|
||||
The angular-phonecat project is configured with a simple static web server for hosting the
|
||||
application during development. Start the web server by running:
|
||||
|
||||
```
|
||||
npm start
|
||||
```
|
||||
|
||||
This will create a local webserver that is listening to port 8000 on your local machine.
|
||||
You can now browse to the application at:
|
||||
|
||||
```
|
||||
http://localhost:8000/app/index.html
|
||||
```
|
||||
|
||||
### Running Unit Tests
|
||||
|
||||
We use unit tests to ensure that the JavaScript code in our application is operating correctly.
|
||||
Unit tests focus on testing small isolated parts of the application. The unit tests are kept in the
|
||||
`test/unit` directory.
|
||||
|
||||
The angular-phonecat project is configured to use [Karma][karma] to run the unit tests for the
|
||||
application. Start Karma by running:
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
This will start the Karma unit test runner. Karma will read the configuration file at
|
||||
`test/karma.conf.js`. This configuration file tells Karma to:
|
||||
|
||||
- open up a Chrome browser and connect it to Karma
|
||||
- execute all the unit tests in this browser
|
||||
- report the results of these tests in the terminal/command line window
|
||||
- watch all the project's JavaScript files and re-run the tests whenever any of these change
|
||||
|
||||
It is good to leave this running all the time, in the background, as it will give you immediate
|
||||
feedback about whether your changes pass the unit tests while you are working on the code.
|
||||
|
||||
|
||||
### Running End to End Tests
|
||||
|
||||
We use End to End tests to ensure that the application as a whole operates as expected.
|
||||
End to End tests are designed to test the whole client side application, in particular that the
|
||||
views are displaying and behaving correctly. It does this by simulating real user interaction with
|
||||
the real application running in the browser.
|
||||
|
||||
The End to End tests are kept in the `test/e2e` directory.
|
||||
|
||||
The angular-phonecat project is configured to use [Protractor][protractor] to run the End to End
|
||||
tests for the application. Protractor relies upon a set of drivers to allow it to interact with
|
||||
the browser. You can install these drivers by running:
|
||||
|
||||
```
|
||||
npm run update-webdriver
|
||||
```
|
||||
|
||||
*(You should only need to do this once.)*
|
||||
|
||||
Since Protractor works by interacting with a running application, we need to start our web server:
|
||||
|
||||
```
|
||||
npm start
|
||||
```
|
||||
|
||||
Then in a separate terminal/command line window, we can run the Protractor test scripts against the
|
||||
application by running:
|
||||
|
||||
```
|
||||
npm run protractor
|
||||
```
|
||||
|
||||
Protractor will read the configuration file at `test/protractor-conf.js`. This configuration tells
|
||||
Protractor to:
|
||||
|
||||
- open up a Chrome browser and connect it to the application
|
||||
- execute all the End to End tests in this browser
|
||||
- report the results of these tests in the terminal/command line window
|
||||
- close down the browser and exit
|
||||
|
||||
It is good to run the end to end tests whenever you make changes to the HTML views or want to check
|
||||
that the application as a whole is executing correctly. It is very common to run End to End tests
|
||||
before pushing a new commit of changes to a remote repository.
|
||||
|
||||
|
||||
[git]: http://git-scm.com/
|
||||
[node]: http://nodejs.org/
|
||||
[angular-phonecat]: https://github.com/angular/angular-phonecat
|
||||
[protractor]: https://github.com/angular/protractor
|
||||
[bower]: http://bower.io/
|
||||
[http-server]: https://github.com/nodeapps/http-server
|
||||
[karma]: https://github.com/karma-runner/karma
|
||||
|
||||
@@ -11,65 +11,27 @@ with the most important source code files, learn how to start the development se
|
||||
angular-seed, and run the application in the browser.
|
||||
|
||||
|
||||
<div class="tabbable" show="true" ng-model="$cookies.platformPreference">
|
||||
<div class="tab-pane well" id="git-mac" title="Git on Mac/Linux" value="gitUnix">
|
||||
<ol>
|
||||
<li><p>In <code>angular-phonecat</code> directory, run this command:</p>
|
||||
<pre>git checkout -f step-0</pre>
|
||||
<p>This resets your workspace to step 0 of the tutorial app.</p>
|
||||
<p>You must repeat this for every future step in the tutorial and change the number to
|
||||
the number of the step you are on. This will cause any changes you made within
|
||||
your working directory to be lost.</p></li>
|
||||
In `angular-phonecat` directory, run this command:
|
||||
|
||||
<li>To see the app running in a browser, do one of the following:
|
||||
<ul>
|
||||
<li><b>For node.js users:</b>
|
||||
<ol>
|
||||
<li>In a <i>separate</i> terminal tab or window, run <code>node ./scripts/web-server.js</code> to start the web server.</li>
|
||||
<li>Open a browser window for the app and navigate to <a
|
||||
href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><b>For other http servers:</b>
|
||||
<ol>
|
||||
<li>Configure the server to serve the files in the <code>angular-phonecat</code> directory.</li>
|
||||
<li>Navigate in your browser to <code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
```
|
||||
git checkout -f step-0
|
||||
```
|
||||
|
||||
|
||||
<div class="tab-pane well" id="git-win" title="Git on Windows" value="gitWin">
|
||||
<ol>
|
||||
<li><p>Open Git bash and run this command (in <code>angular-phonecat</code> directory):</p>
|
||||
<pre>git checkout -f step-0</pre>
|
||||
<p>This resets your workspace to step 0 of the tutorial app.</p>
|
||||
<p>You must repeat this for every future step in the tutorial and change the number to
|
||||
the number of the step you are on. This will cause any changes you made within
|
||||
your working directory to be lost.</p></li>
|
||||
<li>To see the app running in a browser, do one of the following:
|
||||
<ul>
|
||||
<li><b>For node.js users:</b>
|
||||
<ol>
|
||||
<li>In a <i>separate</i> terminal tab or window, run <code>node scripts\web-server.js</code> to start the web server.</li>
|
||||
<li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><b>For other http servers:</b>
|
||||
<ol>
|
||||
<li>Configure the server to serve the files in the <code>angular-phonecat</code> directory.</li>
|
||||
<li>Navigate in your browser to <code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
This resets your workspace to step 0 of the tutorial app.
|
||||
|
||||
You must repeat this for every future step in the tutorial and change the number to the number of
|
||||
the step you are on. This will cause any changes you made within your working directory to be lost.
|
||||
|
||||
If you haven't already done so you need to install the dependencies by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
To see the app running in a browser, open a *separate* terminal/command line tab or window, then
|
||||
run `npm start` to start the web server. Now, open a browser window for the app and navigate to
|
||||
<a href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a>
|
||||
|
||||
You can now see the page in your browser. It's not very exciting, but that's OK.
|
||||
|
||||
@@ -84,9 +46,9 @@ __`app/index.html`:__
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>My HTML File</title>
|
||||
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<link rel="stylesheet" href="css/bootstrap.css">
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -114,7 +76,7 @@ __`app/index.html`:__
|
||||
|
||||
* AngularJS script tag:
|
||||
|
||||
<script src="lib/angular/angular.js">
|
||||
<script src="bower_components/angular/angular.js">
|
||||
|
||||
This code downloads the `angular.js` script and registers a callback that will be executed by the
|
||||
browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
|
||||
@@ -126,8 +88,10 @@ being the element on which the `ngApp` directive was defined.
|
||||
|
||||
Nothing here {{'yet' + '!'}}
|
||||
|
||||
This line demonstrates the core feature of Angular's templating capabilities – a binding, denoted
|
||||
by double-curlies `{{ }}` as well as a simple expression `'yet' + '!'` used in this binding.
|
||||
This line demonstrates two core features of Angular's templating capabilities:
|
||||
|
||||
* a binding, denoted by double-curlies `{{ }}`
|
||||
* a simple expression `'yet' + '!'` used in this binding.
|
||||
|
||||
The binding tells Angular that it should evaluate an expression and insert the result into the
|
||||
DOM in place of the binding. Rather than a one-time insert, as we'll see in the next steps, a
|
||||
@@ -171,16 +135,18 @@ and one static binding, and our model is empty. That will soon change!
|
||||
|
||||
## What are all these files in my working directory?
|
||||
|
||||
Most of the files in your working directory come from the [angular-seed project](https://github.com/angular/angular-seed) which is typically used to bootstrap
|
||||
new Angular projects. The seed project includes the latest Angular libraries, test libraries,
|
||||
scripts and a simple example app, all pre-configured for developing a typical web app.
|
||||
|
||||
Most of the files in your working directory come from the [angular-seed project][angular-seed] which
|
||||
is typically used to bootstrap new Angular projects. The seed project is pre-configured to install
|
||||
the angular framework (via `bower` into the `app/bower_components/` folder) and tools for developing
|
||||
a typical web app (via `npm`).
|
||||
|
||||
For the purposes of this tutorial, we modified the angular-seed with the following changes:
|
||||
|
||||
* Removed the example app
|
||||
* Added phone images to `app/img/phones/`
|
||||
* Added phone data files (JSON) to `app/phones/`
|
||||
* Added [Bootstrap](http://getbootstrap.com) files to `app/css/` and `app/img/`
|
||||
* Added a dependency on [Bootstrap](http://getbootstrap.com) in the `bower.json` file.
|
||||
|
||||
|
||||
|
||||
@@ -199,9 +165,5 @@ Now let's go to {@link step_01 step 1} and add some content to the web app.
|
||||
|
||||
<ul doc-tutorial-nav="0"></ul>
|
||||
|
||||
<div style="display: none">
|
||||
Note: During the bootstrap the injector and the root scope will then be associated with the
|
||||
element on which the `ngApp` directive was declared, so when debugging the app you can retrieve
|
||||
them from browser console via `angular.element(rootElement).scope()` and
|
||||
`angular.element(rootElement).injector()`.
|
||||
</div>
|
||||
|
||||
[angular-seed]: https://github.com/angular/angular-seed
|
||||
|
||||
@@ -12,15 +12,12 @@ dynamically display the same result with any set of data.
|
||||
|
||||
In this step you will add some basic information about two cell phones to an HTML page.
|
||||
|
||||
- The page now contains a list with information about two phones.
|
||||
|
||||
<div doc-tutorial-reset="1"></div>
|
||||
|
||||
|
||||
The page now contains a list with information about two phones.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-0...step-1):
|
||||
|
||||
__`app/index.html`:__
|
||||
**`app/index.html`:**
|
||||
|
||||
```html
|
||||
<ul>
|
||||
|
||||
@@ -9,20 +9,16 @@
|
||||
Now it's time to make the web page dynamic — with AngularJS. We'll also add a test that verifies the
|
||||
code for the controller we are going to add.
|
||||
|
||||
There are many ways to structure the code for an application. For Angular apps, we encourage the
|
||||
use of [the Model-View-Controller (MVC)
|
||||
design pattern](http://en.wikipedia.org/wiki/Model–View–Controller) to decouple the code and to separate concerns. With that in mind, let's use a
|
||||
little Angular and JavaScript to add model, view, and controller components to our app.
|
||||
There are many ways to structure the code for an application. For Angular apps, we encourage the use of
|
||||
[the Model-View-Controller (MVC) design pattern](http://en.wikipedia.org/wiki/Model–View–Controller)
|
||||
to decouple the code and to separate concerns. With that in mind, let's use a little Angular and
|
||||
JavaScript to add model, view, and controller components to our app.
|
||||
|
||||
- The list of three phones is now generated dynamically from data
|
||||
|
||||
<div doc-tutorial-reset="2"></div>
|
||||
|
||||
|
||||
The app now contains a list with three phones.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-1...step-2):
|
||||
|
||||
|
||||
## View and Template
|
||||
|
||||
In Angular, the __view__ is a projection of the model through the HTML __template__. This means that
|
||||
@@ -37,7 +33,7 @@ __`app/index.html`:__
|
||||
<html ng-app="phonecatApp">
|
||||
<head>
|
||||
...
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="js/controllers.js"></script>
|
||||
</head>
|
||||
<body ng-controller="PhoneListCtrl">
|
||||
@@ -53,21 +49,21 @@ __`app/index.html`:__
|
||||
</html>
|
||||
```
|
||||
|
||||
We replaced the hard-coded phone list with the
|
||||
{@link ng.directive:ngRepeat ngRepeat directive} and two
|
||||
{@link guide/expression Angular expressions} enclosed in curly braces:
|
||||
`{{phone.name}}` and `{{phone.snippet}}`:
|
||||
We replaced the hard-coded phone list with the {@link ng.directive:ngRepeat ngRepeat directive}
|
||||
and two {@link guide/expression Angular expressions}:
|
||||
|
||||
* The `ng-repeat="phone in phones"` statement in the `<li>` tag is an Angular repeater. The
|
||||
repeater tells Angular to create a `<li>` element for each phone in the list using the first `<li>`
|
||||
* The `ng-repeat="phone in phones"` attribute in the `<li>` tag is an Angular repeater directive.
|
||||
The repeater tells Angular to create a `<li>` element for each phone in the list using the `<li>`
|
||||
tag as the template.
|
||||
* The expressions wrapped in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) will be replaced
|
||||
by the value of the expressions.
|
||||
|
||||
We have added a new directive, called `ng-controller`, which attaches a `PhoneListCtrl`
|
||||
__controller__ to the DOM at this point.
|
||||
__controller__ to the DOM at this point:
|
||||
|
||||
* As we've learned in {@link step_00 step 0}, the curly braces around `phone.name` and `phone.snippet` denote
|
||||
bindings. As opposed to evaluating constants, these expressions are referring to our application
|
||||
model, which was set up in our `PhoneListCtrl` controller.
|
||||
* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}` denote
|
||||
bindings, which are referring to our application model, which is set up in our `PhoneListCtrl`
|
||||
controller.
|
||||
|
||||
<img class="diagram" src="img/tutorial/tutorial_02.png">
|
||||
|
||||
@@ -128,23 +124,19 @@ To learn more about Angular scopes, see the {@link ng.$rootScope.Scope angular s
|
||||
## Tests
|
||||
|
||||
The "Angular way" of separating controller from the view, makes it easy to test code as it is being
|
||||
developed. If our controller is available on the global namespace then we can simply instantiate it
|
||||
with a mock `scope` object. Take a look at the following unit test for our controller:
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
developed. If our controller is available on the global namespace then we could simply instantiate it
|
||||
with a mock `scope` object:
|
||||
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
describe('PhoneListCtrl', function(){
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
it('should create "phones" model with 3 phones', function() {
|
||||
var scope = {},
|
||||
ctrl = new PhoneListCtrl(scope);
|
||||
|
||||
it('should create "phones" model with 3 phones', function() {
|
||||
var scope = {},
|
||||
ctrl = new PhoneListCtrl(scope);
|
||||
|
||||
expect(scope.phones.length).toBe(3);
|
||||
});
|
||||
expect(scope.phones.length).toBe(3);
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
@@ -154,67 +146,72 @@ Angular. Since testing is such a critical part of software development, we make
|
||||
tests in Angular so that developers are encouraged to write them.
|
||||
|
||||
### Testing non-Global Controllers
|
||||
In practice, you will not want to have your controller functions in the global namespace. Instead,
|
||||
we have registered our controllers in the `phonecatApp` module. In this case Angular provides a
|
||||
service, `$controller`, which will retrieve your controller by name. Here is the same test using
|
||||
`$controller`:
|
||||
|
||||
In practice, you will not want to have your controller functions in the global namespace. Instead,
|
||||
you can see that we have registered it via an anonymous constructor function on the `phonecatApp`
|
||||
module.
|
||||
|
||||
In this case Angular provides a service, `$controller`, which will retrieve your controller by name.
|
||||
Here is the same test using `$controller`:
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
describe('PhoneListCtrl', function(){
|
||||
|
||||
beforeEach(module('phonecatApp'));
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
it('should create "phones" model with 3 phones', inject(function($controller) {
|
||||
var scope = {},
|
||||
ctrl = $controller('PhoneListCtrl', {$scope:scope});
|
||||
|
||||
it('should create "phones" model with 3 phones', inject(function($controller) {
|
||||
var scope = {},
|
||||
ctrl = $controller('PhoneListCtrl', { $scope: scope });
|
||||
expect(scope.phones.length).toBe(3);
|
||||
}));
|
||||
|
||||
expect(scope.phones.length).toBe(3);
|
||||
}));
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Don't forget that we need to load up the `phonecatApp` module into the test so that the controller
|
||||
is available to be injected.
|
||||
* Before each test we tell Angular to load the `phonecatApp` module.
|
||||
* We ask Angular to `inject` the `$controller` service into our test function
|
||||
* We use `$controller` to create an instance of the `PhoneListCtrl`
|
||||
* With this instance, we verify that the phones array property on the scope contains three records.
|
||||
|
||||
|
||||
### Writing and Running Tests
|
||||
|
||||
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
|
||||
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
|
||||
this tutorial in Jasmine. You can learn about Jasmine on the [Jasmine home page](http://pivotal.github.com/jasmine/) and at the [Jasmine docs](http://pivotal.github.io/jasmine/).
|
||||
this tutorial in Jasmine v1.3. You can learn about Jasmine on the [Jasmine home page][jasmine] and
|
||||
at the [Jasmine docs][jasmine-docs].
|
||||
|
||||
The angular-seed project is pre-configured to run all unit tests using [Karma](http://karma-runner.github.io/). Ensure that the necessary karma plugins are installed.
|
||||
You can do this by issuing `npm install` into your terminal.
|
||||
The angular-seed project is pre-configured to run unit tests using [Karma][karma] but you will need
|
||||
to ensure that Karma and its necessary plugins are installed. You can do this by running
|
||||
`npm install`.
|
||||
|
||||
To run the tests, and then watch the files for changes: `npm test`.
|
||||
|
||||
To run the test, do the following:
|
||||
|
||||
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
|
||||
`./scripts/test.sh` (if you are on Windows, run scripts\test.bat) to start the Karma server (the
|
||||
config file necessary to start the server is located at `./config/karma.conf.js`).
|
||||
|
||||
2. Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
|
||||
* Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
|
||||
the background. Karma will use this browser for test execution.
|
||||
* You should see the following or similar output in the terminal:
|
||||
|
||||
3. You should see the following or similar output in the terminal:
|
||||
|
||||
info: Karma server started at http://localhost:9876/
|
||||
info (launcher): Starting browser "Chrome"
|
||||
info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
|
||||
Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
|
||||
<pre>
|
||||
info: Karma server started at http://localhost:9876/
|
||||
info (launcher): Starting browser "Chrome"
|
||||
info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
|
||||
Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
|
||||
</pre>
|
||||
|
||||
Yay! The test passed! Or not...
|
||||
|
||||
4. To rerun the tests, just change any of the source or test .js files. Karma will notice the change
|
||||
* To rerun the tests, just change any of the source or test .js files. Karma will notice the change
|
||||
and will rerun the tests for you. Now isn't that sweet?
|
||||
|
||||
# Experiments
|
||||
|
||||
* Add another binding to `index.html`. For example:
|
||||
|
||||
<p>Total number of phones: {{phones.length}}</p>
|
||||
```html
|
||||
<p>Total number of phones: {{phones.length}}</p>
|
||||
```
|
||||
|
||||
* Create a new model property in the controller and bind to it from the template. For example:
|
||||
|
||||
@@ -226,7 +223,11 @@ To run the test, do the following:
|
||||
|
||||
Refresh your browser and verify that it says "Hello, World!".
|
||||
|
||||
* Create a repeater that constructs a simple table:
|
||||
* Update the unit test for the controller in ./test/unit/controllersSpec.js to reflect the previous change. For example by adding:
|
||||
|
||||
expect(scope.name).toBe('World');
|
||||
|
||||
* Create a repeater in `index.html` that constructs a simple table:
|
||||
|
||||
<table>
|
||||
<tr><th>row number</th></tr>
|
||||
@@ -240,7 +241,7 @@ To run the test, do the following:
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
||||
</table>
|
||||
|
||||
* Make the unit test fail by changing the `toBe(3)` statement to `toBe(4)`.
|
||||
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
|
||||
|
||||
|
||||
# Summary
|
||||
@@ -251,3 +252,7 @@ to the app.
|
||||
|
||||
|
||||
<ul doc-tutorial-nav="2"></ul>
|
||||
|
||||
[jasmine]: http://jasmine.github.io/
|
||||
[jasmine-docs]: http://jasmine.github.io/1.3/introduction.html
|
||||
[karma]: http://karma-runner.github.io/
|
||||
@@ -11,15 +11,10 @@ simple; we will add full text search (yes, it will be simple!). We will also wri
|
||||
test, because a good end-to-end test is a good friend. It stays with your app, keeps an eye on it,
|
||||
and quickly detects regressions.
|
||||
|
||||
|
||||
<div doc-tutorial-reset="3"></div>
|
||||
|
||||
|
||||
The app now has a search box. Notice that the phone list on the page changes depending on what a
|
||||
* The app now has a search box. Notice that the phone list on the page changes depending on what a
|
||||
user types into the search box.
|
||||
|
||||
The most important differences between Steps 2 and 3 are listed below. You can see the full diff on
|
||||
[GitHub](https://github.com/angular/angular-phonecat/compare/step-2...step-3):
|
||||
<div doc-tutorial-reset="3"></div>
|
||||
|
||||
|
||||
## Controller
|
||||
@@ -33,14 +28,14 @@ __`app/index.html`:__
|
||||
|
||||
```html
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<!--Sidebar content-->
|
||||
|
||||
Search: <input ng-model="query">
|
||||
|
||||
</div>
|
||||
<div class="span10">
|
||||
<div class="col-md-10">
|
||||
<!--Body content-->
|
||||
|
||||
<ul class="phones">
|
||||
@@ -96,104 +91,101 @@ describe('PhoneCat App', function() {
|
||||
describe('Phone list view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../../app/index.html');
|
||||
browser.get('app/index.html');
|
||||
});
|
||||
|
||||
|
||||
it('should filter the phone list as user types into the search box', function() {
|
||||
expect(repeater('.phones li').count()).toBe(3);
|
||||
|
||||
input('query').enter('nexus');
|
||||
expect(repeater('.phones li').count()).toBe(1);
|
||||
var phoneList = element.all(by.repeater('phone in phones'));
|
||||
var query = element(by.model('query'));
|
||||
|
||||
input('query').enter('motorola');
|
||||
expect(repeater('.phones li').count()).toBe(2);
|
||||
expect(phoneList.count()).toBe(3);
|
||||
|
||||
query.sendKeys('nexus');
|
||||
expect(phoneList.count()).toBe(1);
|
||||
|
||||
query.clear();
|
||||
query.sendKeys('motorola');
|
||||
expect(phoneList.count()).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Even though the syntax of this test looks very much like our controller unit test written with
|
||||
Jasmine, the end-to-end test uses APIs of {@link guide/dev_guide.e2e-testing Angular's end-to-end
|
||||
test runner}.
|
||||
|
||||
To run the end-to-end test, open one of the following in a new browser tab:
|
||||
|
||||
* node.js users: http://localhost:8000/test/e2e/runner.html
|
||||
* users with other http servers:
|
||||
`http://localhost:[port-number]/[context-path]/test/e2e/runner.html`
|
||||
* casual reader: http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html
|
||||
|
||||
Previously we've seen how Karma can be used to execute unit tests. Well, it can also run the
|
||||
end-to-end tests! Use `./scripts/e2e-test.sh` (if you are on Windows, run `scripts\e2e-test.bat`) script for that. End-to-end tests are slow, so unlike
|
||||
with unit tests, Karma will exit after the test run and will not automatically rerun the test
|
||||
suite on every file change. To rerun the test suite, execute the `e2e-test.sh` or `e2e-test.bat` script again.
|
||||
|
||||
Note: You must ensure you've installed the karma-ng-scenario framework plugin prior to running the
|
||||
`e2e-test.sh` script. You can do this by issuing `npm install` into your terminal.
|
||||
|
||||
This test verifies that the search box and the repeater are correctly wired together. Notice how
|
||||
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
|
||||
really is that easy to set up any functional, readable, end-to-end test.
|
||||
|
||||
### Running End to End Tests with Protractor
|
||||
Even though the syntax of this test looks very much like our controller unit test written with
|
||||
Jasmine, the end-to-end test uses APIs of [Protractor](https://github.com/angular/protractor). Read
|
||||
about the Protractor APIs at https://github.com/angular/protractor/blob/master/docs/api.md.
|
||||
|
||||
Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
|
||||
Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
|
||||
will exit after the test run and will not automatically rerun the test suite on every file change.
|
||||
To rerun the test suite, execute `npm run protractor` again.
|
||||
|
||||
<div class="alert alert-info">
|
||||
Note: You must ensure you've installed the protractor and updated webdriver prior to running the
|
||||
`npm run protractor`. You can do this by issuing `npm install` and `npm run update-webdriver` into
|
||||
your terminal.
|
||||
</div>
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* Display the current value of the `query` model by adding a `{{query}}` binding into the
|
||||
### Display Current Query
|
||||
Display the current value of the `query` model by adding a `{{query}}` binding into the
|
||||
`index.html` template, and see how it changes when you type in the input box.
|
||||
|
||||
* Let's see how we can get the current value of the `query` model to appear in the HTML page title.
|
||||
### Display Query in Title
|
||||
Let's see how we can get the current value of the `query` model to appear in the HTML page title.
|
||||
|
||||
You might think you could just add the `{{query}}` to the title tag element as follows:
|
||||
* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`:
|
||||
|
||||
<title>Google Phone Gallery: {{query}}</title>
|
||||
```js
|
||||
it('should display the current filter value in the title bar', function() {
|
||||
|
||||
expect(browser.getTitle()).toMatch(/Google Phone Gallery:\s*$/);
|
||||
|
||||
element(by.model('query')).sendKeys('nexus');
|
||||
|
||||
expect(browser.getTitle()).toMatch(/Google Phone Gallery: nexus$/);
|
||||
});
|
||||
```
|
||||
|
||||
Run protractor (`npm run protractor`) to see this test fail.
|
||||
|
||||
|
||||
* You might think you could just add the `{{query}}` to the title tag element as follows:
|
||||
|
||||
<title>Google Phone Gallery: {{query}}</title>
|
||||
|
||||
However, when you reload the page, you won't see the expected result. This is because the "query"
|
||||
model lives in the scope, defined by the `ng-controller="PhoneListCtrl"` directive, on the body element:
|
||||
model lives in the scope, defined by the `ng-controller="PhoneListCtrl"` directive, on the body
|
||||
element:
|
||||
|
||||
<body ng-controller="PhoneListCtrl">
|
||||
|
||||
If you want to bind to the query model from the `<title>` element, you must __move__ the
|
||||
`ngController` declaration to the HTML element because it is the common parent of both the body
|
||||
and title elements:
|
||||
`ngController` declaration to the HTML element because it is the common parent of both the body
|
||||
and title elements:
|
||||
|
||||
<html ng-app="phonecatApp" ng-controller="PhoneListCtrl">
|
||||
|
||||
Be sure to __remove__ the `ng-controller` declaration from the body element.
|
||||
|
||||
While using double curlies works fine within the title element, you might have noticed that
|
||||
* Re-run `npm run protractor` to see the test now pass.
|
||||
|
||||
* While using double curlies works fine within the title element, you might have noticed that
|
||||
for a split second they are actually displayed to the user while the page is loading. A better
|
||||
solution would be to use the {@link ng.directive:ngBind
|
||||
ngBind} or {@link ng.directive:ngBindTemplate
|
||||
ngBindTemplate} directives, which are invisible to the user while the page is loading:
|
||||
solution would be to use the {@link ng.directive:ngBind ngBind} or
|
||||
{@link ng.directive:ngBindTemplate ngBindTemplate} directives, which are invisible to the user
|
||||
while the page is loading:
|
||||
|
||||
<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
|
||||
|
||||
* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`:
|
||||
|
||||
```js
|
||||
it('should display the current filter value within an element with id "status"',
|
||||
function() {
|
||||
expect(element('#status').text()).toMatch(/Current filter: \s*$/);
|
||||
|
||||
input('query').enter('nexus');
|
||||
|
||||
expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);
|
||||
|
||||
//alternative version of the last assertion that tests just the value of the binding
|
||||
using('#status').expect(binding('query')).toBe('nexus');
|
||||
});
|
||||
```
|
||||
|
||||
Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test
|
||||
pass, edit the `index.html` template to add a `div` or `p` element with `id` `"status"` and content
|
||||
with the `query` binding, prefixed by "Current filter:". For instance:
|
||||
|
||||
<div id="status">Current filter: {{query}}</div>
|
||||
|
||||
* Add a `pause()` statement inside of an end-to-end test and rerun it. You'll see the runner pause;
|
||||
this gives you the opportunity to explore the state of your application while it is displayed in
|
||||
the browser. The app is live! You can change the search query to prove it. Notice how useful this
|
||||
is for troubleshooting end-to-end tests.
|
||||
<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
@@ -10,17 +10,13 @@ In this step, you will add a feature to let your users control the order of the
|
||||
list. The dynamic ordering is implemented by creating a new model property, wiring it together with
|
||||
the repeater, and letting the data binding magic do the rest of the work.
|
||||
|
||||
* In addition to the search box, the app displays a drop down menu that allows users to control the
|
||||
order in which the phones are listed.
|
||||
|
||||
|
||||
<div doc-tutorial-reset="4"></div>
|
||||
|
||||
|
||||
You should see that in addition to the search box, the app displays a drop down menu that allows
|
||||
users to control the order in which the phones are listed.
|
||||
|
||||
The most important differences between Steps 3 and 4 are listed below. You can see the full diff on
|
||||
[GitHub](https://github.com/angular/angular-phonecat/compare/step-3...step-4):
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
__`app/index.html`:__
|
||||
@@ -36,7 +32,7 @@ __`app/index.html`:__
|
||||
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
|
||||
{{phone.name}}
|
||||
<span>{{phone.name}}</span>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -91,7 +87,7 @@ phonecatApp.controller('PhoneListCtrl', function ($scope) {
|
||||
record. This property is used to order phones by age.
|
||||
|
||||
* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had
|
||||
not set a default value here, the `orderBy` filter would remain uninitialized until our
|
||||
not set a default value here, the `orderBy` filter would remain uninitialized until our
|
||||
user picked an option from the drop down menu.
|
||||
|
||||
This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the
|
||||
@@ -117,7 +113,7 @@ describe('PhoneCat controllers', function() {
|
||||
var scope, ctrl;
|
||||
|
||||
beforeEach(module('phonecatApp'));
|
||||
|
||||
|
||||
beforeEach(inject(function($controller) {
|
||||
scope = {};
|
||||
ctrl = $controller('PhoneListCtrl', {$scope:scope});
|
||||
@@ -143,7 +139,7 @@ shared by all tests in the parent `describe` block.
|
||||
|
||||
You should now see the following output in the Karma tab:
|
||||
|
||||
Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)
|
||||
<pre>Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)</pre>
|
||||
|
||||
|
||||
Let's turn our attention to the end-to-end test.
|
||||
@@ -152,28 +148,36 @@ __`test/e2e/scenarios.js`:__
|
||||
|
||||
```js
|
||||
...
|
||||
it('should be possible to control phone order via the drop down select box',
|
||||
function() {
|
||||
//let's narrow the dataset to make the test assertions shorter
|
||||
input('query').enter('tablet');
|
||||
it('should be possible to control phone order via the drop down select box', function() {
|
||||
|
||||
expect(repeater('.phones li', 'Phone List').column('phone.name')).
|
||||
toEqual(["Motorola XOOM\u2122 with Wi-Fi",
|
||||
"MOTOROLA XOOM\u2122"]);
|
||||
var phoneNameColumn = element.all(by.repeater('phone in phones').column('{{phone.name}}'));
|
||||
var query = element(by.model('query'));
|
||||
|
||||
select('orderProp').option('Alphabetical');
|
||||
function getNames() {
|
||||
return phoneNameColumn.map(function(elm) {
|
||||
return elm.getText();
|
||||
});
|
||||
}
|
||||
|
||||
expect(repeater('.phones li', 'Phone List').column('phone.name')).
|
||||
toEqual(["MOTOROLA XOOM\u2122",
|
||||
"Motorola XOOM\u2122 with Wi-Fi"]);
|
||||
});
|
||||
...
|
||||
query.sendKeys('tablet'); //let's narrow the dataset to make the test assertions shorter
|
||||
|
||||
expect(getNames()).toEqual([
|
||||
"Motorola XOOM\u2122 with Wi-Fi",
|
||||
"MOTOROLA XOOM\u2122"
|
||||
]);
|
||||
|
||||
element(by.model('orderProp')).element(by.css('option[value="name"]')).click();
|
||||
|
||||
expect(getNames()).toEqual([
|
||||
"MOTOROLA XOOM\u2122",
|
||||
"Motorola XOOM\u2122 with Wi-Fi"
|
||||
]);
|
||||
});...
|
||||
```
|
||||
|
||||
The end-to-end test verifies that the ordering mechanism of the select box is working correctly.
|
||||
|
||||
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
|
||||
`runner.html` to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html).
|
||||
You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
# Experiments
|
||||
|
||||
|
||||
@@ -7,20 +7,16 @@
|
||||
|
||||
|
||||
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 Angular's built-in {@link guide/dev_guide.services services} called {@link
|
||||
from our server using one of Angular's built-in {@link guide/services services} called {@link
|
||||
ng.$http $http}. We will use Angular's {@link guide/di dependency
|
||||
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
|
||||
|
||||
* There are now a list of 20 phones, loaded from the server.
|
||||
|
||||
<div doc-tutorial-reset="5"></div>
|
||||
|
||||
|
||||
You should now see a list of 20 phones.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-4...step-5):
|
||||
|
||||
## Data
|
||||
|
||||
The `app/phones/phones.json` file in your project is a dataset that contains a larger list of phones
|
||||
stored in the JSON format.
|
||||
|
||||
@@ -44,7 +40,7 @@ Following is a sample of the file:
|
||||
|
||||
We'll use Angular's {@link ng.$http $http} service in our controller to make an HTTP
|
||||
request to your web server to fetch the data in the `app/phones/phones.json` file. `$http` is just
|
||||
one of several built-in {@link guide/dev_guide.services angular services} that handle common operations
|
||||
one of several built-in {@link guide/services Angular services} that handle common operations
|
||||
in web apps. Angular injects these services for you where you need them.
|
||||
|
||||
Services are managed by Angular's {@link guide/di DI subsystem}. Dependency injection
|
||||
@@ -74,10 +70,10 @@ tutorial.)
|
||||
|
||||
The `$http` service returns a {@link ng.$q promise object} with a `success`
|
||||
method. We call this method to handle the asynchronous response and assign the phone data to the
|
||||
scope controlled by this controller, as a model called `phones`. Notice that angular detected the
|
||||
scope controlled by this controller, as a model called `phones`. Notice that Angular detected the
|
||||
json response and parsed it for us!
|
||||
|
||||
To use a service in angular, you simply declare the names of the dependencies you need as arguments
|
||||
To use a service in Angular, you simply declare the names of the dependencies you need as arguments
|
||||
to the controller's constructor function, as follows:
|
||||
|
||||
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}
|
||||
@@ -90,13 +86,13 @@ Note that the names of arguments are significant, because the injector uses thes
|
||||
dependencies.
|
||||
|
||||
|
||||
<img class="diagram" src="img/tutorial/xhr_service_final.png">
|
||||
<img class="diagram" src="img/tutorial/tutorial_05.png">
|
||||
|
||||
|
||||
### `$` Prefix Naming Convention
|
||||
|
||||
You can create your own services, and in fact we will do exactly that in step 11. As a naming
|
||||
convention, angular's built-in services, Scope methods and a few other Angular APIs have a `$`
|
||||
convention, Angular's built-in services, Scope methods and a few other Angular APIs have a `$`
|
||||
prefix in front of the name.
|
||||
|
||||
The `$` prefix is there to namespace Angular-provided services.
|
||||
@@ -109,28 +105,30 @@ properties are considered private, and should not be accessed or modified.
|
||||
### A Note on Minification
|
||||
|
||||
Since Angular infers the controller's dependencies from the names of arguments to the controller's
|
||||
constructor function, if you were to [minify](http://goo.gl/SAnnsm) the JavaScript code for `PhoneListCtrl` controller, all of its function arguments would be
|
||||
minified as well, and the dependency injector would not be able to identify services correctly.
|
||||
constructor function, if you were to [minify](http://goo.gl/SAnnsm) the JavaScript code for
|
||||
`PhoneListCtrl` controller, all of its function arguments would be minified as well, and the
|
||||
dependency injector would not be able to identify services correctly.
|
||||
|
||||
There are two ways to overcome issues caused by minification:
|
||||
We can overcome this problem by annotating the function with the names of the dependencies, provided
|
||||
as strings, which will not get minified. There are two ways to provide these injection annotations:
|
||||
|
||||
* You can create a `$inject` property on the controller function which holds an array of strings.
|
||||
* Create a `$inject` property on the controller function which holds an array of strings.
|
||||
Each string in the array is the name of the service to inject for the corresponding parameter.
|
||||
In the case of our example we would write:
|
||||
In our example we would write:
|
||||
|
||||
```js
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
PhoneListCtrl.$inject = ['$scope', '$http'];
|
||||
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
|
||||
```
|
||||
```js
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
PhoneListCtrl.$inject = ['$scope', '$http'];
|
||||
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
|
||||
```
|
||||
|
||||
* Use the inline bracket notation which wraps the function to be injected into an array of strings
|
||||
(representing the dependency names) followed by the function to be injected:
|
||||
* Use an inline annotation where, instead of just providing the function, you provide an array.
|
||||
This array contains a list of the service names, followed by the function itself.
|
||||
|
||||
```js
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
|
||||
```
|
||||
```js
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
|
||||
```
|
||||
|
||||
Both of these methods work with any function that can be injected by Angular, so it's up to your
|
||||
project's style guide to decide which one you use.
|
||||
@@ -166,9 +164,9 @@ __`test/unit/controllersSpec.js`:__
|
||||
|
||||
Because we started using dependency injection and our controller has dependencies, constructing the
|
||||
controller in our tests is a bit more complicated. We could use the `new` operator and provide the
|
||||
constructor with some kind of fake `$http` implementation. However, the recommended (and easier) way
|
||||
is to create a controller in the test environment in the same way that angular does it in the
|
||||
production code behind the scenes, as follows:
|
||||
constructor with some kind of fake `$http` implementation. However, Angular provides a mock `$http`
|
||||
service that we can use in unit tests. We configure "fake" responses to server requests by calling
|
||||
methods on a service called $httpBackend:
|
||||
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
@@ -181,7 +179,7 @@ describe('PhoneCat controllers', function() {
|
||||
|
||||
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
|
||||
// This allows us to inject a service but then attach it to a variable
|
||||
// with the same name as the service.
|
||||
// with the same name as the service in order to avoid a name conflict.
|
||||
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpBackend.expectGET('phones/phones.json').
|
||||
@@ -252,13 +250,13 @@ Finally, we verify that the default value of `orderProp` is set correctly:
|
||||
|
||||
You should now see the following output in the Karma tab:
|
||||
|
||||
Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)
|
||||
<pre>Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)</pre>
|
||||
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* At the bottom of `index.html`, add a `{{phones | json}}` binding to see the list of phones
|
||||
* At the bottom of `index.html`, add a `<pre>{{phones | json}}</pre>` binding to see the list of phones
|
||||
displayed in json format.
|
||||
|
||||
* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones
|
||||
@@ -269,7 +267,7 @@ to the first 5 in the list. Use the following code in the `$http` callback:
|
||||
|
||||
# Summary
|
||||
|
||||
Now that you have learned how easy it is to use angular services (thanks to Angular's dependency
|
||||
Now that you have learned how easy it is to use Angular services (thanks to Angular's dependency
|
||||
injection), go to {@link step_06 step 6}, where you will add some
|
||||
thumbnail images of phones and some links.
|
||||
|
||||
|
||||
@@ -10,15 +10,10 @@ In this step, you will add thumbnail images for the phones in the phone list, an
|
||||
now, will go nowhere. In subsequent steps you will use the links to display additional information
|
||||
about the phones in the catalog.
|
||||
|
||||
* There are now links and images of the phones in the list.
|
||||
|
||||
<div doc-tutorial-reset="6"></div>
|
||||
|
||||
|
||||
You should now see links and images of the phones in the list.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-5...step-6):
|
||||
|
||||
|
||||
## Data
|
||||
|
||||
Note that the `phones.json` file contains unique ids and image urls for each of the phones. The
|
||||
@@ -63,7 +58,7 @@ the element attribute.
|
||||
|
||||
We also added phone images next to each record using an image tag with the {@link
|
||||
ng.directive:ngSrc ngSrc} directive. That directive prevents the
|
||||
browser from treating the angular `{{ expression }}` markup literally, and initiating a request to
|
||||
browser from treating the Angular `{{ expression }}` markup literally, and initiating a request to
|
||||
invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
|
||||
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.
|
||||
@@ -76,9 +71,12 @@ __`test/e2e/scenarios.js`__:
|
||||
```js
|
||||
...
|
||||
it('should render phone specific links', function() {
|
||||
input('query').enter('nexus');
|
||||
element('.phones li a').click();
|
||||
expect(browser().location().url()).toBe('/phones/nexus-s');
|
||||
var query = element(by.model('query'));
|
||||
query.sendKeys('nexus');
|
||||
element(by.css('.phones li a')).click();
|
||||
browser.getLocationAbsUrl().then(function(url) {
|
||||
expect(url.split('#')[1]).toBe('/phones/nexus-s');
|
||||
});
|
||||
});
|
||||
...
|
||||
```
|
||||
@@ -86,8 +84,7 @@ __`test/e2e/scenarios.js`__:
|
||||
We added a new end-to-end test to verify that the app is generating correct links to the phone
|
||||
views that we will implement in the upcoming steps.
|
||||
|
||||
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
|
||||
runner to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-6/test/e2e/runner.html).
|
||||
You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
+195
-138
@@ -7,18 +7,50 @@
|
||||
|
||||
|
||||
In this step, you will learn how to create a layout template and how to build an app that has
|
||||
multiple views by adding routing.
|
||||
multiple views by adding routing, using an Angular module called 'ngRoute'.
|
||||
|
||||
* When you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones`
|
||||
and the phone list appears in the browser.
|
||||
* When you click on a phone link the url changes to one specific to that phone and the stub of a
|
||||
phone detail page is displayed.
|
||||
|
||||
<div doc-tutorial-reset="7"></div>
|
||||
|
||||
## Dependencies
|
||||
|
||||
Note that when you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones`
|
||||
and the same phone list appears in the browser. When you click on a phone link the stub of a phone
|
||||
detail page is displayed.
|
||||
The routing functionality added by this step is provided by angular in the `ngRoute` module, which
|
||||
is distributed separately from the core Angular framework.
|
||||
|
||||
We are using [Bower][bower] to install client side dependencies. This step updates the
|
||||
`bower.json` configuration file to include the new dependency:
|
||||
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-6...step-7).
|
||||
```json
|
||||
{
|
||||
"name": "angular-seed",
|
||||
"description": "A starter project for AngularJS",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/angular/angular-seed",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "1.2.x",
|
||||
"angular-mocks": "~1.2.x",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.2.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-route": "~1.2.x"` tells bower to install a version of the
|
||||
angular-route component that is compatible with version 1.2.x. We must tell bower to download
|
||||
and install this dependency.
|
||||
|
||||
If you have bower installed globally then you can run `bower install` but for this project we have
|
||||
preconfigured npm to run bower install for us:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
|
||||
## Multiple Views, Routing and Layout Template
|
||||
@@ -34,12 +66,11 @@ template into what we call a "layout template". This is a template that is commo
|
||||
our application. Other "partial templates" are then included into this layout template depending on
|
||||
the current "route" — the view that is currently displayed to the user.
|
||||
|
||||
Application routes in Angular are declared via the
|
||||
{@link ngRoute.$routeProvider $routeProvider}, which is the provider of the
|
||||
{@link ngRoute.$route $route service}. This service makes it easy to wire together
|
||||
controllers, view templates, and the current
|
||||
URL location in the browser. Using this feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us utilize the browser's
|
||||
history (back and forward navigation) and bookmarks.
|
||||
Application routes in Angular are declared via the {@link ngRoute.$routeProvider $routeProvider},
|
||||
which is the provider of the {@link ngRoute.$route $route service}. This service makes it easy to
|
||||
wire together controllers, view templates, and the current URL location in the browser. Using this
|
||||
feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
|
||||
utilize the browser's history (back and forward navigation) and bookmarks.
|
||||
|
||||
|
||||
### A Note About DI, Injector and Providers
|
||||
@@ -47,12 +78,17 @@ history (back and forward navigation) and bookmarks.
|
||||
As you {@link tutorial/step_05 noticed}, {@link guide/di dependency injection} (DI) is at the core of
|
||||
AngularJS, so it's important for you to understand a thing or two about how it works.
|
||||
|
||||
When the application bootstraps, Angular creates an injector that will be used for all DI stuff in
|
||||
this app. The injector itself doesn't know anything about what `$http` or `$route` services do, in
|
||||
fact it doesn't even know about the existence of these services unless it is configured with proper
|
||||
module definitions. The sole responsibilities of the injector are to load specified module
|
||||
definition(s), register all service providers defined in these modules, and when asked, inject
|
||||
a specified function with dependencies (services) that it lazily instantiates via their providers.
|
||||
When the application bootstraps, Angular creates an injector that will be used to find and inject all
|
||||
of the services that are required by your app. The injector itself doesn't know anything about what
|
||||
`$http` or `$route` services do, in fact it doesn't even know about the existence of these services
|
||||
unless it is configured with proper module definitions.
|
||||
|
||||
The injector only carries out the following steps :
|
||||
|
||||
* load the module definition(s) that you specify in your app
|
||||
* register all Providers defined in these module definitions
|
||||
* when asked to do so, inject a specified function and any necessary dependencies (services) that
|
||||
it lazily instantiates via their Providers.
|
||||
|
||||
Providers are objects that provide (create) instances of services and expose configuration APIs
|
||||
that can be used to control the creation and runtime behavior of a service. In case of the `$route`
|
||||
@@ -66,109 +102,10 @@ service, the `$routeProvider` exposes APIs that allow you to define routes for y
|
||||
Angular modules solve the problem of removing global state from the application and provide a way
|
||||
of configuring the injector. As opposed to AMD or require.js modules, Angular 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 fulfil their goals.
|
||||
both module systems can live side by side and fulfill their goals.
|
||||
|
||||
## The App Module
|
||||
|
||||
__`app/js/app.js`:__
|
||||
|
||||
```js
|
||||
var phonecatApp = angular.module('phonecatApp', [
|
||||
'ngRoute',
|
||||
'phonecatControllers'
|
||||
]);
|
||||
|
||||
phonecatApp.config(['$routeProvider',
|
||||
function($routeProvider) {
|
||||
$routeProvider.
|
||||
when('/phones', {
|
||||
templateUrl: 'partials/phone-list.html',
|
||||
controller: 'PhoneListCtrl'
|
||||
}).
|
||||
when('/phones/:phoneId', {
|
||||
templateUrl: 'partials/phone-detail.html',
|
||||
controller: 'PhoneDetailCtrl'
|
||||
}).
|
||||
otherwise({
|
||||
redirectTo: '/phones'
|
||||
});
|
||||
}]);
|
||||
```
|
||||
|
||||
In order to configure our application with routes, we need to create a module for our application.
|
||||
We call this module `phonecatApp`. Notice the second argument passed to `angular.module`:
|
||||
`['ngRoute', 'phonecatControllers']`. This array lists the modules that `phonecatApp` depends on.
|
||||
|
||||
Above, we added `angular-route.js` to `index.html`. That's not all we need to do to be able to use
|
||||
it, however. We also have to add `ngRoute` as a dependency of our app. To improve the organization
|
||||
of the app, we're going to move the controllers into their own file (as shown below), and call the
|
||||
module `phonecatControllers`. By listing these two modules as dependencies of `phonecatApp`, we
|
||||
can use the directives and services they provide.
|
||||
|
||||
Thus using the `config` API we request the `$routeProvider` to be injected into our config function
|
||||
and use the {@link ngRoute.$routeProvider#when `$routeProvider.when`} API to define our routes.
|
||||
|
||||
Our application routes are defined as follows:
|
||||
|
||||
* The phone list view will be shown when the URL hash fragment is `/phones`. To construct this
|
||||
view, Angular will use the `phone-list.html` template and the `PhoneListCtrl` controller.
|
||||
|
||||
* The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where
|
||||
`:phoneId` is a variable part of the URL. To construct the phone details view, angular will use the
|
||||
`phone-detail.html` template and the `PhoneDetailCtrl` controller.
|
||||
|
||||
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
|
||||
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
|
||||
|
||||
`$routeProvider.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when the browser
|
||||
address doesn't match either of our routes.
|
||||
|
||||
Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses
|
||||
the route declaration — `'/phones/:phoneId'` — as a template that is matched against the current
|
||||
URL. All variables defined with the `:` notation are extracted into the
|
||||
{@link ngRoute.$routeParams `$routeParams`} object.
|
||||
|
||||
|
||||
In order for our application to bootstrap with our newly created module we'll also need to specify
|
||||
the module name as the value of the {@link ng.directive:ngApp ngApp}
|
||||
directive:
|
||||
|
||||
__`app/index.html`:__
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en" ng-app="phonecatApp">
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Controllers
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
|
||||
```js
|
||||
var phonecatControllers = angular.module('phonecatControllers', []);
|
||||
|
||||
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
|
||||
function ($scope, $http) {
|
||||
$http.get('phones/phones.json').success(function(data) {
|
||||
$scope.phones = data;
|
||||
});
|
||||
|
||||
$scope.orderProp = 'age';
|
||||
}]);
|
||||
|
||||
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
|
||||
function($scope, $routeParams) {
|
||||
$scope.phoneId = $routeParams.phoneId;
|
||||
}]);
|
||||
```
|
||||
|
||||
Again, note that we created a new module called `phonecatControllers`. For small AngularJS applications,
|
||||
it's common to create just one module for all of your controllers if there are just a few. For larger apps,
|
||||
you will probably want to create separate modules for each major feature of your app.
|
||||
|
||||
Because our example app is relatively small, we'll add all of our controllers to this module.
|
||||
To deepen your understanding of DI on Angular, see
|
||||
[Understanding Dependency Injection](https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection).
|
||||
|
||||
## Template
|
||||
|
||||
@@ -177,9 +114,8 @@ ngView} directive. The role of the `ngView` directive is to include the view tem
|
||||
route into the layout template. This makes it a perfect fit for our `index.html` template.
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Note:** Starting with AngularJS version 1.2, `ngRoute` is in its own module and must be loaded by loading
|
||||
the `angular-route.js` file distributed with Angular. The easiest way to load the file is to add a `<script>`
|
||||
tag to your `index.html` file as shown below.
|
||||
**Note:** Starting with AngularJS version 1.2, `ngRoute` is in its own module and must be loaded by
|
||||
loading the additional `angular-route.js` file, which we download via Bower above.
|
||||
</div>
|
||||
|
||||
__`app/index.html`:__
|
||||
@@ -189,8 +125,8 @@ __`app/index.html`:__
|
||||
<html lang="en" ng-app="phonecatApp">
|
||||
<head>
|
||||
...
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular-route.js"></script>
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="bower_components/angular-route/angular-route.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/controllers.js"></script>
|
||||
</head>
|
||||
@@ -202,6 +138,12 @@ __`app/index.html`:__
|
||||
</html>
|
||||
```
|
||||
|
||||
We have added two new `<script>` tags in our index file to load up extra JavaScript files into our
|
||||
application:
|
||||
|
||||
- `angular-route.js` : defines the Angular `ngRoute` module, which provides us with routing.
|
||||
- `app.js` : this file now holds the root module of our application.
|
||||
|
||||
Note that we removed most of the code in the `index.html` template and replaced it with a single
|
||||
line containing a div with the `ng-view` attribute. The code that we removed was placed into the
|
||||
`phone-list.html` template:
|
||||
@@ -210,8 +152,8 @@ __`app/partials/phone-list.html`:__
|
||||
|
||||
```html
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<!--Sidebar content-->
|
||||
|
||||
Search: <input ng-model="query">
|
||||
@@ -222,7 +164,7 @@ __`app/partials/phone-list.html`:__
|
||||
</select>
|
||||
|
||||
</div>
|
||||
<div class="span10">
|
||||
<div class="col-md-10">
|
||||
<!--Body content-->
|
||||
|
||||
<ul class="phones">
|
||||
@@ -248,10 +190,116 @@ We also added a placeholder template for the phone details view:
|
||||
__`app/partials/phone-detail.html`:__
|
||||
|
||||
```html
|
||||
TBD: detail view for {{phoneId}}
|
||||
TBD: detail view for <span>{{phoneId}}</span>
|
||||
```
|
||||
|
||||
Note how we are using `phoneId` model defined in the `PhoneDetailCtrl` controller.
|
||||
Note how we are using the `phoneId` expression which will be defined in the `PhoneDetailCtrl` controller.
|
||||
|
||||
## The App Module
|
||||
|
||||
To improve the organization of the app, we are making use of Angular's `ngRoute` module and we've
|
||||
moved the controllers into their own module `phonecatControllers` (as shown below).
|
||||
|
||||
We added `angular-route.js` to `index.html` and created a new `phonecatControllers` module in
|
||||
`controllers.js`. That's not all we need to do to be able to use their code, however. We also have
|
||||
to add the modules dependencies of our app. By listing these two modules as dependencies of
|
||||
`phonecatApp`, we can use the directives and services they provide.
|
||||
|
||||
|
||||
__`app/js/app.js`:__
|
||||
|
||||
```js
|
||||
var phonecatApp = angular.module('phonecatApp', [
|
||||
'ngRoute',
|
||||
'phonecatControllers'
|
||||
]);
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Notice the second argument passed to `angular.module`, `['ngRoute', 'phonecatControllers']`. This
|
||||
array lists the modules that `phonecatApp` depends on.
|
||||
|
||||
|
||||
```js
|
||||
...
|
||||
|
||||
phonecatApp.config(['$routeProvider',
|
||||
function($routeProvider) {
|
||||
$routeProvider.
|
||||
when('/phones', {
|
||||
templateUrl: 'partials/phone-list.html',
|
||||
controller: 'PhoneListCtrl'
|
||||
}).
|
||||
when('/phones/:phoneId', {
|
||||
templateUrl: 'partials/phone-detail.html',
|
||||
controller: 'PhoneDetailCtrl'
|
||||
}).
|
||||
otherwise({
|
||||
redirectTo: '/phones'
|
||||
});
|
||||
}]);
|
||||
```
|
||||
|
||||
Using the `phonecatApp.config()` method, we request the `$routeProvider` to be injected into our
|
||||
config function and use the {@link ngRoute.$routeProvider#when `$routeProvider.when()`} method to
|
||||
define our routes.
|
||||
|
||||
Our application routes are defined as follows:
|
||||
|
||||
* `when('/phones')`: The phone list view will be shown when the URL hash fragment is `/phones`. To
|
||||
construct this view, Angular will use the `phone-list.html` template and the `PhoneListCtrl`
|
||||
controller.
|
||||
|
||||
* `when('/phones/:phoneId')`: The phone details view will be shown when the URL hash fragment
|
||||
matches '/phone/:phoneId', where `:phoneId` is a variable part of the URL. To construct the phone
|
||||
details view, Angular will use the `phone-detail.html` template and the `PhoneDetailCtrl`
|
||||
controller.
|
||||
|
||||
* `otherwise({redirectTo: '/phones'})`: triggers a redirection to `/phones` when the browser
|
||||
address doesn't match either of our routes.
|
||||
|
||||
|
||||
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
|
||||
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
|
||||
|
||||
|
||||
Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses
|
||||
the route declaration — `'/phones/:phoneId'` — as a template that is matched against the current
|
||||
URL. All variables defined with the `:` notation are extracted into the
|
||||
{@link ngRoute.$routeParams `$routeParams`} object.
|
||||
|
||||
|
||||
## Controllers
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
|
||||
```js
|
||||
var phonecatControllers = angular.module('phonecatControllers', []);
|
||||
|
||||
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
|
||||
function ($scope, $http) {
|
||||
$http.get('phones/phones.json').success(function(data) {
|
||||
$scope.phones = data;
|
||||
});
|
||||
|
||||
$scope.orderProp = 'age';
|
||||
}]);
|
||||
|
||||
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
|
||||
function($scope, $routeParams) {
|
||||
$scope.phoneId = $routeParams.phoneId;
|
||||
}]);
|
||||
```
|
||||
|
||||
Again, note that we created a new module called `phonecatControllers`. For small AngularJS
|
||||
applications, it's common to create just one module for all of your controllers if there are just a
|
||||
few. As your application grows it is quite common to refactor your code into additional modules.
|
||||
For larger apps, you will probably want to create separate modules for each major feature of
|
||||
your app.
|
||||
|
||||
Because our example app is relatively small, we'll just add all of our controllers to the
|
||||
`phonecatControllers` module.
|
||||
|
||||
|
||||
## Test
|
||||
@@ -261,28 +309,34 @@ to various URLs and verify that the correct view was rendered.
|
||||
|
||||
```js
|
||||
...
|
||||
it('should redirect index.html to index.html#/phones', function() {
|
||||
browser().navigateTo('app/index.html');
|
||||
expect(browser().location().url()).toBe('/phones');
|
||||
it('should redirect index.html to index.html#/phones', function() {
|
||||
browser.get('app/index.html');
|
||||
browser.getLocationAbsUrl().then(function(url) {
|
||||
expect(url.split('#')[1]).toBe('/phones');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Phone list view', function() {
|
||||
beforeEach(function() {
|
||||
browser.get('app/index.html#/phones');
|
||||
});
|
||||
...
|
||||
|
||||
describe('Phone detail view', function() {
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('app/index.html#/phones/nexus-s');
|
||||
browser.get('app/index.html#/phones/nexus-s');
|
||||
});
|
||||
|
||||
|
||||
it('should display placeholder page with phoneId', function() {
|
||||
expect(binding('phoneId')).toBe('nexus-s');
|
||||
expect(element(by.binding('phoneId')).getText()).toBe('nexus-s');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
|
||||
runner to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-7/test/e2e/runner.html).
|
||||
You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
|
||||
# Experiments
|
||||
@@ -308,3 +362,6 @@ step 8} to implement the phone details view.
|
||||
|
||||
|
||||
<ul doc-tutorial-nav="7"></ul>
|
||||
|
||||
|
||||
[bower]: http://bower.io
|
||||
|
||||
@@ -9,18 +9,15 @@
|
||||
In this step, you will implement the phone details view, which is displayed when a user clicks on a
|
||||
phone in the phone list.
|
||||
|
||||
* When you click on a phone on the list, the phone details page with phone-specific information
|
||||
is displayed.
|
||||
|
||||
To implement the phone details view we used {@link ng.$http $http} to fetch our data, and we
|
||||
fleshed out the `phone-detail.html` view template.
|
||||
|
||||
<div doc-tutorial-reset="8"></div>
|
||||
|
||||
|
||||
Now when you click on a phone on the list, the phone details page with phone-specific information
|
||||
is displayed.
|
||||
|
||||
To implement the phone details view we will use {@link ng.$http $http} to fetch
|
||||
our data, and we'll flesh out the `phone-detail.html` view template.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-7...step-8):
|
||||
|
||||
## Data
|
||||
|
||||
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
|
||||
@@ -79,7 +76,7 @@ route by the `$route` service.
|
||||
## Template
|
||||
|
||||
The TBD placeholder line has been replaced with lists and bindings that comprise the phone details.
|
||||
Note where we use the angular `{{expression}}` markup and `ngRepeat` to project phone data from
|
||||
Note where we use the Angular `{{expression}}` markup and `ngRepeat` to project phone data from
|
||||
our model into the view.
|
||||
|
||||
|
||||
@@ -107,7 +104,7 @@ __`app/partials/phone-detail.html`:__
|
||||
</dl>
|
||||
</li>
|
||||
...
|
||||
</li>
|
||||
<li>
|
||||
<span>Additional Features</span>
|
||||
<dd>{{phone.additionalFeatures}}</dd>
|
||||
</li>
|
||||
@@ -127,7 +124,11 @@ step 5.
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
|
||||
```js
|
||||
...
|
||||
|
||||
beforeEach(module('phonecatApp'));
|
||||
|
||||
...
|
||||
|
||||
describe('PhoneDetailCtrl', function(){
|
||||
var scope, $httpBackend, ctrl;
|
||||
|
||||
@@ -153,7 +154,7 @@ __`test/unit/controllersSpec.js`:__
|
||||
|
||||
You should now see the following output in the Karma tab:
|
||||
|
||||
Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)
|
||||
<pre>Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)</pre>
|
||||
|
||||
|
||||
We also added a new end-to-end test that navigates to the Nexus S detail page and verifies that the
|
||||
@@ -166,26 +167,25 @@ __`test/e2e/scenarios.js`:__
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../../app/index.html#/phones/nexus-s');
|
||||
browser.get('app/index.html#/phones/nexus-s');
|
||||
});
|
||||
|
||||
|
||||
it('should display nexus-s page', function() {
|
||||
expect(binding('phone.name')).toBe('Nexus S');
|
||||
expect(element(by.binding('phone.name')).getText()).toBe('Nexus S');
|
||||
});
|
||||
});
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
|
||||
runner to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html).
|
||||
You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* Using the {@link guide/dev_guide.e2e-testing Angular's end-to-end test runner API}, write a test
|
||||
that verifies that we display 4 thumbnail images on the Nexus S details page.
|
||||
* Using the [Protractor API](https://github.com/angular/protractor/blob/master/docs/api.md),
|
||||
write a test that verifies that we display 4 thumbnail images on the Nexus S details page.
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
@@ -5,20 +5,13 @@
|
||||
|
||||
<ul doc-tutorial-nav="9"></ul>
|
||||
|
||||
|
||||
In this step you will learn how to create your own custom display filter.
|
||||
|
||||
|
||||
<div doc-tutorial-reset="9"></div>
|
||||
|
||||
|
||||
Navigate to one of the detail pages.
|
||||
|
||||
In the previous step, the details page displayed either "true" or "false" to indicate whether
|
||||
* In the previous step, the details page displayed either "true" or "false" to indicate whether
|
||||
certain phone features were present or not. We have used a custom filter to convert those text
|
||||
strings into glyphs: ✓ for "true", and ✘ for "false". Let's see what the filter code looks like.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-8...step-9):
|
||||
<div doc-tutorial-reset="9"></div>
|
||||
|
||||
|
||||
## Custom Filter
|
||||
@@ -40,13 +33,13 @@ The name of our filter is "checkmark". The `input` evaluates to either `true` or
|
||||
return one of the two unicode characters we have chosen to represent true (`\u2713` -> ✓) or false (`\u2718` -> ✘).
|
||||
|
||||
Now that our filter is ready, we need to register the `phonecatFilters` module as a dependency for
|
||||
our main `phonecat` module.
|
||||
our main `phonecatApp` module.
|
||||
|
||||
__`app/js/app.js`:__
|
||||
|
||||
```js
|
||||
...
|
||||
angular.module('phonecatApp', ['phonecatFilters']).
|
||||
angular.module('phonecatApp', ['ngRoute','phonecatControllers','phonecatFilters']).
|
||||
...
|
||||
```
|
||||
|
||||
@@ -116,9 +109,13 @@ for this test run.
|
||||
Note that we call the helper function, `inject(function(checkmarkFilter) { ... })`, to get
|
||||
access to the filter that we want to test. See {@link angular.mock.inject angular.mock.inject()}.
|
||||
|
||||
Notice that the suffix 'Filter' is appended to your filter name when injected.
|
||||
See the {@link guide/filter#using-filters-in-controllers-services-and-directives Filter Guide}
|
||||
section where this is outlined.
|
||||
|
||||
You should now see the following output in the Karma tab:
|
||||
|
||||
Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)
|
||||
<pre>Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)</pre>
|
||||
|
||||
|
||||
# Experiments
|
||||
@@ -131,9 +128,11 @@ following bindings to `index.html`:
|
||||
* `{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}`
|
||||
|
||||
* We can also create a model with an input element, and combine it with a filtered binding. Add
|
||||
the following to index.html:
|
||||
the following to index.html:
|
||||
|
||||
<input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
|
||||
```html
|
||||
<input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
|
||||
```
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
@@ -8,16 +8,11 @@
|
||||
|
||||
In this step, you will add a clickable phone image swapper to the phone details page.
|
||||
|
||||
|
||||
<div doc-tutorial-reset="10"></div>
|
||||
|
||||
|
||||
The phone details view displays one large image of the current phone and several smaller thumbnail
|
||||
* The phone details view displays one large image of the current phone and several smaller thumbnail
|
||||
images. It would be great if we could replace the large image with any of the thumbnails just by
|
||||
clicking on the desired thumbnail image. Let's have a look at how we can do this with Angular.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-9...step-10):
|
||||
|
||||
<div doc-tutorial-reset="10"></div>
|
||||
|
||||
## Controller
|
||||
|
||||
@@ -90,23 +85,22 @@ __`test/e2e/scenarios.js`:__
|
||||
...
|
||||
|
||||
it('should display the first phone image as the main phone image', function() {
|
||||
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
|
||||
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
|
||||
});
|
||||
|
||||
|
||||
it('should swap main image if a thumbnail image is clicked on', function() {
|
||||
element('.phone-thumbs li:nth-child(3) img').click();
|
||||
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg');
|
||||
element(by.css('.phone-thumbs li:nth-child(3) img')).click();
|
||||
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);
|
||||
|
||||
element('.phone-thumbs li:nth-child(1) img').click();
|
||||
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
|
||||
element(by.css('.phone-thumbs li:nth-child(1) img')).click();
|
||||
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
You can now rerun `./scripts/e2e-test.sh` or refresh the browser tab with the end-to-end test
|
||||
runner to see the tests run, or you can see them running on [Angular's server](http://angular.github.com/angular-phonecat/step-10/test/e2e/runner.html).
|
||||
You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
|
||||
@@ -6,37 +6,74 @@
|
||||
<ul doc-tutorial-nav="11"></ul>
|
||||
|
||||
|
||||
In this step, you will improve the way our app fetches data.
|
||||
In this step, you will change the way our app fetches data.
|
||||
|
||||
* We defined a custom service that represents a [RESTful][restful] client. Using this client we
|
||||
can make requests to the server for data in an easier way, without having to deal with the
|
||||
lower-level {@link ng.$http $http} API, HTTP methods and URLs.
|
||||
|
||||
|
||||
<div doc-tutorial-reset="11"></div>
|
||||
|
||||
## Dependencies
|
||||
|
||||
The next improvement we will make to our app is to define a custom service that represents a [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) client. Using this client we
|
||||
can make XHR requests for data in an easier way, without having to deal with the lower-level {@link
|
||||
ng.$http $http} API, HTTP methods and URLs.
|
||||
The RESTful functionality is provided by Angular in the `ngResource` module, which is distributed
|
||||
separately from the core Angular framework.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-10...step-11):
|
||||
We are using [Bower][bower] to install client side dependencies. This step updates the
|
||||
`bower.json` configuration file to include the new dependency:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "angular-seed",
|
||||
"description": "A starter project for AngularJS",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/angular/angular-seed",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "1.2.x",
|
||||
"angular-mocks": "~1.2.x",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.2.x",
|
||||
"angular-resource": "~1.2.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-resource": "~1.2.x"` tells bower to install a version of the
|
||||
angular-resource component that is compatible with version 1.2.x. We must ask bower to download
|
||||
and install this dependency. We can do this by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
If you have bower installed globally then you can run `bower install` but for this project we have
|
||||
preconfigured `npm install` to run bower for us.
|
||||
</div>
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
The custom service is defined in `app/js/services.js` so we need to include this file in our layout
|
||||
template. Additionally, we also need to load the `angular-resource.js` file, which contains the
|
||||
{@link api/ngResource ngResource} module and in it the {@link api/ngResource.$resource $resource}
|
||||
service, that we'll soon use:
|
||||
Our custom resource service will be defined in `app/js/services.js` so we need to include this file
|
||||
in our layout template. Additionally, we also need to load the `angular-resource.js` file, which
|
||||
contains the {@link api/ngResource ngResource} module:
|
||||
|
||||
__`app/index.html`.__
|
||||
|
||||
```html
|
||||
...
|
||||
<script src="bower_components/angular-resource/angular-resource.js"></script>
|
||||
<script src="js/services.js"></script>
|
||||
<script src="lib/angular/angular-resource.js"></script>
|
||||
...
|
||||
```
|
||||
|
||||
## Service
|
||||
|
||||
We create our own service to provide access to the phone data on the server:
|
||||
|
||||
__`app/js/services.js`.__
|
||||
|
||||
```js
|
||||
@@ -52,13 +89,12 @@ phonecatServices.factory('Phone', ['$resource',
|
||||
|
||||
We used the module API to register a custom service using a factory function. We passed in the name
|
||||
of the service - 'Phone' - and the factory function. The factory function is similar to a
|
||||
controller's constructor in that both can declare dependencies via function arguments. The Phone
|
||||
service declared a dependency on the `$resource` service.
|
||||
controller's constructor in that both can declare dependencies to be injected via function
|
||||
arguments. The Phone service declared a dependency on the `$resource` service.
|
||||
|
||||
The {@link ngResource.$resource `$resource`} service makes it easy to create a
|
||||
[RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) client with just a few
|
||||
lines of code. This client can then be used in our application, instead of the lower-level {@link
|
||||
ng.$http $http} service.
|
||||
[RESTful][restful] client with just a few lines of code. This client can then be used in our
|
||||
application, instead of the lower-level {@link ng.$http $http} service.
|
||||
|
||||
__`app/js/app.js`.__
|
||||
|
||||
@@ -128,6 +164,22 @@ we require, so in these cases, we can add a callback to process the server respo
|
||||
|
||||
## Test
|
||||
|
||||
Because we're now using the {@link ngResource ngResource} module, it's necessary to also need to
|
||||
update the Karma config file with angular-resource so the new tests will pass.
|
||||
|
||||
__`test/karma.conf.js`:__
|
||||
|
||||
```js
|
||||
files : [
|
||||
'app/bower_components/angular/angular.js',
|
||||
'app/bower_components/angular-route/angular-route.js',
|
||||
'app/bower_components/angular-resource/angular-resource.js',
|
||||
'app/bower_components/angular-mocks/angular-mocks.js',
|
||||
'app/js/**/*.js',
|
||||
'test/unit/**/*.js'
|
||||
],
|
||||
```
|
||||
|
||||
We have modified our unit tests to verify that our new service is issuing HTTP requests and
|
||||
processing them as expected. The tests also check that our controllers are interacting with the
|
||||
service correctly.
|
||||
@@ -135,13 +187,13 @@ service correctly.
|
||||
The {@link ngResource.$resource $resource} service augments the response object
|
||||
with methods for updating and deleting the resource. If we were to use the standard `toEqual`
|
||||
matcher, our tests would fail because the test values would not match the responses exactly. To
|
||||
solve the problem, we use a newly-defined `toEqualData` [Jasmine matcher](https://github.com/pivotal/jasmine/wiki/Matchers). When the
|
||||
`toEqualData` matcher compares two objects, it takes only object properties into account and
|
||||
solve the problem, we use a newly-defined `toEqualData` [Jasmine matcher][jasmine-matchers]. When
|
||||
the `toEqualData` matcher compares two objects, it takes only object properties into account and
|
||||
ignores methods.
|
||||
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
|
||||
|
||||
```js
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
@@ -166,12 +218,12 @@ describe('PhoneCat controllers', function() {
|
||||
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
|
||||
|
||||
scope = $rootScope.$new();
|
||||
ctrl = $controller(PhoneListCtrl, {$scope: scope});
|
||||
ctrl = $controller('PhoneListCtrl', {$scope: scope});
|
||||
}));
|
||||
|
||||
|
||||
it('should create "phones" model with 2 phones fetched from xhr', function() {
|
||||
expect(scope.phones).toEqual([]);
|
||||
expect(scope.phones).toEqualData([]);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(scope.phones).toEqualData(
|
||||
@@ -201,7 +253,7 @@ describe('PhoneCat controllers', function() {
|
||||
|
||||
$routeParams.phoneId = 'xyz';
|
||||
scope = $rootScope.$new();
|
||||
ctrl = $controller(PhoneDetailCtrl, {$scope: scope});
|
||||
ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
|
||||
}));
|
||||
|
||||
|
||||
@@ -217,7 +269,7 @@ describe('PhoneCat controllers', function() {
|
||||
|
||||
You should now see the following output in the Karma tab:
|
||||
|
||||
Chrome 22.0: Executed 4 of 4 SUCCESS (0.038 secs / 0.01 secs)
|
||||
<pre>Chrome 22.0: Executed 4 of 4 SUCCESS (0.038 secs / 0.01 secs)</pre>
|
||||
|
||||
|
||||
# Summary
|
||||
@@ -227,3 +279,7 @@ learn how to improve this application with animations.
|
||||
|
||||
|
||||
<ul doc-tutorial-nav="11"></ul>
|
||||
|
||||
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers
|
||||
[bower]: http://bower.io/
|
||||
|
||||
@@ -9,21 +9,60 @@
|
||||
In this final step, we will enhance our phonecat web application by attaching CSS and JavaScript
|
||||
animations on top of the template code we created before.
|
||||
|
||||
* Used the `ngAnimate` to enable animations throughout the application.
|
||||
* Common `ng` directives automatically trigger hooks for animations to tap into.
|
||||
* When an animation is found then the animation will run in between the standard DOM operation that
|
||||
is being issued on the element at the given time (e.g. inserting and removing nodes on
|
||||
{@link api/ng.directive:ngRepeat `ngRepeat`} or adding and removing classes on
|
||||
{@link api/ng.directive:ngClass `ngClass`}).
|
||||
|
||||
<div doc-tutorial-reset="12"></div>
|
||||
|
||||
## Dependencies
|
||||
|
||||
Now that everything is set in place for a fully functional web application, we can attach CSS and JavaScript
|
||||
animations to common directives that are used to render our application. AngularJS comes bundled with an
|
||||
additional JavaScript file called `angular-animate.js` which, when included into the website and set as
|
||||
a dependency with the application module, will enable animations throughout the application.
|
||||
The animation functionality is provided by Angular in the `ngAnimate` module, which is distributed
|
||||
separately from the core Angular framework. In addition we will use `JQuery` in this project to do
|
||||
extra JavaScript animations.
|
||||
|
||||
Common `ng` directives automatically trigger hooks for animations to tap into. When an animation is found
|
||||
then the animation will run in between the standard DOM operation that is being issued on the element at
|
||||
the given time (e.g. inserting and removing nodes on ngRepeat or adding and removing classes on ngClass).
|
||||
We are using [Bower][bower] to install client side dependencies. This step updates the
|
||||
`bower.json` configuration file to include the new dependency:
|
||||
|
||||
The most important changes are listed below. You can see the full diff on
|
||||
[GitHub](https://github.com/angular/angular-phonecat/compare/step-11...step-12):
|
||||
```
|
||||
{
|
||||
"name": "angular-seed",
|
||||
"description": "A starter project for AngularJS",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/angular/angular-seed",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "1.2.x",
|
||||
"angular-mocks": "~1.2.x",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.2.x",
|
||||
"angular-resource": "~1.2.x",
|
||||
"jquery": "1.10.2",
|
||||
"angular-animate": "~1.2.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `"angular-animate": "~1.2.x"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.2.x.
|
||||
* `"jquery": "1.10.2"` tells bower to install the 1.10.2 version of JQuery. Note that this is not an
|
||||
Angular library, it is the standard JQuery library. We can use bower to install a wide range of 3rd
|
||||
party libraries.
|
||||
|
||||
We must ask bower to download and install this dependency. We can do this by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
If you have bower installed globally then you can run `bower install` but for this project we have
|
||||
preconfigured `npm install` to run bower for us.
|
||||
</div>
|
||||
|
||||
|
||||
## How Animations work with `ngAnimate`
|
||||
@@ -34,32 +73,40 @@ To get an idea of how animations work with AngularJS, please read the
|
||||
|
||||
## Template
|
||||
|
||||
The changes required within the HTML template code is to link the asset files which define the animations as well
|
||||
as the `angular-animate.js` file. The animation module, known as `ngAnimate`, is defined within
|
||||
`angular-animate.js` and contains the code necessary to make your application become animation aware.
|
||||
The changes required within the HTML template code is to link the asset files which define the animations as
|
||||
well as the `angular-animate.js` file. The animation module, known as {@link api/ngAnimate `ngAnimate`}, is
|
||||
defined within `angular-animate.js` and contains the code necessary to make your application become animation
|
||||
aware.
|
||||
|
||||
Here's what needs to changed in the index file:
|
||||
Here's what needs to be changed in the index file:
|
||||
|
||||
__`app/index.html`.__
|
||||
|
||||
```html
|
||||
...
|
||||
<!-- for CSS Transitions and/or Keyframe Animations -->
|
||||
<link rel="stylesheet" href="css/animations.css">
|
||||
|
||||
...
|
||||
|
||||
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
|
||||
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="bower_components/jquery/jquery.js"></script>
|
||||
|
||||
...
|
||||
|
||||
<!-- required module to enable animation support in AngularJS -->
|
||||
<script src="lib/angular/angular-animate.js"></script>
|
||||
<script src="bower_components/angular-animate/angular-animate.js"></script>
|
||||
|
||||
<!-- for JavaScript Animations -->
|
||||
<script src="js/animations.js"></script>
|
||||
|
||||
<!-- for CSS Transitions and/or Keyframe Animations -->
|
||||
<link rel="stylesheet" href="css/animations.css">
|
||||
...
|
||||
```
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
|
||||
Be sure to load jQuery before all AngularJS scripts, otherwise AngularJS won't detect jQuery and
|
||||
animations will not work as expected.
|
||||
</div>
|
||||
|
||||
Animations can now be created within the CSS code (`animations.css`) as well as the JavaScript code (`animations.js`).
|
||||
@@ -71,7 +118,7 @@ with `ngResource`.
|
||||
__`app/js/animations.js`.__
|
||||
|
||||
```js
|
||||
angular.module('phonecatAnimations', ['ngAnimate']).
|
||||
angular.module('phonecatAnimations', ['ngAnimate']);
|
||||
// ...
|
||||
// this module will later be used to define animations
|
||||
// ...
|
||||
@@ -83,14 +130,14 @@ __`app/js/app.js`.__
|
||||
|
||||
```js
|
||||
// ...
|
||||
angular.module('phonecat', [
|
||||
angular.module('phonecatApp', [
|
||||
'ngRoute',
|
||||
|
||||
'phonecatAnimations',
|
||||
'phonecatControllers',
|
||||
'phonecatFilters',
|
||||
'phonecatServices',
|
||||
]).
|
||||
]);
|
||||
// ...
|
||||
```
|
||||
|
||||
@@ -197,7 +244,7 @@ which are described in detail below.
|
||||
|
||||
## Animating `ngView` with CSS Keyframe Animations
|
||||
|
||||
Next let's add an animation for transitions between route changes in `ngView`.
|
||||
Next let's add an animation for transitions between route changes in {@link api/ngRoute.directive:ngView `ngView`}.
|
||||
|
||||
To start, let's add a new CSS class to our HTML like we did in the example above.
|
||||
This time, instead of the `ng-repeat` element, let's add it to the element containing the ng-view directive.
|
||||
@@ -278,18 +325,22 @@ __`app/css/animations.css`.__
|
||||
/* don't forget about the vendor-prefixes! */
|
||||
```
|
||||
|
||||
Nothing crazy here! Just a simple fade in and fade out effect between pages. The only out of the ordinary thing
|
||||
here is that we're using absolute positioning to position the next page (identified via `ng-enter`) on top of the
|
||||
previous page (the one that has the `ng-leave` class) while performing a cross fade animation in between. So
|
||||
as the previous page is just about to be removed, it fades out while the new page fades in right on top of it.
|
||||
Once the leave animation is over then element is removed and once the enter animation is complete then the
|
||||
`ng-enter` and `ng-enter-active` CSS classes are removed from the element rendering it to be position itself
|
||||
with its default CSS code (so no more absolute positioning once the animation is over). This works fluidly so
|
||||
that pages flow naturally between route changes without anything jumping around.
|
||||
Nothing crazy here! Just a simple fade in and fade out effect between pages. The only out of the
|
||||
ordinary thing here is that we're using absolute positioning to position the next page (identified
|
||||
via `ng-enter`) on top of the previous page (the one that has the `ng-leave` class) while performing
|
||||
a cross fade animation in between. So as the previous page is just about to be removed, it fades out
|
||||
while the new page fades in right on top of it.
|
||||
|
||||
The CSS classes applied (the start and end classes) are much the same as with `ng-repeat`. Each time a new page is
|
||||
loaded the ng-view directive will create a copy of itself, download the template and append the contents. This
|
||||
ensures that all views are contained within a single HTML element which allows for easy animation control.
|
||||
Once the leave animation is over then element is removed and once the enter animation is complete
|
||||
then the `ng-enter` and `ng-enter-active` CSS classes are removed from the element rendering it to
|
||||
be position itself with its default CSS code (so no more absolute positioning once the animation is
|
||||
over). This works fluidly so that pages flow naturally between route changes without anything
|
||||
jumping around.
|
||||
|
||||
The CSS classes applied (the start and end classes) are much the same as with `ng-repeat`. Each time
|
||||
a new page is loaded the ng-view directive will create a copy of itself, download the template and
|
||||
append the contents. This ensures that all views are contained within a single HTML element which
|
||||
allows for easy animation control.
|
||||
|
||||
For more on CSS animations, see the
|
||||
[Web Platform documentation](http://docs.webplatform.org/wiki/css/properties/animations).
|
||||
@@ -301,14 +352,14 @@ Let's add another animation to our application. Switching to our `phone-detail.h
|
||||
we see that we have a nice thumbnail swapper. By clicking on the thumbnails listed on the page,
|
||||
the profile phone image changes. But how can we change this around to add animations?
|
||||
|
||||
Let's think about it first. Basically, when you click on a thumbnail image, you're changing the state of the profile image to reflect the newly
|
||||
selected thumbnail image.
|
||||
Let's think about it first. Basically, when you click on a thumbnail image, you're changing the
|
||||
state of the profile image to reflect the newly selected thumbnail image.
|
||||
The best way to specify state changes within HTML is to use classes.
|
||||
Much like before, how we used a CSS class to specify
|
||||
an animation, this time the animation will occur whenever the CSS class itself changes.
|
||||
Much like before, how we used a CSS class to specify an animation, this time the animation will
|
||||
occur whenever the CSS class itself changes.
|
||||
|
||||
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added to the matching
|
||||
profile image and the animation plays.
|
||||
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added
|
||||
to the matching profile image and the animation plays.
|
||||
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first:
|
||||
|
||||
@@ -334,18 +385,54 @@ __`app/partials/phone-detail.html`.__
|
||||
</ul>
|
||||
```
|
||||
|
||||
Just like with the thumbnails, we're using a repeater to display **all** the profile images as a list, however we're
|
||||
not animating any repeat-related animations. Instead, we're keeping our eye on the ng-class directive since whenever
|
||||
the `active` class is true then it will be applied to the element and will render as visible. Otherwise, the profile image
|
||||
is hidden. In our case, there is always one element that has the active class, and, therefore, there will always
|
||||
be one phone profile image visible on screen at all times.
|
||||
Just like with the thumbnails, we're using a repeater to display **all** the profile images as a
|
||||
list, however we're not animating any repeat-related animations. Instead, we're keeping our eye on
|
||||
the ng-class directive since whenever the `active` class is true then it will be applied to the
|
||||
element and will render as visible. Otherwise, the profile image is hidden. In our case, there is
|
||||
always one element that has the active class, and, therefore, there will always be one phone profile
|
||||
image visible on screen at all times.
|
||||
|
||||
When the active class is added to the element, the `active-add` and the `active-add-active` classes
|
||||
are added just before to signal AngularJS to fire off an animation. When removed, the
|
||||
`active-remove` and the `active-remove-active` classes are applied to the element which in turn
|
||||
trigger another animation.
|
||||
|
||||
To ensure that the phone images are displayed correctly when the page is first loaded we also tweak
|
||||
the detail page CSS styles:
|
||||
|
||||
__`app/css/app.css`__
|
||||
```css
|
||||
.phone-images {
|
||||
background-color: white;
|
||||
width: 450px;
|
||||
height: 450px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
float: left;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
img.phone {
|
||||
float: left;
|
||||
margin-right: 3em;
|
||||
margin-bottom: 2em;
|
||||
background-color: white;
|
||||
padding: 2em;
|
||||
height: 400px;
|
||||
width: 400px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
img.phone:first-child {
|
||||
display: block;
|
||||
}
|
||||
```
|
||||
|
||||
When the active class is added to the element, the `active-add` and the `active-add-active` classes are added just before
|
||||
to signal AngularJS to fire off an animation. When removed, the `active-remove` and the `active-remove-active` classes
|
||||
are applied to the element which in turn trigger another animation.
|
||||
|
||||
You may be thinking that we're just going to create another CSS-enabled animation.
|
||||
Although we could do that, let's take the opportunity to learn how to create JavaScript-enabled animations with the `animation()` module method.
|
||||
Although we could do that, let's take the opportunity to learn how to create JavaScript-enabled
|
||||
animations with the `animation()` module method.
|
||||
|
||||
__`app/js/animations.js`.__
|
||||
|
||||
@@ -409,7 +496,7 @@ isn't required to do JavaScript animations with AngularJS, but we're going to us
|
||||
your own JavaScript animation library is beyond the scope of this tutorial. For more on
|
||||
`jQuery.animate`, see the [jQuery documentation](http://api.jquery.com/animate/).
|
||||
|
||||
The `addClass` and `removeClass` callback functions are called whenever an a class is added or removed
|
||||
The `addClass` and `removeClass` callback functions are called whenever a class is added or removed
|
||||
on the element that contains the class we registered, which is in this case `.phone`. When the `.active`
|
||||
class is added to the element (via the `ng-class` directive) the `addClass` JavaScript callback will
|
||||
be fired with `element` passed in as a parameter to that callback. The last parameter passed in is the
|
||||
@@ -438,3 +525,5 @@ There you have it! We have created a web app in a relatively short amount of ti
|
||||
the_end closing notes} we'll cover where to go from here.
|
||||
|
||||
<ul doc-tutorial-nav="12"></ul>
|
||||
|
||||
[bower]: http://bower.io/
|
||||
|
||||
+13
-11
@@ -1,13 +1,12 @@
|
||||
var path = require('canonical-path');
|
||||
var gruntUtils = require('../lib/grunt/utils');
|
||||
var versionInfo = require('../lib/versions/version-info');
|
||||
var basePath = __dirname;
|
||||
|
||||
var basePackage = require('./config');
|
||||
|
||||
module.exports = function(config) {
|
||||
|
||||
var version = gruntUtils.getVersion();
|
||||
var cdnUrl = "//ajax.googleapis.com/ajax/libs/angularjs/" + version.cdn;
|
||||
var cdnUrl = "//ajax.googleapis.com/ajax/libs/angularjs/" + versionInfo.cdnVersion;
|
||||
|
||||
var getVersion = function(component, sourceFolder, packageFile) {
|
||||
sourceFolder = sourceFolder || '../bower_components';
|
||||
@@ -25,9 +24,12 @@ module.exports = function(config) {
|
||||
{ pattern: '**/*.ngdoc', basePath: path.resolve(basePath, 'content') }
|
||||
]);
|
||||
|
||||
config.set('processing.stopOnError', true);
|
||||
|
||||
config.set('processing.errors.minerrInfoPath', path.resolve(basePath, '../build/errors.json'));
|
||||
|
||||
config.set('rendering.outputFolder', '../build/docs');
|
||||
config.set('rendering.contentsFolder', 'partials');
|
||||
|
||||
config.set('logging.level', 'info');
|
||||
|
||||
@@ -38,7 +40,7 @@ module.exports = function(config) {
|
||||
commonFiles: {
|
||||
scripts: [ '../../../angular.js' ]
|
||||
},
|
||||
dependencyPath: '../../..'
|
||||
dependencyPath: '../../../'
|
||||
},
|
||||
scripts: [
|
||||
'../angular.js',
|
||||
@@ -60,7 +62,7 @@ module.exports = function(config) {
|
||||
'js/docs.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/dist/css/bootstrap.css',
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.css',
|
||||
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
|
||||
'css/prettify-theme.css',
|
||||
'css/docs.css',
|
||||
@@ -73,7 +75,7 @@ module.exports = function(config) {
|
||||
commonFiles: {
|
||||
scripts: [ '../../../angular.min.js' ]
|
||||
},
|
||||
dependencyPath: '../../..'
|
||||
dependencyPath: '../../../'
|
||||
},
|
||||
scripts: [
|
||||
'../angular.min.js',
|
||||
@@ -95,7 +97,7 @@ module.exports = function(config) {
|
||||
'js/docs.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/dist/css/bootstrap.min.css',
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
|
||||
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
|
||||
'css/prettify-theme.css',
|
||||
'css/docs.css',
|
||||
@@ -111,7 +113,7 @@ module.exports = function(config) {
|
||||
'../../../angular.js'
|
||||
]
|
||||
},
|
||||
dependencyPath: '../../..'
|
||||
dependencyPath: '../../../'
|
||||
},
|
||||
scripts: [
|
||||
'components/jquery-' + getVersion('jquery') + '/jquery.js',
|
||||
@@ -134,7 +136,7 @@ module.exports = function(config) {
|
||||
'js/docs.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/dist/css/bootstrap.min.css',
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
|
||||
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
|
||||
'css/prettify-theme.css',
|
||||
'css/docs.css',
|
||||
@@ -147,7 +149,7 @@ module.exports = function(config) {
|
||||
commonFiles: {
|
||||
scripts: [ cdnUrl + '/angular.min.js' ]
|
||||
},
|
||||
dependencyPath: cdnUrl
|
||||
dependencyPath: cdnUrl + '/'
|
||||
},
|
||||
scripts: [
|
||||
cdnUrl + '/angular.min.js',
|
||||
@@ -169,7 +171,7 @@ module.exports = function(config) {
|
||||
'js/docs.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/dist/css/bootstrap.min.css',
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
|
||||
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
|
||||
'css/prettify-theme.css',
|
||||
'css/docs.css',
|
||||
|
||||
+8
-4
@@ -2,7 +2,7 @@ var gulp = require('gulp');
|
||||
var concat = require('gulp-concat');
|
||||
var jshint = require('gulp-jshint');
|
||||
var bower = require('bower');
|
||||
var docGenerator = require('dgeni');
|
||||
var dgeni = require('dgeni');
|
||||
var merge = require('event-stream').merge;
|
||||
var path = require('canonical-path');
|
||||
|
||||
@@ -38,18 +38,22 @@ gulp.task('build-app', function() {
|
||||
gulp.task('assets', ['bower'], function() {
|
||||
return merge(
|
||||
gulp.src(['app/assets/**/*']).pipe(gulp.dest(outputFolder)),
|
||||
copyComponent('bootstrap'),
|
||||
copyComponent('bootstrap', '/dist/**/*'),
|
||||
copyComponent('open-sans-fontface'),
|
||||
copyComponent('lunr.js','/*.js'),
|
||||
copyComponent('google-code-prettify'),
|
||||
copyComponent('jquery'),
|
||||
copyComponent('jquery', '/jquery.*'),
|
||||
copyComponent('marked', '/**/*.js', '../node_modules', 'package.json')
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
gulp.task('doc-gen', function() {
|
||||
return docGenerator('docs.config.js').generateDocs();
|
||||
var generateDocs = dgeni.generator('docs.config.js');
|
||||
return generateDocs()
|
||||
.catch(function(error) {
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
// JSHint the example and protractor test files
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
]>
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
|
||||
x="0px" y="0px" width="687px" height="176px" viewBox="0 0 687 176" overflow="visible" enable-background="new 0 0 687 176"
|
||||
x="0" y="0" width="687px" height="176px" viewBox="0 0 687 176" overflow="visible" enable-background="new 0 0 687 176"
|
||||
xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 56 KiB |
@@ -33,7 +33,7 @@ describe("convertDatetimeData", function() {
|
||||
AMPMS: ['AM', 'PM'],
|
||||
DATEFORMATS: ['a', 'b', 'c', 'd'],
|
||||
TIMEFORMATS: ['e', 'f', 'g', 'h'] };
|
||||
|
||||
|
||||
it('should convert empty datetime obj', function() {
|
||||
var processedData = convert(dataObj);
|
||||
expect(processedData.MONTH).toEqual(['Enero', 'Pebrero']);
|
||||
|
||||
@@ -87,7 +87,11 @@ function pluralExtractor(content, localeInfo) {
|
||||
continue;
|
||||
}
|
||||
var temp = goog.i18n.pluralRules.select.toString().
|
||||
replace(/goog.i18n.pluralRules.Keyword/g, 'PLURAL_CATEGORY').replace(/\n/g, '');
|
||||
replace(/goog\.i18n\.pluralRules\.Keyword/g, 'PLURAL_CATEGORY').
|
||||
replace(/goog\.i18n\.pluralRules\.get_vf_/g, 'getVF').
|
||||
replace(/goog\.i18n\.pluralRules\.get_wt_/g, 'getWT').
|
||||
replace(/goog\.i18n\.pluralRules\.decimals_/g, 'getDecimals').
|
||||
replace(/\n/g, '');
|
||||
|
||||
///@@ is a crazy place holder to be replaced before writing to file
|
||||
localeInfo[localeIds[i]].pluralCat = "@@" + temp + "@@";
|
||||
@@ -158,15 +162,39 @@ function outputLocale(localeInfo, localeID) {
|
||||
}
|
||||
localeObj.id = correctedLocaleId(localeID);
|
||||
|
||||
var prefix =
|
||||
"'use strict';\n" +
|
||||
'angular.module("ngLocale", [], ["$provide", function($provide) {\n' +
|
||||
'var PLURAL_CATEGORY = {' +
|
||||
'ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"' +
|
||||
'};\n' +
|
||||
'$provide.value("$locale", ';
|
||||
var getDecimals = [
|
||||
'function getDecimals(n) {',
|
||||
' n = n + \'\';',
|
||||
' var i = n.indexOf(\'.\');',
|
||||
' return (i == -1) ? 0 : n.length - i - 1;',
|
||||
'}', '', ''
|
||||
].join('\n');
|
||||
|
||||
var suffix = ');\n}]);';
|
||||
var getVF = [
|
||||
'function getVF(n, opt_precision) {',
|
||||
' var v = opt_precision;', '',
|
||||
' if (undefined === v) {',
|
||||
' v = Math.min(getDecimals(n), 3);',
|
||||
' }', '',
|
||||
' var base = Math.pow(10, v);',
|
||||
' var f = ((n * base) | 0) % base;',
|
||||
' return {v: v, f: f};',
|
||||
'}', '', ''
|
||||
].join('\n');
|
||||
|
||||
var getWT =
|
||||
[
|
||||
'function getWT(v, f) {',
|
||||
' if (f === 0) {',
|
||||
' return {w: 0, t: 0};',
|
||||
' }', '',
|
||||
' while ((f % 10) === 0) {',
|
||||
' f /= 10;',
|
||||
' v--;',
|
||||
' }', '',
|
||||
' return {w: v, t: f};',
|
||||
'}', '', ''
|
||||
].join('\n');
|
||||
|
||||
localeObj = {
|
||||
DATETIME_FORMATS: localeObj.DATETIME_FORMATS,
|
||||
@@ -176,6 +204,26 @@ function outputLocale(localeInfo, localeID) {
|
||||
};
|
||||
|
||||
var content = serializeContent(localeInfo[localeID]);
|
||||
if (content.indexOf('getVF(') < 0) {
|
||||
getVF = '';
|
||||
}
|
||||
if (content.indexOf('getWT(') < 0) {
|
||||
getWT = '';
|
||||
}
|
||||
if (!getVF && content.indexOf('getDecimals(') < 0) {
|
||||
getDecimals = '';
|
||||
}
|
||||
|
||||
var prefix =
|
||||
"'use strict';\n" +
|
||||
'angular.module("ngLocale", [], ["$provide", function($provide) {\n' +
|
||||
'var PLURAL_CATEGORY = {' +
|
||||
'ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"' +
|
||||
'};\n' +
|
||||
getDecimals + getVF + getWT +
|
||||
'$provide.value("$locale", ';
|
||||
|
||||
var suffix = ');\n}]);';
|
||||
|
||||
return prefix + content + suffix;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@ cd $BASE_DIR
|
||||
|
||||
set -x # Trace commands as they're executed.
|
||||
|
||||
I18N_BASE="https://raw.githubusercontent.com/google/closure-library/master/closure/goog/i18n"
|
||||
|
||||
# use the github repo as it is more up to date than the svn repo
|
||||
curl https://closure-library.googlecode.com/git/closure/goog/i18n/currency.js > closure/currencySymbols.js
|
||||
curl https://closure-library.googlecode.com/git/closure/goog/i18n/datetimesymbols.js > closure/datetimeSymbols.js
|
||||
curl https://closure-library.googlecode.com/git/closure/goog/i18n/datetimesymbolsext.js > closure/datetimeSymbolsExt.js
|
||||
curl https://closure-library.googlecode.com/git/closure/goog/i18n/numberformatsymbols.js > closure/numberSymbols.js
|
||||
curl https://closure-library.googlecode.com/git/closure/goog/i18n/pluralrules.js > closure/pluralRules.js
|
||||
curl "$I18N_BASE/currency.js" > closure/currencySymbols.js
|
||||
curl "$I18N_BASE/datetimesymbols.js" > closure/datetimeSymbols.js
|
||||
curl "$I18N_BASE/datetimesymbolsext.js" > closure/datetimeSymbolsExt.js
|
||||
curl "$I18N_BASE/numberformatsymbols.js" > closure/numberSymbols.js
|
||||
curl "$I18N_BASE/pluralrules.js" > closure/pluralRules.js
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ set -xe
|
||||
# Define reasonable set of browsers in case we are running manually from commandline
|
||||
if [[ -z "$BROWSERS" ]]
|
||||
then
|
||||
BROWSERS="Chrome,Firefox,Opera,/Users/jenkins/bin/safari.sh,/Users/jenkins/bin/ie8.sh,/Users/jenkins/bin/ie9.sh"
|
||||
BROWSERS="Chrome,Firefox,Opera,/Users/jenkins/bin/safari.sh"
|
||||
fi
|
||||
|
||||
# CLEAN #
|
||||
|
||||
@@ -32,7 +32,8 @@ module.exports = function(config, specificOptions) {
|
||||
customLaunchers: {
|
||||
'SL_Chrome': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome'
|
||||
browserName: 'chrome',
|
||||
version: '34'
|
||||
},
|
||||
'SL_Firefox': {
|
||||
base: 'SauceLabs',
|
||||
|
||||
@@ -35,7 +35,7 @@ module.exports = function(grunt) {
|
||||
|
||||
|
||||
grunt.registerTask('docs', 'create angular docs', function(){
|
||||
var gruntProc = shelljs.exec('node_modules/gulp/bin/gulp.js --gulpfile docs/gulpfile.js');
|
||||
var gruntProc = shelljs.exec('"node_modules/.bin/gulp" --gulpfile docs/gulpfile.js');
|
||||
if (gruntProc.code !== 0) {
|
||||
throw new Error('doc generation failed');
|
||||
}
|
||||
|
||||
+7
-177
@@ -4,8 +4,9 @@ var shell = require('shelljs');
|
||||
var grunt = require('grunt');
|
||||
var spawn = require('child_process').spawn;
|
||||
var semver = require('semver');
|
||||
|
||||
var _ = require('lodash');
|
||||
var version, pkg;
|
||||
|
||||
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
|
||||
|
||||
var PORT_MIN = 8000;
|
||||
@@ -23,23 +24,6 @@ var getRandomPorts = function() {
|
||||
];
|
||||
};
|
||||
|
||||
var getPackage = function() {
|
||||
if ( !pkg ) {
|
||||
|
||||
// Search up the folder hierarchy for the first package.json
|
||||
var packageFolder = path.resolve('.');
|
||||
while ( !fs.existsSync(path.join(packageFolder, 'package.json')) ) {
|
||||
var parent = path.dirname(packageFolder);
|
||||
if ( parent === packageFolder) { break; }
|
||||
packageFolder = parent;
|
||||
}
|
||||
pkg = JSON.parse(fs.readFileSync(path.join(packageFolder,'package.json'), 'UTF-8'));
|
||||
|
||||
}
|
||||
|
||||
return pkg;
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -50,160 +34,6 @@ module.exports = {
|
||||
},
|
||||
|
||||
|
||||
getGitRepoInfo: function() {
|
||||
var GITURL_REGEX = /^https:\/\/github.com\/([^\/]+)\/(.+).git$/;
|
||||
var match = GITURL_REGEX.exec(getPackage().repository.url);
|
||||
var git = {
|
||||
owner: match[1],
|
||||
repo: match[2]
|
||||
};
|
||||
return git;
|
||||
},
|
||||
|
||||
|
||||
getVersion: function(){
|
||||
if (version) return version;
|
||||
|
||||
try {
|
||||
|
||||
var gitTag = getTagOfCurrentCommit();
|
||||
var semVerVersion, codeName, fullVersion;
|
||||
if (gitTag) {
|
||||
// tagged release
|
||||
fullVersion = semVerVersion = semver.valid(gitTag);
|
||||
codeName = getTaggedReleaseCodeName(gitTag);
|
||||
} else {
|
||||
// snapshot release
|
||||
semVerVersion = getSnapshotVersion();
|
||||
fullVersion = semVerVersion + '-' + getSnapshotSuffix();
|
||||
codeName = 'snapshot';
|
||||
}
|
||||
|
||||
var versionParts = semVerVersion.match(/(\d+)\.(\d+)\.(\d+)/);
|
||||
|
||||
version = {
|
||||
full: fullVersion,
|
||||
major: versionParts[1],
|
||||
minor: versionParts[2],
|
||||
dot: versionParts[3],
|
||||
codename: codeName,
|
||||
cdn: getPackage().cdnVersion
|
||||
};
|
||||
|
||||
// Stable versions have an even minor version
|
||||
version.isStable = version.minor%2 === 0;
|
||||
|
||||
return version;
|
||||
|
||||
} catch (e) {
|
||||
grunt.fail.warn(e);
|
||||
}
|
||||
|
||||
function getTagOfCurrentCommit() {
|
||||
var gitTagResult = shell.exec('git describe --exact-match', {silent:true});
|
||||
var gitTagOutput = gitTagResult.output.trim();
|
||||
var branchVersionPattern = new RegExp(getPackage().branchVersion.replace('.', '\\.').replace('*', '\\d+'));
|
||||
if (gitTagResult.code === 0 && gitTagOutput.match(branchVersionPattern)) {
|
||||
return gitTagOutput;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getTaggedReleaseCodeName(tagName) {
|
||||
var tagMessage = shell.exec('git cat-file -p '+ tagName +' | grep "codename"', {silent:true}).output;
|
||||
var codeName = tagMessage && tagMessage.match(/codename\((.*)\)/)[1];
|
||||
if (!codeName) {
|
||||
throw new Error("Could not extract release code name. The message of tag "+tagName+
|
||||
" must match '*codename(some release name)*'");
|
||||
}
|
||||
return codeName;
|
||||
}
|
||||
|
||||
function getSnapshotVersion() {
|
||||
var oldTags = shell.exec('git tag -l v'+getPackage().branchVersion, {silent:true}).output.trim().split('\n');
|
||||
// ignore non semver versions.
|
||||
oldTags = oldTags.filter(function(version) {
|
||||
return version && semver.valid(version);
|
||||
});
|
||||
if (oldTags.length) {
|
||||
oldTags.sort(semver.compare);
|
||||
semVerVersion = oldTags[oldTags.length-1];
|
||||
if (semVerVersion.indexOf('-') !== -1) {
|
||||
semVerVersion = semver.inc(semVerVersion, 'prerelease');
|
||||
} else {
|
||||
semVerVersion = semver.inc(semVerVersion, 'patch');
|
||||
}
|
||||
} else {
|
||||
semVerVersion = semver.valid(getPackage().branchVersion.replace(/\*/g, '0'));
|
||||
}
|
||||
return semVerVersion;
|
||||
}
|
||||
|
||||
function getSnapshotSuffix() {
|
||||
var jenkinsBuild = process.env.TRAVIS_BUILD_NUMBER || process.env.BUILD_NUMBER || 'local';
|
||||
var hash = shell.exec('git rev-parse --short HEAD', {silent: true}).output.replace('\n', '');
|
||||
return 'build.'+jenkinsBuild+'+sha.'+hash;
|
||||
}
|
||||
},
|
||||
|
||||
getPreviousVersions: function() {
|
||||
var VERSION_REGEX = /([1-9]\d*)\.(\d+)\.(\d+)(?:-?rc\.?(\d+)|-(snapshot))?/;
|
||||
|
||||
// Pad out a number with zeros at the front to make it `digits` characters long
|
||||
function pad(num, digits) {
|
||||
var zeros = Array(digits+1).join('0');
|
||||
return (zeros+num).slice(-digits);
|
||||
}
|
||||
|
||||
function padVersion(version) {
|
||||
// We pad out the version numbers with 0s so they sort nicely
|
||||
// - Non-Release Candidates get 9999 for their release candidate section to make them appear earlier
|
||||
// - Snapshots get 9 added to the front to move them to the top of the list
|
||||
var maxLength = 4;
|
||||
var padded = (version.snapshot ? '9' : '0') + pad(version.major, maxLength) +
|
||||
pad(version.minor, maxLength) + pad(version.dot, maxLength) +
|
||||
pad(version.rc || 9999, maxLength);
|
||||
return padded;
|
||||
}
|
||||
|
||||
function getVersionFromTag(tag) {
|
||||
var match = VERSION_REGEX.exec(tag);
|
||||
if ( match ) {
|
||||
var version = {
|
||||
tag: tag,
|
||||
major: match[1], minor: match[2], dot: match[3], rc: match[4],
|
||||
snapshot: !!match[5] && getSnapshotSuffix()
|
||||
};
|
||||
|
||||
if(version.snapshot) {
|
||||
version.full = version.major + '.' + version.minor + '.x (edge)';
|
||||
} else {
|
||||
version.full = version.major + '.' + version.minor + '.' + version.dot +
|
||||
(version.rc ? '-rc.' + version.rc : '');
|
||||
}
|
||||
|
||||
// Stable versions have an even minor version and are not a release candidate
|
||||
version.isStable = !(version.minor%2 || version.rc);
|
||||
|
||||
// Versions before 1.0.2 had a different docs folder name
|
||||
version.docsUrl = 'http://code.angularjs.org/' + version.full + '/docs';
|
||||
if ( version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2 ) ) {
|
||||
version.docsUrl += '-' + version.full;
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
var tags = shell.exec('git tag', {silent: true}).output.split(/\s*\n\s*/);
|
||||
return _(tags)
|
||||
.map(getVersionFromTag)
|
||||
.filter() // getVersion can map to undefined - this clears those out
|
||||
.sortBy(padVersion)
|
||||
.value();
|
||||
},
|
||||
|
||||
startKarma: function(config, singleRun, done){
|
||||
var browsers = grunt.option('browsers');
|
||||
var reporters = grunt.option('reporters');
|
||||
@@ -225,7 +55,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
|
||||
updateWebdriver: function(done){
|
||||
updateWebdriver: function(done){
|
||||
if (process.env.TRAVIS) {
|
||||
// Skip the webdriver-manager update on Travis, since the browsers will
|
||||
// be provided remotely.
|
||||
@@ -306,7 +136,7 @@ module.exports = {
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/'/g, "\\'")
|
||||
.replace(/\r?\n/g, '\\n');
|
||||
js = "!angular.$$csp() && angular.element(document).find('head').prepend('<style type=\"text/css\">" + css + "</style>');";
|
||||
js = "!window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type=\"text/css\">" + css + "</style>');";
|
||||
state.js.push(js);
|
||||
|
||||
return state;
|
||||
@@ -319,9 +149,9 @@ module.exports = {
|
||||
.replace(/"NG_VERSION_FULL"/g, NG_VERSION.full)
|
||||
.replace(/"NG_VERSION_MAJOR"/, NG_VERSION.major)
|
||||
.replace(/"NG_VERSION_MINOR"/, NG_VERSION.minor)
|
||||
.replace(/"NG_VERSION_DOT"/, NG_VERSION.dot)
|
||||
.replace(/"NG_VERSION_DOT"/, NG_VERSION.patch)
|
||||
.replace(/"NG_VERSION_CDN"/, NG_VERSION.cdn)
|
||||
.replace(/"NG_VERSION_CODENAME"/, NG_VERSION.codename);
|
||||
.replace(/"NG_VERSION_CODENAME"/, NG_VERSION.codeName);
|
||||
if (strict !== false) processed = this.singleStrict(processed, '\n\n', true);
|
||||
return processed;
|
||||
},
|
||||
@@ -374,7 +204,7 @@ module.exports = {
|
||||
var mapFile = minFile + '.map';
|
||||
var mapFileName = mapFile.match(/[^\/]+$/)[0];
|
||||
var errorFileName = file.replace(/\.js$/, '-errors.json');
|
||||
var versionNumber = this.getVersion().full;
|
||||
var versionNumber = grunt.config('NG_VERSION').full;
|
||||
shell.exec(
|
||||
'java ' +
|
||||
this.java32flags() + ' ' +
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user