Compare commits
663 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f5ef3724ce | |||
| e60601be4f | |||
| e2663f62b0 | |||
| 9f9ed4c5ff | |||
| 66fc268aeb | |||
| 1d966f8a65 | |||
| ddf6f1143f | |||
| 2636105c5e | |||
| c0b557a96c | |||
| 84873e7f4e | |||
| 95fdb1231f | |||
| ef875ad0cf | |||
| 615841a5d3 | |||
| 7d0c256ecd | |||
| 6cbe096dbf | |||
| 21602b5cd6 | |||
| 4ae671ac88 | |||
| 28ed5ba465 | |||
| 02dc81bae0 | |||
| 445680f601 | |||
| bf729d550b | |||
| dc8ffa51b7 | |||
| d7ba5bc83b | |||
| 950d02b4d4 | |||
| 578e38e0af | |||
| af7c51ee1d | |||
| 25d1822bd8 | |||
| 3945f884c5 | |||
| d5ccabce60 | |||
| bb948176aa | |||
| 163c799eff | |||
| 7da70af1ae | |||
| 836e4c1428 | |||
| eabedba34d | |||
| b4add97c17 | |||
| bacc31bea9 | |||
| ad90c3574f | |||
| e28171d5e4 | |||
| ce73ed091b | |||
| 90ac8d57b0 | |||
| 6eb1179505 | |||
| 9b85757102 | |||
| c6c3949b14 | |||
| e175db37c6 | |||
| f38010d3a2 | |||
| 7fc18b263d | |||
| fabc9f77a3 | |||
| c17c731fdc | |||
| 3692885810 | |||
| 5d43439dbe | |||
| a46f2a0db3 | |||
| 3217a249e1 | |||
| 78f394fd17 | |||
| e82e64d57b | |||
| 8978e066b5 | |||
| 833eb3c844 | |||
| 07926ff1ef | |||
| e801faba2e | |||
| ee6af9a978 | |||
| 74379df6c4 | |||
| fe65dd926c | |||
| 669b53ede2 | |||
| b0c3f28e8f | |||
| 9810dc0993 | |||
| ab5df20dfa | |||
| d83a92c121 | |||
| d0425de29e | |||
| ad5e42cf82 | |||
| 9ed1126adb | |||
| 7a19eb84aa | |||
| 718741acab | |||
| ec8bb675b4 | |||
| 8e32f3fd35 | |||
| 02332107e5 | |||
| afc81b554e | |||
| 26e8ab3693 | |||
| 28ccc76aa1 | |||
| b3c4cb7cff | |||
| 4af4378b11 | |||
| 8611ebe6a0 | |||
| 8f46a3c9ac | |||
| 66fdb36ecb | |||
| f0f5ffa9aa | |||
| 2bc7afd3ba | |||
| a4b45397e0 | |||
| de4e06ed73 | |||
| fd822bdaf9 | |||
| 4f78fd692c | |||
| df6d2ba326 | |||
| ccda436f94 | |||
| e86c435349 | |||
| 1942861472 | |||
| b96e978178 | |||
| bda2bba2be | |||
| ca08c004c8 | |||
| 25a62b58db | |||
| 97e3ec4d1b | |||
| 75f11f1fc4 | |||
| e134a8335f | |||
| 8ee32a75f0 | |||
| f6a8ad87ee | |||
| 7e6ff401b8 | |||
| 7aeb6a24f7 | |||
| e1ecc34edd | |||
| 29d36e94e1 | |||
| 091c173632 | |||
| c115fa9924 | |||
| b7a7fc7065 | |||
| 21b2a5bd21 | |||
| ca1e45beaf | |||
| 084b83ffa9 | |||
| bf5e5f7bc9 | |||
| 2e9fed7b6c | |||
| ea3228e311 | |||
| 2eb49147d6 | |||
| 13f92de624 | |||
| 2bc39bb0b4 | |||
| 62ae7fccbc | |||
| 3ace81b92a | |||
| 9acf45127e | |||
| 6883e8c7a0 | |||
| 7ae536d053 | |||
| 2170c06924 | |||
| 0e5a24c584 | |||
| 4e8f0d6e9f | |||
| f9b6b61468 | |||
| 555f415290 | |||
| 3800d17703 | |||
| 009059dd1b | |||
| 6b7ddf414d | |||
| 8259f10138 | |||
| ab407de54d | |||
| 0d7fe97aff | |||
| a2a830e227 | |||
| 8336f3f0ba | |||
| e14ac2c3b0 | |||
| a13653c814 | |||
| 8017340cd1 | |||
| 17d1aef66a | |||
| 1856f62cb1 | |||
| 7e1f364177 | |||
| aac68bf2ba | |||
| 4b4292edb8 | |||
| 292d5d1421 | |||
| 66dec77555 | |||
| 8fa79066e2 | |||
| 909415d5ed | |||
| 4421f3d435 | |||
| 22cb600280 | |||
| 5ba227c7cd | |||
| f37f0ea16e | |||
| 91ccb4ba6e | |||
| d0f459c56f | |||
| cbedf55641 | |||
| 988ed451b5 | |||
| fc2f188d4d | |||
| 4b1913c5ec | |||
| 06534413d3 | |||
| e54909f5ef | |||
| 79f2512ba7 | |||
| b0eb831bce | |||
| 7f0b97e02c | |||
| fc7f1ef6a0 | |||
| 8bae2a5ecb | |||
| 30e5f6274a | |||
| bc6e0cc954 | |||
| 8d11db0757 | |||
| 2a8fe56997 | |||
| 622c3ec974 | |||
| db78aa1ce1 | |||
| 986608fe76 | |||
| 31b8624121 | |||
| a5607e3061 | |||
| ff2cb86d5d | |||
| 825cbadf80 | |||
| 93f96a16f6 | |||
| c763b009ac | |||
| e5da0c956b | |||
| 3c80cf3df6 | |||
| ad3cc16eef | |||
| 08d09ecbaa | |||
| 456c7f62c5 | |||
| c9e7fb894b | |||
| 4a9ccc0abc | |||
| bceadd8e30 | |||
| 545b31aa2e | |||
| e068addadb | |||
| 0da4902e9d | |||
| 3ba90003b4 | |||
| 5927b23ef3 | |||
| 452607fc64 | |||
| 19401280ae | |||
| 08a33e7bb3 | |||
| 30753cb131 | |||
| dbf8afcba0 | |||
| 446f6b233f | |||
| e3fad0feb3 | |||
| 0f6e199d98 | |||
| 15e6105779 | |||
| ee38918059 | |||
| 94e3b28d24 | |||
| 38e6d1e313 | |||
| 607de75fa4 | |||
| db04241beb | |||
| 774db0aecb | |||
| dc0b0c77c7 | |||
| 6114c8f504 | |||
| b99b0a8072 | |||
| 431b748cac | |||
| de34ca0b64 | |||
| 793ecb4817 | |||
| 1d45e65f4a | |||
| 2bbef363e4 | |||
| 035ad72726 | |||
| 9d808239b3 | |||
| ef01362e44 | |||
| 6c30601ad8 | |||
| 37b5c5cfe9 | |||
| 966cbd4cf8 | |||
| 8534b7c7c0 | |||
| 6802a76007 | |||
| 7ec8a89362 | |||
| 34f174066f | |||
| 530dc412c4 | |||
| 3f99cdbdc3 | |||
| 13e7df68a6 | |||
| 42062dab34 | |||
| 1c9fc1e1de | |||
| 8bc7beacd8 | |||
| 842741ee99 | |||
| 40ad543d27 | |||
| 06835a462a | |||
| 142cffcf64 | |||
| 862d78c1d9 | |||
| 6f8904e027 | |||
| 4dc9e6416a | |||
| 4f6e947e49 | |||
| 15efbbdc1f | |||
| 67a3315e1d | |||
| 8f0dcbab80 | |||
| 1f4b417184 | |||
| 2d8d5aef29 | |||
| eb758bc605 | |||
| 761997e082 | |||
| aacd5b672e | |||
| 8d64793717 | |||
| 908f59a5df | |||
| a45d383da2 | |||
| c1a681d6f4 | |||
| f4df421b44 | |||
| bdef462ccc | |||
| a79231dea6 | |||
| 3e54a1b18a | |||
| 4b90f65614 | |||
| b5594a773a | |||
| f39420e7d7 | |||
| 72e46548b8 | |||
| 9dea9de449 | |||
| bee78a8492 | |||
| f3e04fbd6a | |||
| 00ea08e0ab | |||
| 31b59efa96 | |||
| 17251372b1 | |||
| f768954f38 | |||
| 3237f8b995 | |||
| 7802c90e13 | |||
| c348f2cad6 | |||
| f3456dc282 | |||
| ee04141a5a | |||
| 66fec10dc3 | |||
| ae75c35746 | |||
| 0cf5535333 | |||
| fdd5d9471f | |||
| 0782422d1f | |||
| 8fa066190a | |||
| e90b741c94 | |||
| 3af1e7ca2e | |||
| 0fbaa2f12a | |||
| ad3b8d7bcf | |||
| 3ea2416f80 | |||
| 9636160332 | |||
| 2b2df4754d | |||
| 120701b9d9 | |||
| fe5240732d | |||
| b98c23274b | |||
| 4c6d26a38f | |||
| c43ce91b25 | |||
| b7cf7f2a79 | |||
| ef7cf60ebd | |||
| 7974e7eb5f | |||
| f9b4c9da64 | |||
| 83ac1193f2 | |||
| 7a3fdda965 | |||
| b4f18fc295 | |||
| da464683aa | |||
| a0b35161a6 | |||
| a8f4d87be5 | |||
| 57ea8156a1 | |||
| 6289d18e61 | |||
| 9e37ebe635 | |||
| 345c01c81b | |||
| 975aef2ad2 | |||
| c863514660 | |||
| 86a6cc7152 | |||
| 8f3276bbcd | |||
| 2428907259 | |||
| 8a8a2cf462 | |||
| 47efe44a1d | |||
| c52e749a6e | |||
| 4ab3596295 | |||
| 106674ac1e | |||
| 330d1a870d | |||
| 7e2e7b07b6 | |||
| ce80576e0b | |||
| 10da625ed9 | |||
| 9ee9ca13da | |||
| bb39d34279 | |||
| e09a78438f | |||
| 1e890863e5 | |||
| 76a500179d | |||
| 7b32c71386 | |||
| 28e84ca167 | |||
| 41250e9cf9 | |||
| 77ba539f63 | |||
| 952225f020 | |||
| 35f9f527d3 | |||
| 30bd04feaa | |||
| 25a77c58c1 | |||
| 75721223b5 | |||
| f606ffed4b | |||
| b49035a8c5 | |||
| 86ff9dee23 | |||
| 6dc22fe575 | |||
| 68ab0f9b02 | |||
| d7e0915e62 | |||
| b3d5d2caa9 | |||
| c02ef92630 | |||
| 1e8448b9e5 | |||
| c5f3a413bc | |||
| d3fb5b411e | |||
| 75bc59ee4b | |||
| 6aee2938a7 | |||
| 2043fd43fa | |||
| ce4f27aa7b | |||
| 8e915f5545 | |||
| 8e6e09a4bc | |||
| f3323ec18e | |||
| b5a510a343 | |||
| 9ec45ad5c4 | |||
| 8e880fcb77 | |||
| b74163dc1d | |||
| bad62d87a1 | |||
| 65b6e48742 | |||
| fee3717892 | |||
| d0edc11704 | |||
| f9f0905f4a | |||
| 92ebf11b2a | |||
| e6ee994764 | |||
| c8ee00cb2b | |||
| 7460a7ef61 | |||
| 3c87611188 | |||
| b842642b57 | |||
| d428c9910e | |||
| f8529672f6 | |||
| e83465c362 | |||
| f370be85cb | |||
| 5dbf0cc8a2 | |||
| e4a00626d8 | |||
| 91a34a7027 | |||
| af285dd370 | |||
| 89e001b18a | |||
| 4f2f3c9cbf | |||
| c1abc03cf3 | |||
| 04a62e83bc | |||
| f9f95879f0 | |||
| 8cad231bd2 | |||
| 0e17ade959 | |||
| bb67ee8d28 | |||
| 2a12f7dcaa | |||
| 20ce797906 | |||
| cc9f1fdf38 | |||
| f243c6aeda | |||
| 26e651996a | |||
| f57536ddb6 | |||
| 73e3f4c10c | |||
| ec6d106d4a | |||
| fab4ada3c8 | |||
| d6eba8f39f | |||
| 4295b3dded | |||
| a23d15ad3a | |||
| 66f3317bef | |||
| a4dd9ca769 | |||
| d6e4636618 | |||
| e0ee3a0726 | |||
| e670a812fa | |||
| 517ada2662 | |||
| 88ae927857 | |||
| bb7228e2d9 | |||
| 25b3438fd7 | |||
| f21b9214e6 | |||
| 80ea329d5f | |||
| 6e0ff5fa96 | |||
| 4cdce2d30b | |||
| 48e69ed4d1 | |||
| c35b0a7907 | |||
| 805bb5bb6e | |||
| 4224e8e371 | |||
| 6319ba2ed0 | |||
| dd44e00ddd | |||
| 6e4a501127 | |||
| c7dbe4d98b | |||
| 3069566073 | |||
| bd9a7b9fd7 | |||
| 759e6ea8ed | |||
| c4989d8979 | |||
| af710c3ac1 | |||
| e89ef35d05 | |||
| 2ba3c3ee57 | |||
| c6dbdde6dc | |||
| f8417b48be | |||
| 7f1e2e4846 | |||
| 5533e48dea | |||
| 16d0354f93 | |||
| 350ee31107 | |||
| 2da07127e4 | |||
| d05e839920 | |||
| 22dee3e278 | |||
| bc2ca384b1 | |||
| 2380943106 | |||
| 297c9b5b89 | |||
| 43b2cd45f0 | |||
| e389911a35 | |||
| fd357b4cd2 | |||
| 924ffafc51 | |||
| 3616716615 | |||
| 76df0db598 | |||
| e21a868524 | |||
| 7fe46e8d7e | |||
| 864da8b553 | |||
| d62b8407cf | |||
| 8cb84eac68 | |||
| 2e0e732cad | |||
| 22f9354c21 | |||
| 0c75a96917 | |||
| 14407921c7 | |||
| 8920381b44 | |||
| 3751f172b3 | |||
| 3776e08db0 | |||
| c1debfb81d | |||
| 0fb37b08e7 | |||
| 8fa598fa00 | |||
| 91f9efed49 | |||
| 2094bc8f61 | |||
| 9701f0735b | |||
| a1f3725c0f | |||
| 4ac234833a | |||
| 9d9117384f | |||
| 525e444a0f | |||
| fd112877f8 | |||
| 6181ca600d | |||
| 11e9572b95 | |||
| b6bc6c2ddf | |||
| ea6b87c24b | |||
| e205bd7137 | |||
| bd7e68f12f | |||
| 0a604bdb90 | |||
| 4738d49e1c | |||
| 9fdb09ebf8 | |||
| 1eebb771e3 | |||
| 9250fce19c | |||
| 9a69677551 | |||
| b6db58c647 | |||
| 4b0f2dfe0c | |||
| dad2603752 | |||
| 50076b571d | |||
| aa64d37a23 | |||
| 2e5199997c | |||
| b2f5299e0e | |||
| 805e083c24 | |||
| 1abdc097b2 | |||
| 9f56af9c15 | |||
| c5f0342ad8 | |||
| b85e95709d | |||
| cc5dfaf0ab | |||
| 9272a1a472 | |||
| f85c82acd6 | |||
| 35bb19856c | |||
| 97bdf979a1 | |||
| 64938a2e81 | |||
| e7c22e8153 | |||
| b2dc2ce0b5 | |||
| 498ad0a3be | |||
| cfc18efd28 | |||
| 97573c3930 | |||
| 8bb9f12961 | |||
| ce0fbc6a77 | |||
| 3d388498e5 | |||
| 3d787ab6f4 | |||
| 81063a748c | |||
| 9462b556a3 | |||
| 8e6ecd98ae | |||
| 1d7adac7a5 | |||
| 72ad726efa | |||
| 0d2d7025e6 | |||
| d517bcad5b | |||
| e5419db6c7 | |||
| 754d2541c4 | |||
| 9bd2c3967b | |||
| 38ec6519a3 | |||
| 0b8cf8539d | |||
| f109604315 | |||
| ac3dbae370 | |||
| cf1d365f57 | |||
| 78a0f41058 | |||
| eccd9bfbb3 | |||
| 2d9dd1c172 | |||
| a2c4271128 | |||
| 56c00800c7 | |||
| 15ec78f5ef | |||
| a4863d5244 | |||
| 96a1df192a | |||
| 89c25fe713 | |||
| c06c5a36b1 | |||
| 9985104dc0 | |||
| 94514a91f8 | |||
| 4da65d0e8c | |||
| e1d122a4b7 | |||
| 885c3ad5dd | |||
| 9312bed472 | |||
| faf29dd047 | |||
| 510b5f3d90 | |||
| 1e59822df7 | |||
| d95a6925cd | |||
| b4d680a921 | |||
| ff4480be65 | |||
| cf0513dc6f | |||
| dfba8fb2e7 | |||
| a0af13f672 | |||
| 69e6379d19 | |||
| 1094b3471e | |||
| 3224862a9c | |||
| 0084cb5ca4 | |||
| c578f8c3ed | |||
| 5b05c0de03 | |||
| d19c0ac6d3 | |||
| 5343deb3da | |||
| 26bad2bf87 | |||
| d304b0c3df | |||
| 9d5c533791 | |||
| 7414e7b533 | |||
| 5432dd289a | |||
| 194b2c1ea0 | |||
| f7a9ea6a41 | |||
| 627eba2b7c | |||
| 73ee5fc008 | |||
| 4c762bfe5c | |||
| 749b3e8763 | |||
| fd409bd2df | |||
| e80a64883d | |||
| 8b2753eee2 | |||
| 02aa7978d3 | |||
| 5bf7ff5a3e | |||
| a01aa7055c | |||
| a01d888eec | |||
| 28800a48ad | |||
| dea72be0cc | |||
| cd139f5767 | |||
| 10a7521f0b | |||
| 887da5684b | |||
| 95a29d7bde | |||
| 945056b166 | |||
| 128feb2674 | |||
| a709dc19b8 | |||
| 65585a2d3c | |||
| edbe9d8ca8 | |||
| 9e67da420b | |||
| 5fc2b96b97 | |||
| a7ee4a8884 | |||
| eea0de6db4 | |||
| d15165f207 | |||
| c7998f5f99 | |||
| 08e3b1edbb | |||
| fe743e31f8 | |||
| 55ce859998 | |||
| 5a3c9190dc | |||
| e160944bfa | |||
| 6f8940c5d0 | |||
| 87cbf9f591 | |||
| fd6e5e3f31 | |||
| 7d4aee31bb | |||
| 7a54d2791f | |||
| 65243b7d60 | |||
| 9e30baad3f | |||
| a070ff5ad0 | |||
| c90abf057b | |||
| cdc093a463 | |||
| 00cc9eb32a | |||
| ef4bb28be1 | |||
| 496e6bf901 | |||
| 23b255a8b7 | |||
| e2154cbc0b | |||
| 0a5c00abf8 | |||
| a004d487c4 | |||
| 037f30a0c9 | |||
| c37bb2dc28 | |||
| 7cf70c587e | |||
| 1777110958 | |||
| d2089a1633 | |||
| e9ce22592a | |||
| 4f6fe1d479 | |||
| 0f19cd3625 | |||
| c1caf2560a | |||
| b07cc0e392 | |||
| a26f192e01 | |||
| f943180e34 | |||
| e6a6e32c72 | |||
| e1cfb99ae9 | |||
| 1cdc050ce7 | |||
| d600c608e3 | |||
| 86321d1f57 | |||
| 8724e97b7e | |||
| bf8013ad57 | |||
| a6c45c3e66 | |||
| e626f95469 | |||
| 2fab1a2da9 | |||
| f3a39a6418 | |||
| f00b157841 | |||
| ae20f0c1b3 | |||
| 9f6c5db2a6 | |||
| ea9ca651d2 | |||
| d6db4b1749 | |||
| 9798f5e35f | |||
| 94bf24e3b6 | |||
| fbc7f1b454 | |||
| bf7c9d9900 | |||
| 568574b915 | |||
| 46d690ff01 | |||
| 882f412d57 | |||
| 0d4def68ae | |||
| d35c1ac8b0 | |||
| 2a9f7b7287 | |||
| 245b60d69a | |||
| b6a01bd27d | |||
| aaaad298ac | |||
| ba6b68b6ae | |||
| ed768ebc53 | |||
| 9fd3dfe49d | |||
| d7686a429c | |||
| 6c0cf17404 | |||
| c648fee5c2 | |||
| 9462c78fbf | |||
| c4497d60bc | |||
| 5690627766 | |||
| 2389c71238 | |||
| ce7ab3d1ee | |||
| 7db3b54c1f | |||
| 21ad176246 | |||
| df1d222dd0 | |||
| 7eea26323b | |||
| f0fb4a3928 | |||
| 0144fa1ca9 | |||
| b612826158 | |||
| e5a3acacad |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
|
||||
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
|
||||
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/perf" type="2"/> <item path="/angular.js/src" type="2"/> </resources>}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/perf.sh}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
|
||||
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/>
|
||||
</launchConfiguration>
|
||||
@@ -2,6 +2,14 @@ build/
|
||||
angularjs.netrc
|
||||
jstd.log
|
||||
.DS_Store
|
||||
gen_docs.disable
|
||||
test.disable
|
||||
regression/temp*.html
|
||||
performance/temp*.html
|
||||
.idea/workspace.xml
|
||||
*~
|
||||
angular.js.tmproj
|
||||
node_modules
|
||||
jsTestDriver*.conf
|
||||
angular.xcodeproj
|
||||
.idea
|
||||
|
||||
Generated
-67
@@ -1,67 +0,0 @@
|
||||
IMPORTANT: THIS IS TO CERTIFY THE RIGHT TO USE THE JETBRAINS SOFTWARE PRODUCT, GRANTED BY JETBRAINS S.R.O. UNDER THE TERMS AND CONDITIONS OF THE LICENSE AGREEMENT INCLUDED WITH THE SOFTWARE. PLEASE SAVE A COPY OF THIS EMAIL FOR FUTURE REFERENCES.
|
||||
|
||||
========LICENSE DETAILS========
|
||||
|
||||
Type: Open Source License
|
||||
Reference No*: LC-93161-D352729080
|
||||
Date of Issue: 3 November 2010
|
||||
Expiration Date: 3 November 2011
|
||||
Number of Authorized Users: not limited
|
||||
|
||||
* Please quote this reference when contacting JetBrains
|
||||
|
||||
===========LICENSEE============
|
||||
|
||||
Name: angularjs
|
||||
Customer ID: 93161
|
||||
Address:
|
||||
|
||||
=======SOFTWARE PRODUCT========
|
||||
|
||||
Product Name: WebStorm
|
||||
Licensed Version: 1.0 and any new product release which is made generally available before 3 November 2011
|
||||
|
||||
The software is shipped electronically and is available for download from:
|
||||
http://www.jetbrains.com/webstorm/download/
|
||||
|
||||
Your WebStorm license includes a 1-year upgrade subscription qualifying you for free upgrades to any new versions of WebStorm released by JetBrains during your upgrade subscription period starting on your license issue date and ending on 3 November 2011. To check availability of the new versions of WebStorm, please visit http://www.jetbrains.com.
|
||||
|
||||
For running any new version of WebStorm released by JetBrains during your upgrade subscription period, please use the included below licensing details.
|
||||
|
||||
You can renew your upgrade subscription before or after its expiration. Your new subscription period will start on the date following the expiration date of your current upgrade subscription.
|
||||
|
||||
=========INSTALLATION==========
|
||||
|
||||
Run WebStorm and follow the Installation Wizard's instructions. To register for use of the software or change your existing registration details, go to Help/Register menu of the software and enter the included below the User Name and License Key(s) into the registration dialog:
|
||||
|
||||
User Name: angularjs
|
||||
|
||||
===== LICENSE BEGIN =====
|
||||
93161-03112010
|
||||
00000jBsEx59XVlc79fV"aAqWXQ09e
|
||||
jQsg5TNp5X4HGhc10LNBdu!!ejRcFG
|
||||
7h3S6T09YcRWs23TH0RgaM87!HqmQo
|
||||
===== LICENSE END =====
|
||||
|
||||
===DOCUMENTATION AND SUPPORT===
|
||||
|
||||
WebStorm documentation:
|
||||
http://www.jetbrains.com/webstorm/documentation/
|
||||
|
||||
Available support resources:
|
||||
http://www.jetbrains.com/support/
|
||||
|
||||
Technical support contact:
|
||||
support@jetbrains.com
|
||||
|
||||
Contact for the license renewal requests:
|
||||
opensource@jetbrains.com
|
||||
|
||||
For questions, please contact:
|
||||
sales@jetbrains.com
|
||||
|
||||
|
||||
JetBrains Sales Team
|
||||
http://www.jetbrains.com
|
||||
"Develop with pleasure!"
|
||||
|
||||
Generated
-9
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
Generated
-5
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||
</project>
|
||||
|
||||
Generated
-11
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DependencyValidationManager">
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" />
|
||||
<component name="SvnBranchConfigurationManager">
|
||||
<option name="mySupportsUserInfoFilter" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
Generated
-9
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/angular.js.iml" filepath="$PROJECT_DIR$/.idea/angular.js.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
|
||||
Generated
-68
@@ -1,68 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CodeStyleSettingsManager">
|
||||
<option name="PER_PROJECT_SETTINGS">
|
||||
<value>
|
||||
<option name="USE_SAME_INDENTS" value="true" />
|
||||
<option name="OTHER_INDENT_OPTIONS">
|
||||
<value>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</value>
|
||||
</option>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="js">
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="jsp">
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="sass">
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="xml">
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
<ADDITIONAL_INDENT_OPTIONS fileType="yml">
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="8" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="false" />
|
||||
<option name="SMART_TABS" value="false" />
|
||||
<option name="LABEL_INDENT_SIZE" value="0" />
|
||||
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
|
||||
</ADDITIONAL_INDENT_OPTIONS>
|
||||
</value>
|
||||
</option>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
Generated
-15
@@ -1,15 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="gen_docs" type="BashConfigurationType" factoryName="Bash">
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="INTERPRETER_PATH" value="/bin/bash" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs />
|
||||
<module name="angular.js" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/gen_docs.sh" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<RunnerSettings RunnerId="BashRunner" />
|
||||
<ConfigurationWrapper RunnerId="BashRunner" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
-15
@@ -1,15 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="rake compile" type="BashConfigurationType" factoryName="Bash">
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="INTERPRETER_PATH" value="/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs />
|
||||
<module name="angular.js" />
|
||||
<option name="SCRIPT_NAME" value="/usr/bin/rake" />
|
||||
<option name="PARAMETERS" value="compile" />
|
||||
<RunnerSettings RunnerId="BashRunner" />
|
||||
<ConfigurationWrapper RunnerId="BashRunner" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
-7
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
|
||||
<triggers>auto,full,incremental,</triggers>
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>LaunchConfigHandle</key>
|
||||
<value><project>/.externalToolBuilders/docs.launch</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
|
||||
<triggers>auto,full,incremental,</triggers>
|
||||
@@ -26,7 +36,7 @@
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>LaunchConfigHandle</key>
|
||||
<value><project>/.externalToolBuilders/docs.launch</value>
|
||||
<value><project>/.externalToolBuilders/JSTD_perf.launch</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
|
||||
+704
-15
@@ -1,12 +1,634 @@
|
||||
- The Latest Stable Release: <a href="#0.9.19">0.9.19 canine-psychokinesis</a>
|
||||
- The Latest Unstable Release: <a href="#0.10.5">0.10.5 steel-fist</a>
|
||||
|
||||
<a name="0.10.5"></a>
|
||||
# 0.10.5 steel-fist (11-11-08) #
|
||||
|
||||
## Features:
|
||||
|
||||
- [ng:autobind]: drop angular.js file name restrictions
|
||||
([commit](https://github.com/angular/angular.js/commit/d7ba5bc83ba9a8937384ea677331c5156ed6772d))
|
||||
- [Scope]: better logging of infinite digest error
|
||||
([commit](https://github.com/angular/angular.js/commit/ef875ad0cf4349144cb4674e050dd160564f6dd9),
|
||||
issue [#621](https://github.com/angular/angular.js/issues/621))
|
||||
- enable [widget] styling in IE8 and below using
|
||||
[html5shiv](http://code.google.com/p/html5shiv/)-like approach
|
||||
([commit](https://github.com/angular/angular.js/commit/163c799effd5cfadc57990f4d4127651bae3fbdb),
|
||||
issue [#584](https://github.com/angular/angular.js/issues/584))
|
||||
- [ng:style]: compatibility + perf improvements
|
||||
([commit](https://github.com/angular/angular.js/commit/e2663f62b0fbb8b9ce2e706b821a135e0bc7e885))
|
||||
|
||||
|
||||
## Bug Fixes:
|
||||
- [ng:view]: ignore stale xhr callbacks - fixes issues caused by race-conditions which occured when
|
||||
user navigated to a new route before the current route finished loading
|
||||
(issue [#619](https://github.com/angular/angular.js/issues/619))
|
||||
- [ng:form] should always be a block level (css) element
|
||||
([commit](https://github.com/angular/angular.js/commit/02dc81bae0011b7ae4190363be5fdd5db420aca9))
|
||||
- Fixes for [e2e test runner]'s `$location` dsl
|
||||
([commit](https://github.com/angular/angular.js/commit/dc8ffa51b7ebe5fb9bc1c89087c8b3c9e65d1006))
|
||||
- [ng:repeat] when iterating over arrays ignore non-array properties + when iterating over objects
|
||||
sort keys alphabetically
|
||||
([commit](https://github.com/angular/angular.js/commit/3945f884c5777e629b57c9ab0e93b9d02b9840d0))
|
||||
|
||||
## Docs:
|
||||
- experimental [disqus.com](http://disqus.com/) integration for all docs-next.angularjs.org pages
|
||||
([commit](https://github.com/angular/angular.js/commit/28ed5ba46595a371bd734b92a6e4bb40d1013741),
|
||||
contributed by Dan Doyon)
|
||||
- [e2e test runner] docs were moved to the dev guide
|
||||
|
||||
|
||||
<a name="0.10.4"></a>
|
||||
# 0.10.4 human-torch (2011-10-22) #
|
||||
|
||||
## Features:
|
||||
|
||||
- New validation options for
|
||||
[input widgets](http://docs-next.angularjs.org/api/angular.widget.input): `ng:minlength` and
|
||||
`ng:maxlength`
|
||||
([commit](https://github.com/angular/angular.js/commit/78f394fd17be581c84ecd526bb786ed1681d35cb))
|
||||
(contributed by Konstantin Stepanov)
|
||||
- HTML sanitizer was updated to recognize all safe HTML5 elements
|
||||
(Issue [#89](https://github.com/angular/angular.js/issues/89))
|
||||
- [ng:options]' blank option is now compiled and data-bound as any other template
|
||||
(Issue [#562](https://github.com/angular/angular.js/issues/562))
|
||||
(contributed by tehek)
|
||||
- [$defer](http://docs-next.angularjs.org/api/angular.service.$defer) service now exposes `cancel`
|
||||
method for task cancellation
|
||||
([commit](https://github.com/angular/angular.js/commit/ad90c3574f8365ee4a1a973d5e43c64fe9fcda2c))
|
||||
|
||||
|
||||
## Bug Fixes:
|
||||
|
||||
- [ng:options] should select correct element when '?'-option (invalid value) was previously selected
|
||||
(Issue [#599](https://github.com/angular/angular.js/issues/599)) (contributed by Tehek)
|
||||
- Fix data-binding of radio button's value property
|
||||
(Issue [#316](https://github.com/angular/angular.js/issues/316))
|
||||
- Input with type `password` should no be turned into a readable text field
|
||||
([commit](https://github.com/angular/angular.js/commit/e82e64d57b65d9f3c4f2e8831f30b615a069b7f6))
|
||||
(contributed by Konstantin Stepanov)
|
||||
- [ng:repeat] should ignore object properties starting with `$`
|
||||
([commit](https://github.com/angular/angular.js/commit/833eb3c84445110dc1dad238120573f08ed8d102))
|
||||
- Correctly parse out inlined regexp from the input field's `ng:pattern` attribute.
|
||||
([commit](https://github.com/angular/angular.js/commit/5d43439dbe764a4c7227f51b34a81b044f13901b))
|
||||
- $location service in html5 mode should correctly rewrite links that contain nested elements
|
||||
([commit](https://github.com/angular/angular.js/commit/9b85757102fbd44e88d0a3909fdf8b90f191b593))
|
||||
|
||||
|
||||
## Breaking Changes:
|
||||
|
||||
- the [date] filter now uses 'mediumDate' format if none is specified. This was done to deal with
|
||||
browser inconsistencies (each browser used to use different format)
|
||||
(Issue [#605](https://github.com/angular/angular.js/issues/605),
|
||||
[commit](https://github.com/angular/angular.js/commit/c6c3949b14f4003ecab291243edfca61262f2c3d),
|
||||
[commit](https://github.com/angular/angular.js/commit/e175db37c6f52bba4080efeec22a7120a896099e))
|
||||
- calling the linker function returned by [angular.compile][compile] doesn't automatically run
|
||||
`$digest` on the linked scope any more. This behavior was briefly introduced in 0.10.3 but was
|
||||
causing issues and inefficiencies in production apps so we reverted it. See:
|
||||
[commit](https://github.com/angular/angular.js/commit/f38010d3a2f457a53798212ef72418637dabe189)
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.10.3"></a>
|
||||
# 0.10.3 shattering-heartbeat (2011-10-13) #
|
||||
|
||||
## Features:
|
||||
|
||||
- New forms, validation, support for HTML5 input widgets. Please check out:
|
||||
- [Forms overview](http://docs-next.angularjs.org/guide/dev_guide.forms)
|
||||
- [form widget](http://docs-next.angularjs.org/api/angular.widget.form)
|
||||
- [input widget](http://docs-next.angularjs.org/api/angular.widget.input)
|
||||
- [$formFactory service](http://docs-next.angularjs.org/api/angular.service.$formFactory)
|
||||
- [angular.inputType](http://docs-next.angularjs.org/api/angular.inputType)
|
||||
- [commit](https://github.com/angular/angular.js/commit/4f78fd692c0ec51241476e6be9a4df06cd62fdd6)
|
||||
|
||||
- [ng:repeat] now has element-model affinity, which makes it more friendly to third-party code that
|
||||
is not aware of angular's DOM manipulation. This is also the pre-requisite for supporting
|
||||
animations.
|
||||
([commit](https://github.com/angular/angular.js/commit/75f11f1fc46c35a28c0905f7316ea6779145e2fb))
|
||||
|
||||
|
||||
## Bug Fixes:
|
||||
|
||||
- The select widget with [ng:options] directive now correctly displays selected option (regression
|
||||
from 0.10.2).
|
||||
- Fix for jqLite's removeClass, which under certain circumstances could clobber class names.
|
||||
([commit](https://github.com/angular/angular.js/commit/b96e978178a6acbf048aa6db466ed845e1395445))
|
||||
- Other small fixes and documentation improvements.
|
||||
|
||||
|
||||
## Breaking Changes:
|
||||
|
||||
- Due to changes in how forms and validation works the following were replaced with new apis:
|
||||
- `angular.formatter` - use `angular.inputType` or form's `$createWidget`
|
||||
- `angular.validator` - use `angular.inputType` or form's `$createWidget`
|
||||
- changes to `<input>` and `<select>` elements
|
||||
- `ng:model` directive is now required for data-binding to kick in
|
||||
- the `name` attribute is now optional and is used only as an alias when accessing the input
|
||||
widget via the form object.
|
||||
- view can't affect the model without a user interaction, so the `value` attribute of the
|
||||
`<input>` element and `selected` attribute of the `<option>` element if specified in the
|
||||
template is ignored.
|
||||
- Removed decoration of DOM elements when:
|
||||
- an exception occurs - when an exception happens, it will be passed to the $exceptionHandler
|
||||
service, which can decide what to do with it.
|
||||
- an input widget contains invalid input - in this case the forms validation apis can be used to
|
||||
display a customized error message.
|
||||
- The $hover service was removed (it was needed only for the DOM decoration described above).
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.10.2"></a>
|
||||
# 0.10.2 sneaky-seagull (2011-10-08) #
|
||||
|
||||
## Features:
|
||||
|
||||
- jQuery 1.6.4 support (Issue [#556](https://github.com/angular/angular.js/issues/556))
|
||||
- [jqLite](http://docs-next.angularjs.org/api/angular.element) improvements:
|
||||
- Added support for `prop` method
|
||||
([commit](https://github.com/angular/angular.js/commit/3800d177030d20c5c3d04e3601f892c46e723dc2))
|
||||
- Added support for `unbind` method
|
||||
([commit](https://github.com/angular/angular.js/commit/6b7ddf414de82720bbf547b2fa661bf5fcec7bb6))
|
||||
|
||||
|
||||
## Bug Fixes:
|
||||
|
||||
- Added support for short-circuiting of && and || operators in in angular expressions
|
||||
(Issue [#433](https://github.com/angular/angular.js/issues/433))
|
||||
- Fix for [$limitTo] to properly handle excessive limits (contributed by tehek)
|
||||
(Issue [#571](https://github.com/angular/angular.js/issues/571))
|
||||
- [jqLite]'s css() method now converts dash-separated css property names to camelCase in order to
|
||||
support dash-separated properties on Firefox
|
||||
(Issue [#569](https://github.com/angular/angular.js/issues/569))
|
||||
- action defaults for [$resource]s now take precedence over resource defaults (contributed by
|
||||
Marcello Nuccio)
|
||||
([commit](https://github.com/angular/angular.js/commit/bf5e5f7bc9ebc7dc6cf8fdf3c4923498b22a8654))
|
||||
- Fixed escaping issues in [$route] matcher
|
||||
([commit](https://github.com/angular/angular.js/commit/2bc39bb0b4f81b77597bb52f8572d231cf4f83e2))
|
||||
- Fixed two issues in $browser.defer.cancel mock
|
||||
([commit](https://github.com/angular/angular.js/commit/62ae7fccbc524ff498779564294ed6e1a7a3f51c),
|
||||
[commit](https://github.com/angular/angular.js/commit/8336f3f0ba89b529057027711ab4babd6c2cb649))
|
||||
- Fix for ng:options, which under certain circumstances didn't select the right option element
|
||||
([commit](https://github.com/angular/angular.js/commit/555f4152909e1c0bd5400737a62dc5d63ecd32d3))
|
||||
|
||||
|
||||
## Docs:
|
||||
|
||||
- migrated the docs app to use [$location]'s HTML5 mode (hashbang urls no more)
|
||||
([commit](https://github.com/angular/angular.js/commit/13f92de6246a0af8450fde84b209211a56397fda))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- If Angular is being used with jQuery older than 1.6, some features might not work properly. Please
|
||||
upgrade to jQuery version 1.6.4.
|
||||
|
||||
## Breaking Changes
|
||||
- ng:repeat no longer has ng:repeat-index property. This is because the elements now have
|
||||
affinity to the underlying collection, and moving items around in the collection would move
|
||||
ng:repeat-index property rendering it meaningless.
|
||||
|
||||
|
||||
<a name="0.10.1"></a>
|
||||
# 0.10.1 inexorable-juggernaut (2011-09-09) #
|
||||
|
||||
## Features
|
||||
|
||||
- complete rewrite of the $location service with HTML5 support, many API and semantic changes.
|
||||
Please see:
|
||||
- [$location service API docs](http://docs-next.angularjs.org/#!/api/angular.service.$location)
|
||||
- [$location service dev guide article](http://docs-next.angularjs.org/#!/guide/dev_guide.services.$location)
|
||||
- [location.js source file](https://github.com/angular/angular.js/blob/master/src/service/location.js)
|
||||
- breaking changes section of this changelog
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- $xhr should not covert HTTP status 0 to 200
|
||||
([commit](https://github.com/angular/angular.js/commit/b0eb831bce7d0ea066fd0758124793ed3db6d692))
|
||||
- fixed several doc examples that were broken on IE
|
||||
- ng:change should be called after the new val is set
|
||||
(Issue [#547](https://github.com/angular/angular.js/issues/547))
|
||||
- currency filter should return an empty string for non-numbers
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- $location related changes - for complete list of api changes see:
|
||||
[Migrating from earlier AngularJS releases](http://docs-next.angularjs.org/#!/guide/dev_guide.services.$location)
|
||||
- $location api changes:
|
||||
- $location.href -> $location.absUrl()
|
||||
- $location.hash -> $location.url()
|
||||
- $location.hashPath -> $location.path()
|
||||
- $location.hashSearch -> $location.search()
|
||||
- $location.search -> no equivalent, use $window.location.search (this is so that we can work in
|
||||
hashBang and html5 mode at the same time, check out the docs)
|
||||
- $location.update() / $location.updateHash() -> use $location.url()
|
||||
- n/a -> $location.replace() - new api for replacing history record instead of creating a new one
|
||||
|
||||
- $location semantic changes:
|
||||
- all url pieces are always in sync ($location.path(), $location.url(), $location.search(), ...) -
|
||||
this was previously true only if you used update* methods instead of direct assignment
|
||||
($location.hashPath = 'foo')
|
||||
- we now use (window.history.pushState || onHashChange event || polling) for detecting url changes
|
||||
in the browser (we use the best one available).
|
||||
|
||||
|
||||
|
||||
<a name="0.10.0"></a>
|
||||
# 0.10.0 chicken-hands (2011-09-02) #
|
||||
|
||||
## Features
|
||||
|
||||
- complete rewrite of the Scope implementation with several API and semantic changes. Please see:
|
||||
- [angular.scope API docs](http://docs-next.angularjs.org/#!/api/angular.scope)
|
||||
- [scopes dev guide article](http://docs-next.angularjs.org/#!/guide/dev_guide.scopes)
|
||||
- [scope.js source file](https://github.com/angular/angular.js/blob/master/src/Scope.js)
|
||||
- breaking changes section of this changelog
|
||||
- added event system to scopes (see [$on], [$emit] and [$broadcast])
|
||||
- added i18n and l10n support for date, currency and number filters see [i18n] docs for more info
|
||||
- added localizable [ng:pluralize] widget
|
||||
- added [ng:cloak] directive for hiding uncompiled templates
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- make [ng:class] friendly towards other code adding/removing classes
|
||||
([commit](https://github.com/angular/angular.js/commit/2a8fe56997fddbad673748ce02abf649a709c4ca))
|
||||
- several [jqLite] bugfixes and improvements
|
||||
- [ng:href], [ng:src] and friends now work properly when no expression is present in the attribute
|
||||
value.
|
||||
(Issue [#534](https://github.com/angular/angular.js/issues/534))
|
||||
- expose missing [lowercase], [uppercase] and [isDate] APIs.
|
||||
|
||||
|
||||
## Docs
|
||||
|
||||
- many (but not all just yet) api docs were proof-read and improved
|
||||
|
||||
|
||||
## Breaking Changes:
|
||||
|
||||
- many scope related changes:
|
||||
- $onEval is no more (use $watch with a fn as the only param if you really miss it)
|
||||
- $eval without params doesn't trigger model mutation observations (use $apply/$digest instead)
|
||||
- $digest propagates through the scope tree automatically (this is the desired behavior anyway)
|
||||
- $watch various API changes
|
||||
- scope is now the first argument passed into the $watch listener
|
||||
- `this` in the $watch listener is undefined instead of current scope
|
||||
- objects and arrays are watched and compared by equality and not just identity
|
||||
- the initial execution of the $watch listener now executes asynchronously with respect to the
|
||||
code registering it via $watch
|
||||
- exceptionHandler argument is no more
|
||||
- initRun argument is no more
|
||||
- angular.scope does not create child scopes by taking parent as the first argument - use $new
|
||||
instead
|
||||
- scope.$set and scope.$get were removed, use direct property assignment instead or $eval
|
||||
- $route.onChange was removed and replaced with $beforeRouteChange, $afterRouteChange and
|
||||
$routeUpdate events that can be used together with the new $routeParams service
|
||||
- `angular.equals()` now uses `===` instead of `==` when comparing primitives
|
||||
|
||||
|
||||
|
||||
<a name="0.9.19"></a>
|
||||
# 0.9.19 canine-psychokinesis (2011-08-20) #
|
||||
|
||||
## Features
|
||||
- added error handling support for JSONP requests (see error callback param of the [$xhr] service)
|
||||
([commit](https://github.com/angular/angular.js/commit/05e2c3196c857402a9aa93837b565e0a2736af23))
|
||||
- exposed http response headers in the [$xhr] and [$resource] callbacks
|
||||
([commit](https://github.com/angular/angular.js/commit/4ec1d8ee86e3138fb91543ca0dca28463895c090)
|
||||
contributed by Karl Seamon)
|
||||
- added `reloadOnSearch` [$route] param support to prevent unnecessary controller reloads and
|
||||
resulting flicker
|
||||
([commit](https://github.com/angular/angular.js/commit/e004378d100ce767a1107180102790a9a360644e))
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- fixed memory leak found in [ng:options] directive
|
||||
([commit](https://github.com/angular/angular.js/commit/6aa04b1db48853340d720e0a1a3e325ac523a06f))
|
||||
- make ng:class-even/odd compatible with ng:class
|
||||
(Issue [#508](https://github.com/angular/angular.js/issues/508))
|
||||
- fixed error handling for resources that didn't work in certain situations
|
||||
([commit](https://github.com/angular/angular.js/commit/c37bfde9eb31556ee1eb146795b0c1f1504a4a26)
|
||||
contributed by Karl Seamon)
|
||||
|
||||
|
||||
## Docs
|
||||
- [jsFiddle](http://jsfiddle.net/) integration for all docs.angularjs.org examples (contributed by
|
||||
Dan Doyon).
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
- removed [jqLite] show/hide support. See the
|
||||
[commit](https://github.com/angular/angular.js/commit/4c8eaa1eb05ba98d30ff83f4420d6fcd69045d99)
|
||||
message for details. Developers should use jquery or jqLite's `css('display', 'none')` and
|
||||
`css('display', 'block'/'inline'/..)` instead
|
||||
|
||||
|
||||
<a name="0.9.18"></a>
|
||||
# 0.9.18 jiggling-armfat (2011-07-29) #
|
||||
|
||||
### Features
|
||||
- [ECMAScript 5 Strict Mode](https://developer.mozilla.org/en/JavaScript/Strict_mode) compliance
|
||||
- [jqLite]
|
||||
- added `show()`, `hide()` and `eq()` methods to jqlite
|
||||
([commit](https://github.com/angular/angular.js/commit/7a3fdda9650a06792d9278a8cef06d544d49300f))
|
||||
- added $defer.cancel to support cancelation of tasks defered via the [$defer] service
|
||||
- [date] filter
|
||||
- added support for `full`, `long`, `medium` and `short` date-time format flags
|
||||
([commit](https://github.com/angular/angular.js/commit/3af1e7ca2ee8c2acd69e5bcbb3ffc1bf51239285))
|
||||
- added support for `z` flag, which stands for short string timezone identifier, e.g. PST
|
||||
- internal improvements to enable localization of date filter output
|
||||
- [number] filter
|
||||
- internal improvements to enable localization of number filter output
|
||||
- [currency] filter
|
||||
- support for custom currency symbols via an optional param
|
||||
- internal improvements to enable localization of number filter output
|
||||
- added [angular.version] for exposing the version of the loaded angular.js file
|
||||
- updated angular.js and angular.min.js file headers with angular version and shorter & updated
|
||||
license info
|
||||
- [ng:options]
|
||||
- support binding to expression (Issue [#449](https://github.com/angular/angular.js/issues/449))
|
||||
- support iterating over objects (Issue [#448](https://github.com/angular/angular.js/issues/448))
|
||||
- support ng:change (Issue [#463](https://github.com/angular/angular.js/issues/463))
|
||||
- support option groups (`<optgroup>`)
|
||||
(Issue [#450](https://github.com/angular/angular.js/issues/450))
|
||||
- [$xhr] and [$resource] support for per-request error callbacks (Issue
|
||||
[#408](https://github.com/angular/angular.js/issues/408)) (contributed by Karl Seamon)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- make injector compatible with Rhino (HtmlUnit) (contributed by Mårten Dolk)
|
||||
[commit](https://github.com/angular/angular.js/commit/77ba539f630c57b17d71dbf1e9c5667a7eb603b7)
|
||||
- `ie-compat.js` fixes and improvements related to fetching this file on the fly on legacy browsers
|
||||
- [jqLite]
|
||||
- fix `bind()` when binding to more events separated by space
|
||||
[commit](https://github.com/angular/angular.js/commit/9ee9ca13da3883d06733637f9048a83d94e6f1f8)
|
||||
- non-existing attributes should return undefined just like in jQuery
|
||||
[commit](https://github.com/angular/angular.js/commit/10da625ed93511dbf5d4e61ca4e42f6f2d478959)
|
||||
- set event.target for IE<8
|
||||
[commit](https://github.com/angular/angular.js/commit/ce80576e0b8ac9ed5a5b1f1a4dbc2446434a0002)
|
||||
- improved implementation of [ng:show] and [ng:hide] directives by using jqLite/jQuery hide and
|
||||
show methods
|
||||
- [ng:options]
|
||||
- fix incorrect re-growing of options on datasource change
|
||||
(Issue [#464](https://github.com/angular/angular.js/issues/464))
|
||||
|
||||
|
||||
### Docs
|
||||
- added full offline support for docs (click on the link in the footer of docs.angularjs.org)
|
||||
- many content improvements and corrections across all docs (reference api, tutorial, dev guide)
|
||||
- many small design improvements
|
||||
|
||||
|
||||
### Other
|
||||
- doubled our e2e test suite by running all angular e2e tests with jqLite in addition to jQuery
|
||||
|
||||
|
||||
### Breaking changes
|
||||
- [commit](https://github.com/angular/angular.js/commit/3af1e7ca2ee8c2acd69e5bcbb3ffc1bf51239285)
|
||||
removed support for the `MMMMM` (long month name), use `MMMM` instead. This was done to align
|
||||
Angular with
|
||||
[Unicode Technical Standard #35](http://unicode.org/reports/tr35/#Date_Format_Patterns) used by
|
||||
Closure, as well as, future DOM apis currently being proposed to w3c.
|
||||
- `$xhr.error`'s `request` argument has no `callback` property anymore, use `success` instead
|
||||
|
||||
|
||||
|
||||
<a name="0.9.17"></a>
|
||||
# <angular/> 0.9.17 vegetable-reanimation (2011-06-30) #
|
||||
|
||||
### New Features
|
||||
- New [ng:options] directive to better bind a model to `<select>` and `<option>` elements.
|
||||
- New [ng:disabled], [ng:selected], [ng:checked], [ng:multiple] and [ng:readonly] directives.
|
||||
- Added support for string representation of month and day in [date] filter.
|
||||
- Added support for `prepend()` to [jqLite].
|
||||
- Added support for configurable HTTP header defaults for the [$xhr] service.
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- Number filter would return incorrect value when fractional part had leading zeros.
|
||||
- Issue #338: Show error when template with with multiple DOM roots is being compiled.
|
||||
- Issue #399: return unsorted array if no predicate.
|
||||
- Fixed issues with incorrect value of $position in ng:repeat when collection size changes.
|
||||
- Fixed JSONP support in [$xhr] which didn't work without jquery since v0.9.13.
|
||||
|
||||
|
||||
### Documentation
|
||||
- various small fixes and improvements
|
||||
|
||||
|
||||
### Breaking changes
|
||||
- $service now has $service.invoke for method injection ($service(self, fn) no longer works)
|
||||
- injection name inference no longer supports method curry and linking functions. Both must be
|
||||
explicitly specified using $inject property.
|
||||
- Dynamic iteration (ng:repeat) on `<option>` elements is no longer supported. Use ng:options
|
||||
- Removal of index formatter (`ng:format="index"`) since its only use was with repeated `<options>`
|
||||
(see above).
|
||||
- Calling [$orderBy] without a predicate now returns the original unsorted array, instead of
|
||||
ordering by natural order.
|
||||
|
||||
|
||||
|
||||
<a name="0.9.16"></a>
|
||||
# <angular/> 0.9.16 weather-control (2011-06-07) #
|
||||
|
||||
### Features
|
||||
- [JsTD Scenario Adapter] for running scenario tests with jstd (from command line and in multiple
|
||||
browsers)
|
||||
|
||||
|
||||
### Documentation
|
||||
- brand new template for <http://docs.angularjs.org/>
|
||||
- brand new tutorial that describes how to build a typical angular app
|
||||
<http://docs.angularjs.org/#!/tutorial>
|
||||
- lots of new content for the dev guide (still work in progress)
|
||||
<http://docs.angularjs.org/#!/guide>
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- ng:href produces unclickable links on IE7 [#352](https://github.com/angular/angular.js/issues/352)
|
||||
- IE 8 in compatibility mode breaks routing [#353](https://github.com/angular/angular.js/issues/353)
|
||||
- IE translates a 204 response code to 1223 [#357](https://github.com/angular/angular.js/issues/357)
|
||||
- Fixed unit test in IE7 [#360](https://github.com/angular/angular.js/pull/360)
|
||||
- Fixed unit tests on FF4, Opera [#364](https://github.com/angular/angular.js/pull/364)
|
||||
- Fixed opera date.toISOString issue [#367](https://github.com/angular/angular.js/pull/367)
|
||||
|
||||
|
||||
### Breaking changes
|
||||
- html scenario runner requires ng:autotest script attribute to start tests automatically
|
||||
([example](https://github.com/angular/angular.js/blob/master/example/personalLog/scenario/runner.html#L5))
|
||||
|
||||
|
||||
|
||||
<a name="0.9.15"></a>
|
||||
# <angular/> 0.9.15 lethal-stutter (2011-04-11) #
|
||||
|
||||
### Features
|
||||
- IE9 support
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- reverted [ng:view] sync cache fix due to regression in the order of initialization of parent
|
||||
and child controllers. (commits 9bd2c396 and 3d388498)
|
||||
- [$resource] success callback is now executed whenever the http status code is `<200,300>`
|
||||
|
||||
|
||||
### Docs
|
||||
- fixed intentation code that caused some of the snippets on docs.angularjs.org to be mangled.
|
||||
- many small improvements of the api docs.
|
||||
|
||||
|
||||
|
||||
<a name="0.9.14"></a>
|
||||
# <angular/> 0.9.14 key-maker (2011-04-01) #
|
||||
|
||||
### Performance
|
||||
- [ng:repeat] grows (adds children) significantly faster. (commit 15ec78f5)
|
||||
- [$xhr.cache] optionally executes callbacks synchronously. (commit c06c5a36)
|
||||
- [ng:view] and [ng:include] use sync [$xhr.cache]
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed [$resource] encoding of query params. (commits e1d122a4, 78a0f410)
|
||||
|
||||
|
||||
### House cleaning
|
||||
- code cleanup
|
||||
- better minification (min is now 2.5% or almost 1kb smaller)
|
||||
- minor documentation fixes
|
||||
- JsTestDriver 1.3.2 upgrade with fixed coverage support
|
||||
|
||||
|
||||
|
||||
<a name="0.9.13"></a>
|
||||
# <angular/> 0.9.13 curdling-stare (2011-03-13) #
|
||||
|
||||
### New Features
|
||||
- Added XSRF protection for the [$xhr] service. (commit c578f8c3)
|
||||
- Targeted auto-bootstrap — [ng:autobind] now takes an optional value which specifies an element id
|
||||
to be compiled instead of compiling the entire html document. (commit 9d5c5337)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed IE7 regression which prevented angular from bootstrapping in this browser.
|
||||
- Cookies which contain unescaped '=' are now visible via the [$cookies] service. (commit 26bad2bf)
|
||||
- [$xhr] service now executes "success" callback for all 2xx responses, not just 200.
|
||||
(commit 5343deb3)
|
||||
- Always remove the script tag after successful JSONP request. (commit 0084cb5c)
|
||||
- Removal of all `document.write` statements to make angular compabile with async script loaders.
|
||||
(commit 3224862a)
|
||||
|
||||
|
||||
### Breaking changes
|
||||
- The `post` parameter of [$browser.xhr][$browser] is now non-optional. Since everyone should be
|
||||
using the [$xhr] service instead of $browser.xhr, this should not break anyone. If you do use
|
||||
$browser.xhr then just add null for the post value argument where post was not passed in.
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.9.12"></a>
|
||||
# <angular/> 0.9.12 thought-implanter (2011-03-03) #
|
||||
|
||||
### API
|
||||
- Added a delay parameter to the [$defer] service. (commit edbe9d8c)
|
||||
- Added `scope()` method to [angular.element][element] (jQuery) instances to retrieve a [scope]
|
||||
associated with a given DOM element. (commit 0a5c00ab)
|
||||
- Added inference of DI dependencies from function signature. This feature is experimental, check
|
||||
out [dependency injection][guide.di] docs. (commit 7d4aee31)
|
||||
|
||||
|
||||
### New Features
|
||||
- Angular now correctly recognizes and uses jQuery even if it was loaded after angular's script.
|
||||
More info at [angular.element][element]. (commit a004d487)
|
||||
- All built-in angular services are now lazy-loaded. (commit a070ff5a)
|
||||
- To make styling of custom html tags created via [widgets][widget] and [directives][directive]
|
||||
easier, all of these elements now contain a css class with name in form of
|
||||
`<namespace>-<directive/widget name>`, e.g. `<ng:include class="ng-include">`. (commit c7998f5f)
|
||||
- [$xhr] service now automatically detects and strips google-style JSON security prefix from http
|
||||
responses. (commit cd139f57)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- Rewrite of JQuery lite implementation for better supports operations on multiple nodes when
|
||||
matched by a selector and remove other bugs. (commit 00cc9eb3)
|
||||
- Corrected an issue where properties inherited from \_\_proto\_\_ show up in ng:repeat.
|
||||
(commit 9e67da42)
|
||||
- Fixed url encoding issue affecting [$resource] service. (commits e9ce2259 + 9e30baad)
|
||||
- Removed `$eval()` call from the [$cookies] factory function, which was causing duplicate
|
||||
instances of singleton services to be created. (commit 65585a2d)
|
||||
|
||||
|
||||
### Docs
|
||||
- New docs [contribution guidelines][contribute].
|
||||
- New [description of release artifacts][downloading].
|
||||
- Lots of improvements and other new content.
|
||||
|
||||
|
||||
### Breaking changes
|
||||
- Removed the `$init()` method that used to be called after compilation of a template. This should
|
||||
affect only fraction of angular apps because the api was primarily being used by low level widgets
|
||||
tests.
|
||||
|
||||
The old way of compiling the DOM element was angular.compile(element).$init(); The $init was there
|
||||
to allow the users to do any work to the scope before the view would be bound. This is a left over
|
||||
from not having proper MVC. The new recommended way to deal with initializing scope is to put it
|
||||
in the root constructor controller. To migrate simply remove the call to $init() and move any code
|
||||
you had before $init() to the root controller.
|
||||
|
||||
(commit 23b255a8)
|
||||
- Changed [angular.compile][compile] API from `angular.compile(element[, scope])` to
|
||||
`angular.compile(element)([scope], [cloneAttachFn])` (commits ef4bb28b + 945056b1)
|
||||
- Removed ng:watch directives since it encourages logic in the UI. (commit 87cbf9f5)
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.9.11"></a>
|
||||
# <angular/> 0.9.11 snow-maker (2011-02-08) #
|
||||
|
||||
### Documentation
|
||||
- completed migration of docs from the wiki site to
|
||||
[http://docs.angularjs.org/](http://docs.angularjs.org/)
|
||||
- many, but by far not all, docs were updated, improved and cleaned up
|
||||
|
||||
### Features
|
||||
- [$route] service now supports these features:
|
||||
- route not found handling via `#otherwise()`
|
||||
- redirection support via `#when('/foo', {redirectTo: '/bar'})` (including param interpolation)
|
||||
- setting the parent scope for scopes created by the service via `#parent()`
|
||||
- reloading the current route via `#reload()`
|
||||
|
||||
### API
|
||||
- added `angular.element(...).scope()` method to retrieve scope for a given element.
|
||||
|
||||
### Bug Fixes
|
||||
- <option> value attribute gets clobbered when the element contains new line character(s).
|
||||
- <ng:view> widget now works when nested inside an <ng:include> widget
|
||||
- other various small fixes
|
||||
|
||||
### Breaking changes
|
||||
- mock [`$browser`](http://docs.angularjs.org/#!/api/angular.mock.service.$browser) now throws an
|
||||
exception if the `flush()` method is called when there are no requests to be flushed. If you
|
||||
experience `No xhr requests to be flushed!` errors in your tests, it's because you called
|
||||
`$browser.xhr.flush()` unexpectedly. To make the error go away, either make sure your code makes a
|
||||
request via the `$xhr` service or remove all unneeded `flush()` calls.
|
||||
|
||||
|
||||
<a name="0.9.10"></a>
|
||||
# <angular/> 0.9.10 flea-whisperer (2011-01-26) #
|
||||
|
||||
### Features
|
||||
- new [`ng:view`](http://docs.angularjs.org/#!angular.widget.ng:view) widget to simplify integration
|
||||
- new [`ng:view`](http://docs.angularjs.org/#!/api/angular.widget.ng:view) widget to simplify integration
|
||||
with the `$route` service
|
||||
- the content of all standard HTML widgets is now being processed
|
||||
(e.g. `<button>{{foo}}</button>` works now) (commit 1d7b9d56)
|
||||
- new [`$log`](http://docs.angularjs.org/#!angular.mock.service.$log) and
|
||||
[`$exceptionHandler`](http://docs.angularjs.org/#!angular.mock.service.$exceptionHandler) service
|
||||
- new [`$log`](http://docs.angularjs.org/#!/api/angular.mock.service.$log) and
|
||||
[`$exceptionHandler`](http://docs.angularjs.org/#!/api/angular.mock.service.$exceptionHandler) service
|
||||
mocks now part of `angular-mocks.js` (commit f5d08963)
|
||||
|
||||
### Bug Fixes
|
||||
@@ -21,6 +643,7 @@ with the `$route` service
|
||||
- lots of improvements related to formatting of the content of docs.anguarjs.org
|
||||
|
||||
|
||||
<a name="0.9.9"></a>
|
||||
# <angular/> 0.9.9 time-shift (2011-01-13) #
|
||||
|
||||
### Security
|
||||
@@ -51,8 +674,8 @@ with the `$route` service
|
||||
- angular.filter.date now properly handles some corner-cases (issue #159 - fix contributed by Vojta)
|
||||
|
||||
### Breaking changes
|
||||
- API for accessing registered services — `scope.$inject` — was renamed to
|
||||
[`scope.$service`](http://docs.angularjs.org/#!angular.scope.$service). (commit b2631f61)
|
||||
- API for accessing registered services — `scope.$inject` — was renamed to
|
||||
[`scope.$service`](http://docs.angularjs.org/#!/api/angular.scope.$service). (commit b2631f61)
|
||||
|
||||
- Support for `eager-published` services was removed. This change was done to make explicit
|
||||
dependency declaration always required in order to allow making relatively expensive services
|
||||
@@ -73,18 +696,21 @@ with the `$route` service
|
||||
(e.g. `ng:init="$location = $service('$location'), ...`) in the view or more correctly create
|
||||
a service like this:
|
||||
|
||||
angular.service('published-svc-shim', function() {
|
||||
this.$location = this.$service('$location');
|
||||
this.$route = this.$service('$route');
|
||||
this.$cookies = this.$service('$cookies');
|
||||
this.$window = this.$service('$window');
|
||||
this.$document = this.$service('$document');
|
||||
this.$exceptionHandler = this.$service('$exceptionHandler');
|
||||
this.$invalidWidgets = this.$service('$invalidWidgets');
|
||||
}, {$eager: true});
|
||||
angular.service('published-svc-shim', function($location, $route, $cookies, $window,
|
||||
$document, $exceptionHandler, $invalidWidgets) {
|
||||
this.$location = $location;
|
||||
this.$route = $route;
|
||||
this.$cookies = $cookies;
|
||||
this.$window = $window;
|
||||
this.$document = $document;
|
||||
this.$exceptionHandler = $exceptionHandler;
|
||||
this.$invalidWidgets = $invalidWidgets;
|
||||
}, {$inject: ['$location', '$route', '$cookies', '$window', '$document', '$exceptionHandler',
|
||||
'$invalidWidgets'],
|
||||
$eager: true});
|
||||
|
||||
- In the light of the `eager-published` change, to complete the cleanup we renamed `$creation`
|
||||
property of services to `eager` with its value being a boolean.
|
||||
property of services to `$eager` with its value being a boolean.
|
||||
To transition, please rename all `$creation: 'eager'` declarations to `$eager: true`.
|
||||
(commit 1430c6d6)
|
||||
|
||||
@@ -93,6 +719,7 @@ with the `$route` service
|
||||
- The `toString` method of the `angular.service.$location` service was removed. (commit 23875cb3)
|
||||
|
||||
|
||||
<a name="0.9.8"></a>
|
||||
# <angular/> 0.9.8 astral-projection (2010-12-23) #
|
||||
|
||||
### Docs/Getting started
|
||||
@@ -106,6 +733,7 @@ with the `$route` service
|
||||
- Ignore input widgets which have no name (issue #153)
|
||||
|
||||
|
||||
<a name="0.9.7"></a>
|
||||
# <angular/> 0.9.7 sonic-scream (2010-12-10) #
|
||||
|
||||
### Bug Fixes
|
||||
@@ -124,6 +752,7 @@ with the `$route` service
|
||||
your controllers. (commit e5e69d9b90850eb653883f52c76e28dd870ee067)
|
||||
|
||||
|
||||
<a name="0.9.6"></a>
|
||||
# <angular/> 0.9.6 night-vision (2010-12-06) #
|
||||
|
||||
### Security
|
||||
@@ -153,6 +782,7 @@ with the `$route` service
|
||||
- The HTML sanitizer is slightly more strinct now. Please see info in the "Security" section above.
|
||||
|
||||
|
||||
<a name="0.9.5"></a>
|
||||
# <angular/> 0.9.5 turkey-blast (2010-11-25) #
|
||||
|
||||
### Docs
|
||||
@@ -162,6 +792,7 @@ with the `$route` service
|
||||
- added `angular.Array.limitTo` to make it easy to select first or last few items of an array
|
||||
|
||||
|
||||
<a name="0.9.4"></a>
|
||||
# <angular/> 0.9.4 total-recall (2010-11-18) #
|
||||
|
||||
### Docs
|
||||
@@ -178,6 +809,7 @@ with the `$route` service
|
||||
- Better error handling - compilation exception now contain stack trace (commit b2d63ac4)
|
||||
|
||||
|
||||
<a name="0.9.3"></a>
|
||||
# <angular/> 0.9.3 cold-resistance (2010-11-10) #
|
||||
|
||||
### Docs
|
||||
@@ -205,6 +837,7 @@ with the `$route` service
|
||||
simple RegExp validator.
|
||||
|
||||
|
||||
<a name="0.9.2"></a>
|
||||
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
|
||||
|
||||
### Docs
|
||||
@@ -242,6 +875,7 @@ with the `$route` service
|
||||
implements HEAD
|
||||
|
||||
|
||||
<a name="0.9.1"></a>
|
||||
# <angular/> 0.9.1 repulsion-field (2010-10-26) #
|
||||
|
||||
### Security
|
||||
@@ -268,6 +902,7 @@ with the `$route` service
|
||||
- html filter now sanitizes html content for XSS attacks which may result in different behavior
|
||||
|
||||
|
||||
<a name="0.9.0"></a>
|
||||
# <angular/> 0.9.0 dragon-breath (2010-10-20) #
|
||||
|
||||
### Security
|
||||
@@ -297,3 +932,57 @@ with the `$route` service
|
||||
|
||||
### Big Thanks to Our Community Contributors
|
||||
- Vojta Jina
|
||||
|
||||
|
||||
|
||||
|
||||
[lowercase]: http://docs.angularjs.org/#!/api/angular.lowercase
|
||||
[uppercase]: http://docs.angularjs.org/#!/api/angular.uppercase
|
||||
[isDate]: http://docs.angularjs.org/#!/api/angular.isDate
|
||||
[scope]: http://docs.angularjs.org/#!/api/angular.scope
|
||||
[compile]: http://docs.angularjs.org/#!/api/angular.compile
|
||||
[element]: http://docs.angularjs.org/#!/api/angular.element
|
||||
[widget]: http://docs.angularjs.org/#!/api/angular.widget
|
||||
[ng:repeat]: http://docs.angularjs.org/#!/api/angular.widget.@ng:repeat
|
||||
[ng:view]: http://docs.angularjs.org/#!/api/angular.widget.ng:view
|
||||
[ng:include]: http://docs.angularjs.org/#!/api/angular.widget.ng:include
|
||||
[ng:options]: http://docs.angularjs.org/#!/api/angular.directive.ng:options
|
||||
[ng:disabled]: http://docs.angularjs.org/#!/api/angular.directive.ng:disabled
|
||||
[ng:selected]: http://docs.angularjs.org/#!/api/angular.directive.ng:selected
|
||||
[ng:checked]: http://docs.angularjs.org/#!/api/angular.directive.ng:checked
|
||||
[ng:multiple]: http://docs.angularjs.org/#!/api/angular.directive.ng:multiple
|
||||
[ng:readonly]: http://docs.angularjs.org/#!/api/angular.directive.ng:readonly
|
||||
[ng:show]: http://docs.angularjs.org/#!/api/angular.directive.ng:show
|
||||
[ng:hide]: http://docs.angularjs.org/#!/api/angular.directive.ng:hide
|
||||
[ng:class]: http://docs.angularjs.org/#!/api/angular.directive.ng:class
|
||||
[ng:src]: http://docs.angularjs.org/#!/api/angular.directive.ng:src
|
||||
[ng:href]: http://docs.angularjs.org/#!/api/angular.directive.ng:href
|
||||
[ng:style]: http://docs.angularjs.org/#!/api/angular.directive.ng:style
|
||||
[$defer]: http://docs.angularjs.org/#!/api/angular.service.$defer
|
||||
[$cookies]: http://docs.angularjs.org/#!/api/angular.service.$cookies
|
||||
[$xhr]: http://docs.angularjs.org/#!/api/angular.service.$xhr
|
||||
[$xhr.cache]: http://docs.angularjs.org/#!/api/angular.service.$xhr.cache
|
||||
[$resource]: http://docs.angularjs.org/#!/api/angular.service.$resource
|
||||
[$route]: http://docs.angularjs.org/#!/api/angular.service.$route
|
||||
[$orderBy]: http://docs.angularjs.org/#!/api/angular.Array.orderBy
|
||||
[date]: http://docs.angularjs.org/#!/api/angular.filter.date
|
||||
[number]: http://docs.angularjs.org/#!/api/angular.filter.number
|
||||
[currency]: http://docs.angularjs.org/#!/api/angular.filter.currency
|
||||
[directive]: http://docs.angularjs.org/#!/api/angular.directive
|
||||
[ng:autobind]: http://docs.angularjs.org/#!/api/angular.directive.ng:autobind
|
||||
[guide.di]: http://docs.angularjs.org/#!/guide/dev_guide.di
|
||||
[downloading]: http://docs.angularjs.org/#!/misc/downloading
|
||||
[contribute]: http://docs.angularjs.org/#!/misc/contribute
|
||||
[jqLite]: http://docs.angularjs.org/#!/api/angular.element
|
||||
[angular.version]: http://docs.angularjs.org/#!/api/angular.version
|
||||
[Jstd Scenario Adapter]: https://github.com/angular/angular.js/blob/master/src/jstd-scenario-adapter/Adapter.js
|
||||
[i18n]: http://docs-next.angularjs.org/#!/guide/dev_guide.i18n
|
||||
[ng:pluralize]: http://docs-next.angularjs.org/#!/api/angular.widget.ng:pluralize
|
||||
[ng:form]: http://docs-next.angularjs.org/api/angular.widget.form
|
||||
[ng:cloak]: http://docs-next.angularjs.org/#!/api/angular.directive.ng:cloak
|
||||
[$on]: http://docs-next.angularjs.org/#!/api/angular.scope.$on
|
||||
[$emit]: http://docs-next.angularjs.org/#!/api/angular.scope.$emit
|
||||
[$broadcast]: http://docs-next.angularjs.org/#!/api/angular.scope.$broadcast
|
||||
[$limitTo]: http://docs-next.angularjs.org/api/angular.Array.limitTo
|
||||
[$location]: http://docs-next.angularjs.org/api/angular.service.$location
|
||||
[e2e test runner]: http://docs-next.angularjs.org/guide/dev_guide.e2e-testing
|
||||
|
||||
@@ -1,43 +1,8 @@
|
||||
require 'yaml'
|
||||
include FileUtils
|
||||
|
||||
ANGULAR = [
|
||||
'src/Angular.js',
|
||||
'src/JSON.js',
|
||||
'src/Compiler.js',
|
||||
'src/Scope.js',
|
||||
'src/Injector.js',
|
||||
'src/parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/sanitizer.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
'src/formatters.js',
|
||||
'src/validators.js',
|
||||
'src/services.js',
|
||||
'src/directives.js',
|
||||
'src/markups.js',
|
||||
'src/widgets.js',
|
||||
'src/AngularPublic.js',
|
||||
]
|
||||
|
||||
ANGULAR_SCENARIO = [
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js',
|
||||
]
|
||||
content = File.open('angularFiles.js', 'r') {|f| f.read }
|
||||
files = eval(content.gsub(/angularFiles = /, '').gsub(/:/, '=>'));
|
||||
|
||||
BUILD_DIR = 'build'
|
||||
|
||||
@@ -47,6 +12,16 @@ task :default => [:compile, :test]
|
||||
desc 'Init the build workspace'
|
||||
task :init do
|
||||
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
|
||||
|
||||
v = YAML::load( File.open( 'version.yaml' ) )
|
||||
match = v['version'].match(/^([^-]*)(-snapshot)?$/)
|
||||
|
||||
NG_VERSION = Struct.new(:full, :major, :minor, :dot, :codename).
|
||||
new(match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : ''),
|
||||
match[1].split('.')[0],
|
||||
match[1].split('.')[1],
|
||||
match[1].split('.')[2],
|
||||
v['codename'])
|
||||
end
|
||||
|
||||
|
||||
@@ -61,145 +36,198 @@ desc 'Compile Scenario'
|
||||
task :compile_scenario => :init do
|
||||
|
||||
deps = [
|
||||
'lib/jquery/jquery-1.4.2.js',
|
||||
'lib/jquery/jquery.js',
|
||||
'src/scenario/angular.prefix',
|
||||
ANGULAR,
|
||||
ANGULAR_SCENARIO,
|
||||
files['angularSrc'],
|
||||
files['angularScenario'],
|
||||
'src/scenario/angular.suffix',
|
||||
]
|
||||
|
||||
concat = 'cat ' + deps.flatten.join(' ')
|
||||
|
||||
File.open(path_to('angular-scenario.js'), 'w') do |f|
|
||||
f.write(%x{#{concat}})
|
||||
f.write(%x{#{concat}}.gsub('"NG_VERSION_FULL"', NG_VERSION.full))
|
||||
f.write(gen_css('css/angular.css') + "\n")
|
||||
f.write(gen_css('css/angular-scenario.css'))
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Compile JSTD Scenario Adapter'
|
||||
task :compile_jstd_scenario_adapter => :init do
|
||||
|
||||
desc 'Generate IE css js patch'
|
||||
task :generate_ie_compat => :init do
|
||||
css = File.open('css/angular.css', 'r') {|f| f.read }
|
||||
deps = [
|
||||
'src/jstd-scenario-adapter/angular.prefix',
|
||||
'src/jstd-scenario-adapter/Adapter.js',
|
||||
'src/jstd-scenario-adapter/angular.suffix',
|
||||
]
|
||||
|
||||
# finds all css rules that contain backround images and extracts the rule name(s), content type of
|
||||
# the image and base64 encoded image data
|
||||
r = /\n([^\{\n]+)\s*\{[^\}]*background-image:\s*url\("data:([^;]+);base64,([^"]+)"\);[^\}]*\}/
|
||||
concat = 'cat ' + deps.flatten.join(' ')
|
||||
|
||||
images = css.scan(r)
|
||||
File.open(path_to('jstd-scenario-adapter.js'), 'w') do |f|
|
||||
f.write(%x{#{concat}}.gsub('"NG_VERSION_FULL"', NG_VERSION.full))
|
||||
end
|
||||
|
||||
# create a js file with multipart header containing the extracted images. the entire file *must*
|
||||
# be CRLF (\r\n) delimited
|
||||
File.open(path_to('angular-ie-compat.js'), 'w') do |f|
|
||||
f.write("/*\r\n" +
|
||||
"Content-Type: multipart/related; boundary=\"_\"\r\n" +
|
||||
"\r\n")
|
||||
|
||||
images.each_index do |idx|
|
||||
f.write("--_\r\n" +
|
||||
"Content-Location:img#{idx}\r\n" +
|
||||
"Content-Transfer-Encoding:base64\r\n" +
|
||||
"\r\n" +
|
||||
images[idx][2] + "\r\n")
|
||||
end
|
||||
|
||||
f.write("--_--\r\n" +
|
||||
"*/\r\n")
|
||||
|
||||
# generate a css string containing *background-image rules for IE that point to the mime type
|
||||
# images in the header
|
||||
cssString = ''
|
||||
images.each_index do |idx|
|
||||
cssString += "#{images[idx][0]}{*background-image:url(\"mhtml:' + jsUri + '!img#{idx}\")}"
|
||||
end
|
||||
|
||||
# generate a javascript closure that contains a function which will append the generated css
|
||||
# string as a stylesheet to the current html document
|
||||
jsString = "(function(){ \r\n" +
|
||||
" var jsUri = document.location.href.replace(/\\/[^\/]+(#.*)?$/, '/') + " +
|
||||
" document.getElementById('ng-ie-compat').src; \r\n" +
|
||||
" var css = '#{cssString}' \r\n" +
|
||||
" var s = document.createElement('style'); \r\n" +
|
||||
" s.setAttribute('type', 'text/css'); \r\n" +
|
||||
" if (s.styleSheet) { \r\n" +
|
||||
" s.styleSheet.cssText = css; \r\n" +
|
||||
" } else { \r\n" +
|
||||
" s.appendChild(document.createTextNode(css)); \r\n" +
|
||||
" } \r\n" +
|
||||
" document.getElementsByTagName('head')[0].appendChild(s); \r\n" +
|
||||
"})();\r\n"
|
||||
|
||||
f.write(jsString)
|
||||
# TODO(vojta) use jstd configuration when implemented
|
||||
# (instead of including jstd-adapter-config.js)
|
||||
File.open(path_to('jstd-scenario-adapter-config.js'), 'w') do |f|
|
||||
f.write("/**\r\n" +
|
||||
" * Configuration for jstd scenario adapter \n */\n" +
|
||||
"var jstdScenarioAdapter = {\n relativeUrlPrefix: '/build/docs/'\n};\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc 'Compile JavaScript'
|
||||
task :compile => [:init, :compile_scenario, :generate_ie_compat] do
|
||||
task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter] do
|
||||
|
||||
deps = [
|
||||
'src/angular.prefix',
|
||||
ANGULAR,
|
||||
files['angularSrc'],
|
||||
'src/angular.suffix',
|
||||
]
|
||||
|
||||
File.open(path_to('angular.js'), 'w') do |f|
|
||||
concat = 'cat ' + deps.flatten.join(' ')
|
||||
f.write(%x{#{concat}})
|
||||
|
||||
content = %x{#{concat}}.
|
||||
gsub('"NG_VERSION_FULL"', NG_VERSION.full).
|
||||
gsub('"NG_VERSION_MAJOR"', NG_VERSION.major).
|
||||
gsub('"NG_VERSION_MINOR"', NG_VERSION.minor).
|
||||
gsub('"NG_VERSION_DOT"', NG_VERSION.dot).
|
||||
gsub('"NG_VERSION_CODENAME"', NG_VERSION.codename).
|
||||
gsub(/^\s*['"]use strict['"];?\s*$/, ''). # remove all file-specific strict mode flags
|
||||
gsub(/'USE STRICT'/, "'use strict'") # rename the placeholder in angular.prefix
|
||||
|
||||
f.write(content)
|
||||
f.write(gen_css('css/angular.css', true))
|
||||
end
|
||||
|
||||
%x(java -jar lib/compiler-closure/compiler.jar \
|
||||
%x(java -jar lib/closure-compiler/compiler.jar \
|
||||
--compilation_level SIMPLE_OPTIMIZATIONS \
|
||||
--language_in ECMASCRIPT5_STRICT \
|
||||
--js #{path_to('angular.js')} \
|
||||
--js_output_file #{path_to('angular.min.js')})
|
||||
|
||||
FileUtils.cp_r 'i18n/locale', path_to('i18n')
|
||||
end
|
||||
|
||||
|
||||
desc 'Generate docs'
|
||||
task :docs do
|
||||
task :docs => [:init] do
|
||||
`node docs/src/gen-docs.js`
|
||||
File.open(path_to('docs/.htaccess'), File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('"NG_VERSION_FULL"', NG_VERSION.full)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc 'Create angular distribution'
|
||||
task :package => [:clean, :compile, :docs] do
|
||||
v = YAML::load( File.open( 'version.yaml' ) )['version']
|
||||
match = v.match(/^([^-]*)(-snapshot)?$/)
|
||||
version = match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : '')
|
||||
tarball = "angular-#{NG_VERSION.full}.tgz"
|
||||
|
||||
tarball = "angular-#{version}.tgz"
|
||||
|
||||
pkg_dir = path_to("pkg/angular-#{version}")
|
||||
pkg_dir = path_to("pkg/angular-#{NG_VERSION.full}")
|
||||
FileUtils.rm_r(path_to('pkg'), :force => true)
|
||||
FileUtils.mkdir_p(pkg_dir)
|
||||
|
||||
['src/angular-mocks.js',
|
||||
path_to('angular.js'),
|
||||
path_to('angular.min.js'),
|
||||
path_to('angular-ie-compat.js'),
|
||||
path_to('angular-scenario.js')
|
||||
path_to('angular-scenario.js'),
|
||||
path_to('jstd-scenario-adapter.js'),
|
||||
path_to('jstd-scenario-adapter-config.js'),
|
||||
].each do |src|
|
||||
dest = src.gsub(/^[^\/]+\//, '').gsub(/((\.min)?\.js)$/, "-#{version}\\1")
|
||||
dest = src.gsub(/^[^\/]+\//, '').gsub(/((\.min)?\.js)$/, "-#{NG_VERSION.full}\\1")
|
||||
FileUtils.cp(src, pkg_dir + '/' + dest)
|
||||
end
|
||||
|
||||
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{version}"
|
||||
FileUtils.cp_r path_to('i18n'), "#{pkg_dir}/i18n-#{NG_VERSION.full}"
|
||||
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{NG_VERSION.full}"
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{version}/index.html", File::RDWR) do |f|
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{version}.min.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{version}/docs-scenario.html", File::RDWR) do |f|
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular-scenario.js', "angular-scenario-#{version}.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-nocache.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-nocache.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-debug.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-debug.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/docs-scenario.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular-scenario.js', "angular-scenario-#{NG_VERSION.full}.js")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache.manifest", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache-offline.manifest", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
%x(tar -czf #{path_to(tarball)} -C #{path_to('pkg')} .)
|
||||
|
||||
FileUtils.cp path_to(tarball), pkg_dir
|
||||
FileUtils.mv pkg_dir, path_to(['pkg', NG_VERSION.full])
|
||||
|
||||
puts "Package created: #{path_to(tarball)}"
|
||||
end
|
||||
|
||||
@@ -268,7 +296,7 @@ def gen_css(cssFile, minify = false)
|
||||
css.gsub! /'/, "\\\\'"
|
||||
css.gsub! /\n/, "\\n"
|
||||
|
||||
return %Q{document.write('<style type="text/css">#{css}</style>');}
|
||||
return %Q{angular.element(document).find('head').append('<style type="text/css">#{css}</style>');}
|
||||
end
|
||||
|
||||
|
||||
|
||||
Vendored
+152
@@ -0,0 +1,152 @@
|
||||
angularFiles = {
|
||||
'angularSrc': [
|
||||
'src/Angular.js',
|
||||
'src/JSON.js',
|
||||
'src/Compiler.js',
|
||||
'src/Scope.js',
|
||||
'src/Injector.js',
|
||||
'src/parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/sanitizer.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
'src/service/cookieStore.js',
|
||||
'src/service/cookies.js',
|
||||
'src/service/defer.js',
|
||||
'src/service/document.js',
|
||||
'src/service/exceptionHandler.js',
|
||||
'src/service/formFactory.js',
|
||||
'src/service/location.js',
|
||||
'src/service/log.js',
|
||||
'src/service/resource.js',
|
||||
'src/service/route.js',
|
||||
'src/service/routeParams.js',
|
||||
'src/service/sniffer.js',
|
||||
'src/service/window.js',
|
||||
'src/service/xhr.bulk.js',
|
||||
'src/service/xhr.cache.js',
|
||||
'src/service/xhr.error.js',
|
||||
'src/service/xhr.js',
|
||||
'src/service/locale.js',
|
||||
'src/directives.js',
|
||||
'src/markups.js',
|
||||
'src/widgets.js',
|
||||
'src/widget/form.js',
|
||||
'src/widget/input.js',
|
||||
'src/widget/select.js',
|
||||
'src/AngularPublic.js'
|
||||
],
|
||||
|
||||
'angularScenario': [
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js'
|
||||
],
|
||||
|
||||
'jstd': [
|
||||
'lib/jasmine/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'lib/jquery/jquery.js',
|
||||
'test/jquery_remove.js',
|
||||
'@angularSrc',
|
||||
'example/personalLog/*.js',
|
||||
'test/testabilityPatch.js',
|
||||
'test/matchers.js',
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/output/*.js',
|
||||
'src/jstd-scenario-adapter/*.js',
|
||||
'src/scenario/*.js',
|
||||
'src/angular-mocks.js',
|
||||
'test/mocks.js',
|
||||
'test/scenario/*.js',
|
||||
'test/scenario/output/*.js',
|
||||
'test/jstd-scenario-adapter/*.js',
|
||||
'test/*.js',
|
||||
'test/service/*.js',
|
||||
'test/widget/*.js',
|
||||
'example/personalLog/test/*.js'
|
||||
],
|
||||
|
||||
'jstdExclude': [
|
||||
'test/jquery_alias.js',
|
||||
'src/angular-bootstrap.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'src/AngularPublic.js'
|
||||
],
|
||||
|
||||
'jstdScenario': [
|
||||
'build/angular-scenario.js',
|
||||
'build/jstd-scenario-adapter-config.js',
|
||||
'build/jstd-scenario-adapter.js',
|
||||
'build/docs/docs-scenario.js'
|
||||
],
|
||||
|
||||
'jstdMocks': [
|
||||
'lib/jasmine/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'build/angular.js',
|
||||
'src/angular-mocks.js',
|
||||
'test/matchers.js',
|
||||
'test/angular-mocksSpec.js'
|
||||
],
|
||||
|
||||
'jstdPerf': [
|
||||
'lib/jasmine/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'angularSrc',
|
||||
'src/angular-mocks.js',
|
||||
'perf/data/*.js',
|
||||
'perf/testUtils.js',
|
||||
'perf/*.js'
|
||||
],
|
||||
|
||||
'jstdPerfExclude': [
|
||||
'src/angular-bootstrap.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'src/AngularPublic.js'
|
||||
],
|
||||
|
||||
'jstdJquery': [
|
||||
'lib/jasmine/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'lib/jquery/jquery.js',
|
||||
'test/jquery_alias.js',
|
||||
'@angularSrc',
|
||||
'example/personalLog/*.js',
|
||||
'test/testabilityPatch.js',
|
||||
'test/matchers.js',
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/output/*.js',
|
||||
'src/jstd-scenario-adapter/*.js',
|
||||
'src/scenario/*.js',
|
||||
'src/angular-mocks.js',
|
||||
'test/mocks.js',
|
||||
'test/scenario/*.js',
|
||||
'test/scenario/output/*.js',
|
||||
'test/jstd-scenario-adapter/*.js',
|
||||
'test/*.js',
|
||||
'test/service/*.js',
|
||||
'test/widget/*.js',
|
||||
'example/personalLog/test/*.js'
|
||||
],
|
||||
|
||||
'jstdJqueryExclude': [
|
||||
'src/angular-bootstrap.js',
|
||||
'src/AngularPublic.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'test/jquery_remove.js'
|
||||
]
|
||||
};
|
||||
+6
-80
@@ -1,87 +1,13 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
[ng\:cloak], .ng-cloak {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ng-format-negative {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.ng-exception {
|
||||
border: 2px solid #FF0000;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: smaller;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.ng-validation-error {
|
||||
border: 2px solid #FF0000;
|
||||
}
|
||||
|
||||
|
||||
/*****************
|
||||
* TIP
|
||||
*****************/
|
||||
#ng-callout {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
font-family: Verdana, Arial, Helvetica, sans-serif;
|
||||
vertical-align: baseline;
|
||||
background: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#ng-callout .ng-arrow-left{
|
||||
background-image: url("data:image/gif;base64,R0lGODlhCwAXAKIAAMzMzO/v7/f39////////wAAAAAAAAAAACH5BAUUAAQALAAAAAALABcAAAMrSLoc/AG8FeUUIN+sGebWAnbKSJodqqlsOxJtqYooU9vvk+vcJIcTkg+QAAA7");
|
||||
background-repeat: no-repeat;
|
||||
background-position: left top;
|
||||
position: absolute;
|
||||
z-index:101;
|
||||
left:-12px;
|
||||
height:23px;
|
||||
width:10px;
|
||||
top:-3px;
|
||||
}
|
||||
|
||||
#ng-callout .ng-arrow-right{
|
||||
background-image: url("data:image/gif;base64,R0lGODlhCwAXAKIAAMzMzO/v7/f39////////wAAAAAAAAAAACH5BAUUAAQALAAAAAALABcAAAMrCLTcoM29yN6k9socs91e5X3EyJloipYrO4ohTMqA0Fn2XVNswJe+H+SXAAA7");
|
||||
background-repeat: no-repeat;
|
||||
background-position: left top;
|
||||
position: absolute;
|
||||
z-index:101;
|
||||
height:23px;
|
||||
width:11px;
|
||||
top:-2px;
|
||||
}
|
||||
|
||||
#ng-callout {
|
||||
position: absolute;
|
||||
z-index:100;
|
||||
border: 2px solid #CCCCCC;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#ng-callout .ng-content{
|
||||
padding:10px 10px 10px 10px;
|
||||
color:#333333;
|
||||
}
|
||||
|
||||
#ng-callout .ng-title{
|
||||
background-color: #CCCCCC;
|
||||
text-align: left;
|
||||
padding-left: 8px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 2px;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
|
||||
/*****************
|
||||
* indicators
|
||||
*****************/
|
||||
.ng-input-indicator-wait {
|
||||
background-image: url("data:image/png;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==");
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
ng\:form {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.directive
|
||||
@namespace Namespace for all directives.
|
||||
|
||||
@description
|
||||
A directive is an HTML attribute that you can use in an existing HTML element type or in a
|
||||
DOM element type that you create as {@link angular.widget}, to modify that element's
|
||||
properties. You can use any number of directives per element.
|
||||
|
||||
For example, you can add the ng:bind directive as an attribute of an HTML span element, as in
|
||||
`<span ng:bind="1+2"></span>`. How does this work? The compiler passes the attribute value
|
||||
`1+2` to the ng:bind extension, which in turn tells the {@link angular.scope} to watch that
|
||||
expression and report changes. On any change it sets the span text to the expression value.
|
||||
|
||||
Here's how to define {@link angular.directive.ng:bind ng:bind}:
|
||||
<pre>
|
||||
angular.directive('ng:bind', function(expression, compiledElement) {
|
||||
var compiler = this;
|
||||
return function(linkElement) {
|
||||
var currentScope = this;
|
||||
currentScope.$watch(expression, function(value) {
|
||||
linkElement.text(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
# Directive vs. Attribute Widget
|
||||
Both [attribute widgets](#!angular.widget) and directives can compile a DOM element
|
||||
attribute. So why have two different ways to do the same thing? The answer is that order
|
||||
matters, but we have no control over the order in which attributes are read. To solve this
|
||||
we apply attribute widget before the directive.
|
||||
|
||||
For example, consider this piece of HTML, which uses the directives `ng:repeat`, `ng:init`,
|
||||
and `ng:bind`:
|
||||
<pre>
|
||||
<ul ng:init="people=['mike', 'mary']">
|
||||
<li ng:repeat="person in people" ng:init="a=a+1" ng:bind="person"></li>
|
||||
</ul>
|
||||
</pre>
|
||||
|
||||
Notice that the order of execution matters here. We need to execute
|
||||
{@link angular.directive.ng:repeat ng:repeat} before we run the
|
||||
{@link angular.directive.ng:init ng:init} and `ng:bind` on the `<li/>;`. This is because we
|
||||
want to run the `ng:init="a=a+1` and `ng:bind="person"` once for each person in people. We
|
||||
could not have used directive to create this template because attributes are read in an
|
||||
unspecified order and there is no way of guaranteeing that the repeater attribute would
|
||||
execute first. Using the `ng:repeat` attribute directive ensures that we can transform the
|
||||
DOM element into a template.
|
||||
|
||||
Widgets run before directives. Widgets may manipulate the DOM whereas directives are not
|
||||
expected to do so, and so they run last.
|
||||
@@ -1,43 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc function
|
||||
@name angular.element
|
||||
@function
|
||||
|
||||
@description
|
||||
Wraps a raw DOM element or HTML string as [jQuery](http://jquery.com) element.
|
||||
`angular.element` is either an alias for [jQuery](http://api.jquery.com/jQuery/) function if
|
||||
jQuery is loaded or a function that wraps the element or string in angular's jQuery lite
|
||||
implementation.
|
||||
|
||||
Real jQuery always takes precedence if it was loaded before angular.
|
||||
|
||||
Angular's jQuery lite implementation is a tiny API-compatible subset of jQuery which allows
|
||||
angular to manipulate DOM. The functions implemented are usually just the basic versions of
|
||||
them and might not support arguments and invocation styles.
|
||||
|
||||
NOTE: All element references in angular are always wrapped with jQuery (lite) and are never
|
||||
raw DOM references.
|
||||
|
||||
Angular's jQuery lite implements these functions:
|
||||
|
||||
- [addClass()](http://api.jquery.com/addClass/)
|
||||
- [after()](http://api.jquery.com/after/)
|
||||
- [append()](http://api.jquery.com/append/)
|
||||
- [attr()](http://api.jquery.com/attr/)
|
||||
- [bind()](http://api.jquery.com/bind/)
|
||||
- [children()](http://api.jquery.com/children/)
|
||||
- [clone()](http://api.jquery.com/clone/)
|
||||
- [css()](http://api.jquery.com/css/)
|
||||
- [data()](http://api.jquery.com/data/)
|
||||
- [hasClass()](http://api.jquery.com/hasClass/)
|
||||
- [parent()](http://api.jquery.com/parent/)
|
||||
- [remove()](http://api.jquery.com/remove/)
|
||||
- [removeAttr()](http://api.jquery.com/removeAttr/)
|
||||
- [removeClass()](http://api.jquery.com/removeClass/)
|
||||
- [removeData()](http://api.jquery.com/removeData/)
|
||||
- [replaceWith()](http://api.jquery.com/replaceWith/)
|
||||
- [text()](http://api.jquery.com/text/)
|
||||
- [trigger()](http://api.jquery.com/trigger/)
|
||||
|
||||
@param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
|
||||
@returns {Object} jQuery object.
|
||||
@@ -1,76 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.filter
|
||||
@namespace Namespace for all filters.
|
||||
@description
|
||||
# Overview
|
||||
Filters are a standard way to format your data for display to the user. For example, you
|
||||
might have the number 1234.5678 and would like to display it as US currency: $1,234.57.
|
||||
Filters allow you to do just that. In addition to transforming the data, filters also modify
|
||||
the DOM. This allows the filters to for example apply css styles to the filtered output if
|
||||
certain conditions were met.
|
||||
|
||||
|
||||
# Standard Filters
|
||||
|
||||
The Angular framework provides a standard set of filters for common operations, including:
|
||||
{@link angular.filter.currency currency}, {@link angular.filter.json json},
|
||||
{@link angular.filter.number number}, and {@link angular.filter.html html}. You can also add
|
||||
your own filters.
|
||||
|
||||
|
||||
# Syntax
|
||||
|
||||
Filters can be part of any {@link angular.scope} evaluation but are typically used with
|
||||
{{bindings}}. Filters typically transform the data to a new data type, formating the data in
|
||||
the process. Filters can be chained and take optional arguments. Here are few examples:
|
||||
|
||||
* No filter: {{1234.5678}} => 1234.5678
|
||||
* Number filter: {{1234.5678|number}} => 1,234.57. Notice the “,” and rounding to two
|
||||
significant digits.
|
||||
* Filter with arguments: {{1234.5678|number:5}} => 1,234.56780. Filters can take optional
|
||||
arguments, separated by colons in a binding. To number, the argument “5” requests 5 digits
|
||||
to the right of the decimal point.
|
||||
|
||||
|
||||
# Writing your own Filters
|
||||
|
||||
Writing your own filter is very easy: just define a JavaScript function on `angular.filter`.
|
||||
The framework passes in the input value as the first argument to your function. Any filter
|
||||
arguments are passed in as additional function arguments.
|
||||
|
||||
You can use these variables in the function:
|
||||
|
||||
* `this` — The current scope.
|
||||
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
|
||||
the DOM in addition to transforming the input.
|
||||
|
||||
|
||||
@exampleDescription
|
||||
The following example filter reverses a text string. In addition, it conditionally makes the
|
||||
text upper-case (to demonstrate optional arguments) and assigns color (to demonstrate DOM
|
||||
modification).
|
||||
|
||||
@example
|
||||
<script type="text/javascript">
|
||||
angular.filter('reverse', function(input, uppercase, color) {
|
||||
var out = "";
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
out = input.charAt(i) + out;
|
||||
}
|
||||
if (uppercase) {
|
||||
out = out.toUpperCase();
|
||||
}
|
||||
if (color) {
|
||||
this.$element.css('color', color);
|
||||
}
|
||||
return out;
|
||||
});
|
||||
</script>
|
||||
|
||||
<input name="text" type="text" value="hello" /><br>
|
||||
No filter: {{text}}<br>
|
||||
Reverse: {{text|reverse}}<br>
|
||||
Reverse + uppercase: {{text|reverse:true}}<br>
|
||||
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.formatter
|
||||
@namespace Namespace for all formats.
|
||||
@description
|
||||
# Overview
|
||||
The formatters are responsible for translating user readable text in an input widget to a
|
||||
data model stored in an application.
|
||||
|
||||
# Writting your own Formatter
|
||||
Writing your own formatter is easy. Just register a pair of JavaScript functions with
|
||||
`angular.formatter`. One function for parsing user input text to the stored form,
|
||||
and one for formatting the stored data to user-visible text.
|
||||
|
||||
Here is an example of a "reverse" formatter: The data is stored in uppercase and in
|
||||
reverse, while it is displayed in lower case and non-reversed. User edits are
|
||||
automatically parsed into the internal form and data changes are automatically
|
||||
formatted to the viewed form.
|
||||
|
||||
<pre>
|
||||
function reverse(text) {
|
||||
var reversed = [];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
reversed.unshift(text.charAt(i));
|
||||
}
|
||||
return reversed.join('');
|
||||
}
|
||||
|
||||
angular.formatter('reverse', {
|
||||
parse: function(value){
|
||||
return reverse(value||'').toUpperCase();
|
||||
},
|
||||
format: function(value){
|
||||
return reverse(value||'').toLowerCase();
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
@example
|
||||
<script type="text/javascript">
|
||||
function reverse(text) {
|
||||
var reversed = [];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
reversed.unshift(text.charAt(i));
|
||||
}
|
||||
return reversed.join('');
|
||||
}
|
||||
|
||||
angular.formatter('reverse', {
|
||||
parse: function(value){
|
||||
return reverse(value||'').toUpperCase();
|
||||
},
|
||||
format: function(value){
|
||||
return reverse(value||'').toLowerCase();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Formatted:
|
||||
<input type="text" name="data" value="angular" ng:format="reverse"/>
|
||||
<br/>
|
||||
|
||||
Stored:
|
||||
<input type="text" name="data"/><br/>
|
||||
<pre>{{data}}</pre>
|
||||
|
||||
|
||||
@scenario
|
||||
it('should store reverse', function(){
|
||||
expect(element('.doc-example input:first').val()).toEqual('angular');
|
||||
expect(element('.doc-example input:last').val()).toEqual('RALUGNA');
|
||||
|
||||
this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
$document.elements('.doc-example input:last').val('XYZ').trigger('change');
|
||||
done();
|
||||
});
|
||||
expect(element('.doc-example input:first').val()).toEqual('zyx');
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular
|
||||
@namespace The exported angular namespace.
|
||||
@@ -1,166 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.service
|
||||
|
||||
@description
|
||||
# Overview
|
||||
Services are substituable objects, which are wired together using dependency injection (DI).
|
||||
Each service could have dependencies (other services), which are passed in constructor.
|
||||
Because JS is dynamicaly typed language, dependency injection can not use static types
|
||||
to identify these dependencies, so each service must explicitely define its dependencies.
|
||||
This is done by `$inject` property.
|
||||
|
||||
|
||||
# Built-in services
|
||||
angular provides a set of services for common operations. These services can be overriden by custom
|
||||
services if needed.
|
||||
|
||||
Like other core angular variables and identifiers, the built-in services always start with `$`.
|
||||
|
||||
* `{@link angular.service.$browser $browser}`
|
||||
* `{@link angular.service.$window $window}`
|
||||
* `{@link angular.service.$document $document}`
|
||||
* `{@link angular.service.$location $location}`
|
||||
* `{@link angular.service.$log $log}`
|
||||
* `{@link angular.service.$exceptionHandler $exceptionHandler}`
|
||||
* `{@link angular.service.$hover $hover}`
|
||||
* `{@link angular.service.$invalidWidgets $invalidWidgets}`
|
||||
* `{@link angular.service.$route $route}`
|
||||
* `{@link angular.service.$xhr $xhr}`
|
||||
* `{@link angular.service.$xhr.error $xhr.error}`
|
||||
* `{@link angular.service.$xhr.bulk $xhr.bulk}`
|
||||
* `{@link angular.service.$xhr.cache $xhr.cache}`
|
||||
* `{@link angular.service.$resource $resource}`
|
||||
* `{@link angular.service.$cookies $cookies}`
|
||||
* `{@link angular.service.$cookieStore $cookieStore}`
|
||||
|
||||
# Writing your own custom services
|
||||
angular provides only set of basic services, so for any nontrivial application it will be necessary
|
||||
to write one or more custom services. To do so, a factory function that creates a services needs to
|
||||
be registered with angular's dependency injector. This factory function must return an object - the
|
||||
service (it is not called with the `new` operator).
|
||||
|
||||
**angular.service** accepts three parameters:
|
||||
|
||||
- `{string} name` - Name of the service.
|
||||
- `{function()} factory` - Factory function (called just once by DI).
|
||||
- `{Object} config` - Configuration object with following properties:
|
||||
- `$inject` - {Array.<string>} - Array of service ids that this service depends on. These
|
||||
services will be passed as arguments into the factory function in the same order as specified
|
||||
in the `$inject` array. Defaults to `[]`.
|
||||
- `$eager` - {boolean} - If true, the service factory will be called and thus, the service will
|
||||
be instantiated when angular boots. If false, service will be lazily instantiated when it is
|
||||
first requested during instantiation of a dependant. Defaults to `false`.
|
||||
|
||||
The `this` of the factory function is bound to the root scope of the angular application.
|
||||
|
||||
angular enables services to participate in dependency injection (DI) by registering themselves with
|
||||
angular's DI system (injector) under a `name` (id) as well as by declaring dependencies which need
|
||||
to be provided for the factory function of the registered service. The ability to swap dependencies
|
||||
for mocks/stubs/dummies in tests allows for services to be highly testable.
|
||||
|
||||
Here is an example of very simple service. This service requires $window service (it's
|
||||
passed as a parameter to factory function) and it's just a function.
|
||||
|
||||
This service simple stores all notifications and after third one, it displays all of them by
|
||||
window alert.
|
||||
<pre>
|
||||
angular.service('notify', function(win) {
|
||||
var msgs = [];
|
||||
return function(msg) {
|
||||
msgs.push(msg);
|
||||
if (msgs.length == 3) {
|
||||
win.alert(msgs.join("\n"));
|
||||
msgs = [];
|
||||
}
|
||||
};
|
||||
}, {$inject: ['$window']});
|
||||
</pre>
|
||||
|
||||
And here is a unit test for this service. We use Jasmine spy (mock) instead of real browser's alert.
|
||||
<pre>
|
||||
var mock, notify;
|
||||
|
||||
beforeEach(function() {
|
||||
mock = {alert: jasmine.createSpy()};
|
||||
notify = angular.service('notify')(mock);
|
||||
});
|
||||
|
||||
it('should not alert first two notifications', function() {
|
||||
notify('one');
|
||||
notify('two');
|
||||
expect(mock.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should alert all after third notification', function() {
|
||||
notify('one');
|
||||
notify('two');
|
||||
notify('three');
|
||||
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
|
||||
});
|
||||
|
||||
it('should clear messages after alert', function() {
|
||||
notify('one');
|
||||
notify('two');
|
||||
notify('third');
|
||||
notify('more');
|
||||
notify('two');
|
||||
notify('third');
|
||||
expect(mock.alert.callCount).toEqual(2);
|
||||
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
|
||||
});
|
||||
</pre>
|
||||
|
||||
# Injecting services into controllers
|
||||
Using services as dependencies for controllers is very similar to using them as dependencies for
|
||||
another service.
|
||||
|
||||
JavaScript is dynamic language, so DI is not able to figure out which services to inject by
|
||||
static types (like in static typed languages). Therefore you must specify the service name
|
||||
by the `$inject` property - it's an array that contains strings with names of services to be
|
||||
injected. The name must match the id that service has been registered as with angular.
|
||||
The order of the services in the array matters, because this order will be used when calling
|
||||
the factory function with injected parameters. The names of parameters in factory function
|
||||
don't matter, but by convention they match the service ids.
|
||||
<pre>
|
||||
function myController($loc, $log) {
|
||||
this.firstMethod = function() {
|
||||
// use $location service
|
||||
$loc.setHash();
|
||||
};
|
||||
this.secondMethod = function() {
|
||||
// use $log service
|
||||
$log.info('...');
|
||||
};
|
||||
}
|
||||
// which services to inject ?
|
||||
myController.$inject = ['$location', '$log'];
|
||||
</pre>
|
||||
|
||||
@example
|
||||
<script type="text/javascript">
|
||||
angular.service('notify', function(win) {
|
||||
var msgs = [];
|
||||
return function(msg) {
|
||||
msgs.push(msg);
|
||||
if (msgs.length == 3) {
|
||||
win.alert(msgs.join("\n"));
|
||||
msgs = [];
|
||||
}
|
||||
};
|
||||
}, {$inject: ['$window']});
|
||||
|
||||
function myController(notifyService) {
|
||||
this.callNotify = function(msg) {
|
||||
notifyService(msg);
|
||||
};
|
||||
}
|
||||
|
||||
myController.$inject = ['notify'];
|
||||
</script>
|
||||
|
||||
<div ng:controller="myController">
|
||||
<p>Let's try this simple notify service, injected into the controller...</p>
|
||||
<input ng:init="message='test'" type="text" name="message" />
|
||||
<button ng:click="callNotify(message);">NOTIFY</button>
|
||||
</div>
|
||||
@@ -1,73 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.validator
|
||||
@namespace Namespace for all filters.
|
||||
@description
|
||||
# Overview
|
||||
Validators are a standard way to check the user input against a specific criteria. For
|
||||
example, you might need to check that an input field contains a well-formed phone number.
|
||||
|
||||
# Syntax
|
||||
Attach a validator on user input widgets using the `ng:validate` attribute.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Change me: <input type="text" name="number" ng:validate="integer" value="123">
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should validate the default number string', function() {
|
||||
expect(element('input[name=number]').attr('class')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
it('should not validate "foo"', function() {
|
||||
input('number').enter('foo');
|
||||
expect(element('input[name=number]').attr('class')).
|
||||
toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Writing your own Validators
|
||||
Writing your own validator is easy. To make a function available as a
|
||||
validator, just define the JavaScript function on the `angular.validator`
|
||||
object. <angular/> passes in the input to validate as the first argument
|
||||
to your function. Any additional validator arguments are passed in as
|
||||
additional arguments to your function.
|
||||
|
||||
You can use these variables in the function:
|
||||
|
||||
* `this` — The current scope.
|
||||
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
|
||||
the DOM in addition to transforming the input.
|
||||
|
||||
In this example we have written a upsTrackingNo validator.
|
||||
It marks the input text "valid" only when the user enters a well-formed
|
||||
UPS tracking number.
|
||||
|
||||
@css ng-validation-error
|
||||
When validation fails, this css class is applied to the binding, making its borders red by
|
||||
default.
|
||||
|
||||
@example
|
||||
<script>
|
||||
angular.validator('upsTrackingNo', function(input, format) {
|
||||
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
|
||||
return input.match(regexp)?"":"The format must match " + format;
|
||||
});
|
||||
</script>
|
||||
<input type="text" name="trackNo" size="40"
|
||||
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
|
||||
value="1Z 123 456 78 9012 345 6"/>
|
||||
|
||||
@scenario
|
||||
it('should validate correct UPS tracking number', function() {
|
||||
expect(element('input[name=trackNo]').attr('class')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
|
||||
it('should not validate in correct UPS tracking number', function() {
|
||||
input('trackNo').enter('foo');
|
||||
expect(element('input[name=trackNo]').attr('class')).
|
||||
toMatch(/ng-validation-error/);
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.widget
|
||||
@namespace Namespace for all widgets.
|
||||
@description
|
||||
# Overview
|
||||
Widgets allow you to create DOM elements that the browser doesn't
|
||||
already understand. You create the widget in your namespace and
|
||||
assign it behavior. You can only bind one widget per DOM element
|
||||
(unlike directives, in which you can use any number per DOM
|
||||
element). Widgets are expected to manipulate the DOM tree by
|
||||
adding new elements whereas directives are expected to only modify
|
||||
element properties.
|
||||
|
||||
Widgets come in two flavors: element and attribute.
|
||||
|
||||
# Element Widget
|
||||
Let's say we would like to create a new element type in the
|
||||
namespace `my` that can watch an expression and alert() the user
|
||||
with each new value.
|
||||
|
||||
<pre>
|
||||
<my:watch exp="name"/>
|
||||
</pre>
|
||||
|
||||
You can implement `my:watch` like this:
|
||||
<pre>
|
||||
angular.widget('my:watch', function(compileElement) {
|
||||
var compiler = this;
|
||||
var exp = compileElement.attr('exp');
|
||||
return function(linkElement) {
|
||||
var currentScope = this;
|
||||
currentScope.$watch(exp, function(value){
|
||||
alert(value);
|
||||
}};
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
# Attribute Widget
|
||||
Let's implement the same widget, but this time as an attribute
|
||||
that can be added to any existing DOM element.
|
||||
<pre>
|
||||
<div my-watch="name">text</div>
|
||||
</pre>
|
||||
You can implement `my:watch` attribute like this:
|
||||
<pre>
|
||||
angular.widget('@my:watch', function(expression, compileElement) {
|
||||
var compiler = this;
|
||||
return function(linkElement) {
|
||||
var currentScope = this;
|
||||
currentScope.$watch(expression, function(value){
|
||||
alert(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
@example
|
||||
<script>
|
||||
angular.widget('my:time', function(compileElement){
|
||||
compileElement.css('display', 'block');
|
||||
return function(linkElement){
|
||||
function update(){
|
||||
linkElement.text('Current time is: ' + new Date());
|
||||
setTimeout(update, 1000);
|
||||
}
|
||||
update();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<my:time></my:time>
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
@ngdoc overview
|
||||
@name angular.inputType
|
||||
@description
|
||||
|
||||
Angular {@link guide/dev_guide.forms forms} allow you to build complex widgets. However for
|
||||
simple widget which are based on HTML input text element a simpler way of providing the validation
|
||||
and parsing is also provided. `angular.inputType` is a short hand for creating a widget which
|
||||
already has the DOM listeners and `$render` method supplied. The only thing which needs to
|
||||
be provided by the developer are the optional `$validate` listener and
|
||||
`$parseModel` or `$parseModel` methods.
|
||||
|
||||
All `inputType` widgets support:
|
||||
|
||||
- CSS classes:
|
||||
- **`ng-valid`**: when widget is valid.
|
||||
- **`ng-invalid`**: when widget is invalid.
|
||||
- **`ng-pristine`**: when widget has not been modified by user action.
|
||||
- **`ng-dirty`**: when has been modified do to user action.
|
||||
|
||||
- Widget properties:
|
||||
- **`$valid`**: When widget is valid.
|
||||
- **`$invalid`**: When widget is invalid.
|
||||
- **`$pristine`**: When widget has not been modified by user interaction.
|
||||
- **`$dirty`**: When user has been modified do to user interaction.
|
||||
- **`$required`**: When the `<input>` element has `required` attribute. This means that the
|
||||
widget will have `REQUIRED` validation error if empty.
|
||||
- **`$disabled`**: When the `<input>` element has `disabled` attribute.
|
||||
- **`$readonly`**: When the `<input>` element has `readonly` attribute.
|
||||
|
||||
- Widget Attribute Validators:
|
||||
- **`required`**: Sets `REQUIRED` validation error key if the input is empty
|
||||
- **`ng:pattern`** Sets `PATTERN` validation error key if the value does not match the
|
||||
RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
patterns defined as scope expressions.
|
||||
|
||||
|
||||
|
||||
# Example
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.inputType('json', function() {
|
||||
this.$parseView = function() {
|
||||
try {
|
||||
this.$modelValue = angular.fromJson(this.$viewValue);
|
||||
if (this.$error.JSON) {
|
||||
this.$emit('$valid', 'JSON');
|
||||
}
|
||||
} catch (e) {
|
||||
this.$emit('$invalid', 'JSON');
|
||||
}
|
||||
}
|
||||
|
||||
this.$parseModel = function() {
|
||||
this.$viewValue = angular.toJson(this.$modelValue);
|
||||
}
|
||||
});
|
||||
|
||||
function Ctrl() {
|
||||
this.data = {
|
||||
framework:'angular',
|
||||
codenames:'supper-powers'
|
||||
}
|
||||
this.required = false;
|
||||
this.disabled = false;
|
||||
this.readonly = false;
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="Ctrl">
|
||||
<form name="myForm">
|
||||
<input type="json" ng:model="data" size="80"
|
||||
ng:required="{{required}}" ng:disabled="{{disabled}}"
|
||||
ng:readonly="{{readonly}}"/><br/>
|
||||
Required: <input type="checkbox" ng:model="required"> <br/>
|
||||
Disabled: <input type="checkbox" ng:model="disabled"> <br/>
|
||||
Readonly: <input type="checkbox" ng:model="readonly"> <br/>
|
||||
<pre>data={{data}}</pre>
|
||||
<pre>myForm={{myForm}}</pre>
|
||||
</form>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should invalidate on wrong input', function() {
|
||||
expect(element('form[name=myForm]').prop('className')).toMatch('ng-valid');
|
||||
input('data').enter('{}');
|
||||
expect(binding('data')).toEqual('data={\n }');
|
||||
input('data').enter('{');
|
||||
expect(element('form[name=myForm]').prop('className')).toMatch('ng-invalid');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -0,0 +1,25 @@
|
||||
@ngdoc overview
|
||||
@name angular.service
|
||||
@description
|
||||
|
||||
The services API provides objects for carrying out common web app tasks. Service objects are
|
||||
managed by angular's {@link guide/dev_guide.di dependency injection system}.
|
||||
|
||||
* {@link angular.service.$browser $browser } - Provides an instance of a browser object
|
||||
* {@link angular.service.$cookieStore $cookieStore } - Provides key / value storage backed by
|
||||
session cookies
|
||||
* {@link angular.service.$cookies $cookies } - Provides read / write access to browser cookies
|
||||
* {@link angular.service.$defer $defer } - Defers function execution and try / catch block
|
||||
* {@link angular.service.$document $document } - Provides reference to `window.document` element
|
||||
* {@link angular.service.$exceptionHandler $exceptionHandler } - Receives uncaught angular
|
||||
exceptions
|
||||
* {@link angular.service.$location $location } - Parses the browser location URL
|
||||
* {@link angular.service.$log $log } - Provides logging service
|
||||
* {@link angular.service.$resource $resource } - Creates objects for interacting with RESTful
|
||||
server-side data sources
|
||||
* {@link angular.service.$route $route } - Provides deep-linking services
|
||||
* {@link angular.service.$window $window } - References the browsers `window` object
|
||||
* {@link angular.service.$xhr $xhr} - Generates an XHR request.
|
||||
|
||||
For information on how angular services work and how to write your own services, see {@link
|
||||
guide/dev_guide.services Angular Services} in the angular Developer Guide.
|
||||
@@ -0,0 +1,71 @@
|
||||
@ngdoc overview
|
||||
@name API Reference
|
||||
@description
|
||||
|
||||
## Angular Compiler API
|
||||
|
||||
* {@link angular.widget Widgets} - Angular custom DOM element
|
||||
* {@link angular.directive Directives} - Angular DOM element attributes
|
||||
* {@link angular.markup Markup} and {@link angular.attrMarkup Attribute Markup}
|
||||
* {@link angular.filter Filters} - Angular output filters
|
||||
* {@link angular.compile angular.compile()} - Template compiler
|
||||
|
||||
## Angular Scope API
|
||||
|
||||
* {@link angular.scope Scope Object} - Angular scope object
|
||||
|
||||
|
||||
## Angular Services & Dependency Injection API
|
||||
|
||||
* {@link angular.service Angular Services}
|
||||
* {@link angular.injector angular.injector() }
|
||||
|
||||
|
||||
## Angular Testing API
|
||||
|
||||
* {@link angular.mock Testing Mocks API} - Mock objects for testing
|
||||
* {@link guide/dev_guide.e2e-testing Angular Scenario Runner} - Automated scenario testing
|
||||
documentation
|
||||
|
||||
|
||||
## Angular Utility Functions
|
||||
|
||||
### HTML & DOM Manipulation
|
||||
|
||||
* {@link angular.element angular.element()}
|
||||
|
||||
### Misc
|
||||
|
||||
* {@link angular.bind angular.bind() }
|
||||
* {@link angular.extend angular.extend() }
|
||||
* {@link angular.forEach angular.forEach() }
|
||||
* {@link angular.identity angular.identity() }
|
||||
* {@link angular.noop angular.noop() }
|
||||
|
||||
|
||||
## Type Identification
|
||||
|
||||
* {@link angular.isArray angular.isArray() }
|
||||
* {@link angular.isDate angular.isDate() }
|
||||
* {@link angular.isDefined angular.isDefined() }
|
||||
* {@link angular.isFunction angular.isFunction() }
|
||||
* {@link angular.isNumber angular.isNumber() }
|
||||
* {@link angular.isObject angular.isObject() }
|
||||
* {@link angular.isString angular.isString() }
|
||||
* {@link angular.isUndefined angular.isUndefined() }
|
||||
|
||||
## Strings
|
||||
|
||||
* {@link angular.lowercase angular.lowercase() }
|
||||
* {@link angular.uppercase angular.uppercase() }
|
||||
|
||||
### JSON
|
||||
|
||||
* {@link angular.fromJson angular.fromJson() }
|
||||
* {@link angular.toJson angular.toJson() }
|
||||
|
||||
|
||||
|
||||
## Utility methods for JavaScript types
|
||||
* {@link angular.Object Object API} - Utility functions for JavaScript objects
|
||||
* {@link angular.Array Array API} - Utility functions for JavaScript arrays
|
||||
@@ -0,0 +1,107 @@
|
||||
@ngdoc overview
|
||||
@name Cookbook: Advanced Form
|
||||
@description
|
||||
|
||||
Here we extend the basic form example to include common features such as reverting, dirty state
|
||||
detection, and preventing invalid form submission.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function UserForm() {
|
||||
this.state = /^\w\w$/;
|
||||
this.zip = /^\d\d\d\d\d$/;
|
||||
this.master = {
|
||||
name: 'John Smith',
|
||||
address:{
|
||||
line1: '123 Main St.',
|
||||
city:'Anytown',
|
||||
state:'AA',
|
||||
zip:'12345'
|
||||
},
|
||||
contacts:[
|
||||
{type:'phone', value:'1(234) 555-1212'}
|
||||
]
|
||||
};
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
UserForm.prototype = {
|
||||
cancel: function() {
|
||||
this.form = angular.copy(this.master);
|
||||
},
|
||||
|
||||
save: function() {
|
||||
this.master = this.form;
|
||||
this.cancel();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="UserForm">
|
||||
|
||||
<form name="myForm">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" ng:model="form.name" required/> <br/><br/>
|
||||
|
||||
<label>Address:</label> <br/>
|
||||
<input type="text" ng:model="form.address.line1" size="33" required/> <br/>
|
||||
<input type="text" ng:model="form.address.city" size="12" required/>,
|
||||
<input type="text" ng:model="form.address.state" size="2"
|
||||
ng:pattern="state" required/>
|
||||
<input type="text" ng:model="form.address.zip" size="5"
|
||||
ng:pattern="zip" required/><br/><br/>
|
||||
|
||||
<label>Contacts:</label>
|
||||
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
|
||||
<div ng:repeat="contact in form.contacts">
|
||||
<select ng:model="contact.type">
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
<option>pager</option>
|
||||
<option>IM</option>
|
||||
</select>
|
||||
<input type="text" ng:model="contact.value" required/>
|
||||
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<button ng:click="cancel()" ng:disabled="{{master.$equals(form)}}">Cancel</button>
|
||||
<button ng:click="save()" ng:disabled="{{myForm.$invalid || master.$equals(form)}}">Save</button>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
Debug View:
|
||||
<pre>form={{form}}
|
||||
master={{master}}</pre>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should enable save button', function() {
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
input('form.name').enter('');
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
input('form.name').enter('change');
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Save)').click();
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
});
|
||||
it('should enable cancel button', function() {
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
input('form.name').enter('change');
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Cancel)').click();
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
expect(element(':input[ng\\:model="form.name"]').val()).toEqual('John Smith');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
#Things to notice
|
||||
|
||||
* Cancel & save buttons are only enabled if the form is dirty — there is something to cancel or
|
||||
save.
|
||||
* Save button is only enabled if there are no validation errors on the form.
|
||||
* Cancel reverts the form changes back to original state.
|
||||
* Save updates the internal model of the form.
|
||||
* Debug view shows the two models. One presented to the user form and the other being the pristine
|
||||
copy master.
|
||||
@@ -0,0 +1,63 @@
|
||||
@ngdoc overview
|
||||
@name Cookbook: Resources - Buzz
|
||||
@description
|
||||
|
||||
External resources are URLs that provide JSON data, which are then rendered with the help of
|
||||
templates. angular has a resource factory that can be used to give names to the URLs and then
|
||||
attach behavior to them. For example you can use the
|
||||
{@link http://code.google.com/apis/buzz/v1/getting_started.html#background-operations| Google Buzz
|
||||
API}
|
||||
to retrieve Buzz activity and comments.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
BuzzController.$inject = ['$resource'];
|
||||
function BuzzController($resource) {
|
||||
this.userId = 'googlebuzz';
|
||||
this.Activity = $resource(
|
||||
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
|
||||
{alt: 'json', callback: 'JSON_CALLBACK'},
|
||||
{ get: {method: 'JSON', params: {visibility: '@self'}},
|
||||
replies: {method: 'JSON', params: {visibility: '@self', comments: '@comments'}}
|
||||
});
|
||||
}
|
||||
BuzzController.prototype = {
|
||||
fetch: function() {
|
||||
this.activities = this.Activity.get({userId:this.userId});
|
||||
},
|
||||
expandReplies: function(activity) {
|
||||
activity.replies = this.Activity.replies({userId: this.userId, activityId: activity.id});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="BuzzController">
|
||||
<input ng:model="userId"/>
|
||||
<button ng:click="fetch()">fetch</button>
|
||||
<hr/>
|
||||
<div class="buzz" ng:repeat="item in activities.data.items">
|
||||
<h1 style="font-size: 15px;">
|
||||
<img ng:src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a ng:href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href ng:click="expandReplies(item)" style="float: right;">
|
||||
Expand replies: {{item.links.replies[0].count}}
|
||||
</a>
|
||||
</h1>
|
||||
{{item.object.content | html}}
|
||||
<div class="reply" ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;">
|
||||
<img ng:src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a ng:href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
|
||||
{{reply.content | html}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
xit('fetch buzz and expand', function() {
|
||||
element(':button:contains(fetch)').click();
|
||||
expect(repeater('div.buzz').count()).toBeGreaterThan(0);
|
||||
element('.buzz a:contains(Expand replies):first').click();
|
||||
expect(repeater('div.reply').count()).toBeGreaterThan(0);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -0,0 +1,116 @@
|
||||
@ngdoc overview
|
||||
@name Cookbook: Deep Linking
|
||||
@description
|
||||
|
||||
Deep linking allows you to encode the state of the application in the URL so that it can be
|
||||
bookmarked and the application can be restored from the URL to the same state.
|
||||
|
||||
While <angular/> does not force you to deal with bookmarks in any particular way, it has services
|
||||
which make the common case described here very easy to implement.
|
||||
|
||||
# Assumptions
|
||||
|
||||
Your application consists of a single HTML page which bootstraps the application. We will refer
|
||||
to this page as the chrome.
|
||||
Your application is divided into several screens (or views) which the user can visit. For example,
|
||||
the home screen, settings screen, details screen, etc. For each of these screens, we would like to
|
||||
assign a URL so that it can be bookmarked and later restored. Each of these screens will be
|
||||
associated with a controller which define the screen's behavior. The most common case is that the
|
||||
screen will be constructed from an HTML snippet, which we will refer to as the partial. Screens can
|
||||
have multiple partials, but a single partial is the most common construct. This example makes the
|
||||
partial boundary visible using a blue line.
|
||||
|
||||
You can make a routing table which shows which URL maps to which partial view template and which
|
||||
controller.
|
||||
|
||||
# Example
|
||||
|
||||
In this example we have a simple app which consist of two screens:
|
||||
|
||||
* Welcome: url `welcome` Show the user contact information.
|
||||
* Settings: url `settings` Show an edit screen for user contact information.
|
||||
|
||||
|
||||
The two partials are defined in the following URLs:
|
||||
|
||||
* <a href="./examples/settings.html" ng:ext-link>./examples/settings.html</a>
|
||||
* <a href="./examples/welcome.html" ng:ext-link>./examples/welcome.html</a>
|
||||
|
||||
<doc:example>
|
||||
<doc:source jsfiddle="false">
|
||||
<script>
|
||||
AppCntl.$inject = ['$route']
|
||||
function AppCntl($route) {
|
||||
// define routes
|
||||
$route.when("/welcome", {template:'./examples/welcome.html', controller:WelcomeCntl});
|
||||
$route.when("/settings", {template:'./examples/settings.html', controller:SettingsCntl});
|
||||
$route.parent(this);
|
||||
|
||||
// initialize the model to something useful
|
||||
this.person = {
|
||||
name:'anonymous',
|
||||
contacts:[{type:'email', url:'anonymous@example.com'}]
|
||||
};
|
||||
}
|
||||
|
||||
function WelcomeCntl($route){}
|
||||
WelcomeCntl.prototype = {
|
||||
greet: function() {
|
||||
alert("Hello " + this.person.name);
|
||||
}
|
||||
};
|
||||
|
||||
SettingsCntl.$inject = ['$location'];
|
||||
function SettingsCntl($location){
|
||||
this.$location = $location;
|
||||
this.cancel();
|
||||
}
|
||||
SettingsCntl.prototype = {
|
||||
cancel: function() {
|
||||
this.form = angular.copy(this.person);
|
||||
},
|
||||
|
||||
save: function() {
|
||||
angular.copy(this.form, this.person);
|
||||
this.$location.path('/welcome');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="AppCntl">
|
||||
<h1>Your App Chrome</h1>
|
||||
[ <a href="welcome">Welcome</a> | <a href="settings">Settings</a> ]
|
||||
<hr/>
|
||||
<span style="background-color: blue; color: white; padding: 3px;">
|
||||
Partial: {{$route.current.template}}
|
||||
</span>
|
||||
<ng:view style="border: 1px solid blue; margin: 0; display:block; padding:1em;"></ng:view>
|
||||
<small>Your app footer </small>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should navigate to URL', function() {
|
||||
element('a:contains(Welcome)').click();
|
||||
expect(element('ng\\:view').text()).toMatch(/Hello anonymous/);
|
||||
element('a:contains(Settings)').click();
|
||||
input('form.name').enter('yourname');
|
||||
element(':button:contains(Save)').click();
|
||||
element('a:contains(Welcome)').click();
|
||||
expect(element('ng\\:view').text()).toMatch(/Hello yourname/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
|
||||
# Things to notice
|
||||
|
||||
* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
|
||||
initialization of the {@link api/angular.service.$route $route} service with the proper URL
|
||||
routes.
|
||||
* The {@link api/angular.service.$route $route} service then watches the URL and instantiates the
|
||||
appropriate controller when the URL changes.
|
||||
* The {@link api/angular.widget.ng:view ng:view} widget loads the view when the URL changes. It
|
||||
also
|
||||
sets the view scope to the newly instantiated controller.
|
||||
* Changing the URL is sufficient to change the controller and view. It makes no difference whether
|
||||
the URL is changed programatically or by the user.
|
||||
@@ -0,0 +1,103 @@
|
||||
@ngdoc overview
|
||||
@name Cookbook: Form
|
||||
@description
|
||||
|
||||
A web application's main purpose is to present and gather data. For this reason angular strives
|
||||
to make both of these operations trivial. This example shows off how you can build a simple form to
|
||||
allow a user to enter data.
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function FormController() {
|
||||
this.user = {
|
||||
name: 'John Smith',
|
||||
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
|
||||
contacts:[{type:'phone', value:'1(234) 555-1212'}]
|
||||
};
|
||||
this.state = /^\w\w$/;
|
||||
this.zip = /^\d\d\d\d\d$/;
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="FormController" class="example">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" ng:model="user.name" required/> <br/><br/>
|
||||
|
||||
<label>Address:</label><br/>
|
||||
<input type="text" ng:model="user.address.line1" size="33" required> <br/>
|
||||
<input type="text" ng:model="user.address.city" size="12" required>,
|
||||
<input type="text" ng:model="user.address.state" size="2"
|
||||
ng:pattern="state" required>
|
||||
<input type="text" ng:model="user.address.zip" size="5"
|
||||
ng:pattern="zip" required><br/><br/>
|
||||
|
||||
<label>Phone:</label>
|
||||
[ <a href="" ng:click="user.contacts.$add()">add</a> ]
|
||||
<div ng:repeat="contact in user.contacts">
|
||||
<select ng:model="contact.type">
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
<option>pager</option>
|
||||
<option>IM</option>
|
||||
</select>
|
||||
<input type="text" ng:model="contact.value" required/>
|
||||
[ <a href="" ng:click="user.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<hr/>
|
||||
Debug View:
|
||||
<pre>user={{user}}</pre>
|
||||
</div>
|
||||
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should show debug', function() {
|
||||
expect(binding('user')).toMatch(/John Smith/);
|
||||
});
|
||||
it('should add contact', function() {
|
||||
using('.example').element('a:contains(add)').click();
|
||||
using('.example div:last').input('contact.value').enter('you@example.org');
|
||||
expect(binding('user')).toMatch(/\(234\) 555\-1212/);
|
||||
expect(binding('user')).toMatch(/you@example.org/);
|
||||
});
|
||||
|
||||
it('should remove contact', function() {
|
||||
using('.example').element('a:contains(X)').click();
|
||||
expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
|
||||
});
|
||||
|
||||
it('should validate zip', function() {
|
||||
expect(using('.example').
|
||||
element(':input[ng\\:model="user.address.zip"]').
|
||||
prop('className')).not().toMatch(/ng-invalid/);
|
||||
using('.example').input('user.address.zip').enter('abc');
|
||||
expect(using('.example').
|
||||
element(':input[ng\\:model="user.address.zip"]').
|
||||
prop('className')).toMatch(/ng-invalid/);
|
||||
});
|
||||
|
||||
it('should validate state', function() {
|
||||
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
|
||||
.not().toMatch(/ng-invalid/);
|
||||
using('.example').input('user.address.state').enter('XXX');
|
||||
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
|
||||
.toMatch(/ng-invalid/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Things to notice
|
||||
|
||||
* The user data model is initialized {@link api/angular.directive.ng:controller controller} and is
|
||||
available in
|
||||
the {@link api/angular.scope scope} with the initial data.
|
||||
* For debugging purposes we have included a debug view of the model to better understand what
|
||||
is going on.
|
||||
* The {@link api/angular.widget.input input widgets} simply refer to the model and are data-bound.
|
||||
* The inputs {@link guide/dev_guide.forms validate}. (Try leaving them blank or entering non digits
|
||||
in the zip field)
|
||||
* In your application you can simply read from or write to the model and the form will be updated.
|
||||
* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
|
||||
reflected in the view.
|
||||
@@ -0,0 +1,38 @@
|
||||
@ngdoc overview
|
||||
@name Cookbook: Hello World
|
||||
@description
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function HelloCntl() {
|
||||
this.name = 'World';
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="HelloCntl">
|
||||
Your name: <input type="text" ng:model="name" value="World"/>
|
||||
<hr/>
|
||||
Hello {{name}}!
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should change the binding when user enters text', function() {
|
||||
expect(binding('name')).toEqual('World');
|
||||
input('name').enter('angular');
|
||||
expect(binding('name')).toEqual('angular');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
# Things to notice
|
||||
|
||||
Take a look through the source and note:
|
||||
|
||||
* The script tag that {@link guide/dev_guide.bootstrap bootstraps} the angular environment.
|
||||
* The text {@link api/angular.widget.input input widget} which is bound to the greeting name text.
|
||||
* No need for listener registration and event firing on change events.
|
||||
* The implicit presence of the `name` variable which is in the root {@link api/angular.scope scope}.
|
||||
* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
|
||||
* The concept of {@link guide/dev_guide.templates.databinding data binding}, which reflects any
|
||||
changes to the
|
||||
input field in the greeting text.
|
||||
@@ -0,0 +1,58 @@
|
||||
@ngdoc overview
|
||||
@name Cookbook
|
||||
@description
|
||||
|
||||
Welcome to the angular cookbook. Here we will show you typical uses of angular by example.
|
||||
|
||||
|
||||
# Hello World
|
||||
|
||||
{@link helloworld Hello World}: The simplest possible application that demonstrates the
|
||||
classic Hello World!
|
||||
|
||||
|
||||
# Basic Form
|
||||
|
||||
{@link form Basic Form}: Displaying forms to the user for editing is the bread and butter
|
||||
of web applications. Angular makes forms easy through bidirectional data binding.
|
||||
|
||||
|
||||
# Advanced Form
|
||||
|
||||
{@link advancedform Advanced Form}: Taking the form example to the next level and
|
||||
providing advanced features such as dirty detection, form reverting and submit disabling if
|
||||
validation errors exist.
|
||||
|
||||
|
||||
# Model View Controller
|
||||
|
||||
{@link mvc MVC}: Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
|
||||
to separate the behavior (JavaScript controller) from the presentation (HTML view). This
|
||||
separation aids in maintainability and testability of your project.
|
||||
|
||||
|
||||
# Multi-page App and Deep Linking
|
||||
|
||||
{@link deeplinking Deep Linking}: An AJAX application never navigates away from the
|
||||
first page it loads. Instead, it changes the DOM of its single page. Eliminating full-page reloads
|
||||
is what makes AJAX apps responsive, but it creates a problem in that apps with a single URL
|
||||
prevent you from emailing links to a particular screen within your application.
|
||||
|
||||
Deep linking tries to solve this by changing the URL anchor without reloading a page, thus
|
||||
allowing you to send links to specific screens in your app.
|
||||
|
||||
|
||||
# Services
|
||||
|
||||
{@link api/angular.service Services}: Services are long lived objects in your applications that are
|
||||
available across controllers. A collection of useful services are pre-bundled with angular but you
|
||||
will likely add your own. Services are initialized using dependency injection, which resolves the
|
||||
order of initialization. This safeguards you from the perils of global state (a common way to
|
||||
implement long lived objects).
|
||||
|
||||
|
||||
# External Resources
|
||||
|
||||
{@link buzz Resources}: Web applications must be able to communicate with the external
|
||||
services to get and update data. Resources are the abstractions of external URLs which are
|
||||
specially tailored to angular data binding.
|
||||
@@ -0,0 +1,124 @@
|
||||
@ngdoc overview
|
||||
@name Cookbook: MVC
|
||||
@description
|
||||
|
||||
MVC allows for a clean an testable separation between the behavior (controller) and the view
|
||||
(HTML template). A Controller is just a JavaScript class which is grafted onto the scope of the
|
||||
view. This makes it very easy for the controller and the view to share the model.
|
||||
|
||||
The model is simply the controller's this. This makes it very easy to test the controller in
|
||||
isolation since one can simply instantiate the controller and test without a view, because there is
|
||||
no connection between the controller and the view.
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function TicTacToeCntl($location){
|
||||
this.$location = $location;
|
||||
this.cellStyle= {
|
||||
'height': '20px',
|
||||
'width': '20px',
|
||||
'border': '1px solid black',
|
||||
'text-align': 'center',
|
||||
'vertical-align': 'middle',
|
||||
'cursor': 'pointer'
|
||||
};
|
||||
this.reset();
|
||||
this.$watch('$location.search().board', this.readUrl);
|
||||
}
|
||||
TicTacToeCntl.prototype = {
|
||||
dropPiece: function(row, col) {
|
||||
if (!this.winner && !this.board[row][col]) {
|
||||
this.board[row][col] = this.nextMove;
|
||||
this.nextMove = this.nextMove == 'X' ? 'O' : 'X';
|
||||
this.setUrl();
|
||||
}
|
||||
},
|
||||
reset: function() {
|
||||
this.board = [
|
||||
['', '', ''],
|
||||
['', '', ''],
|
||||
['', '', '']
|
||||
];
|
||||
this.nextMove = 'X';
|
||||
this.winner = '';
|
||||
this.setUrl();
|
||||
},
|
||||
grade: function() {
|
||||
var b = this.board;
|
||||
this.winner =
|
||||
row(0) || row(1) || row(2) ||
|
||||
col(0) || col(1) || col(2) ||
|
||||
diagonal(-1) || diagonal(1);
|
||||
function row(row) { return same(b[row][0], b[row][1], b[row][2]);}
|
||||
function col(col) { return same(b[0][col], b[1][col], b[2][col]);}
|
||||
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
|
||||
function same(a, b, c) { return (a==b && b==c) ? a : '';};
|
||||
},
|
||||
setUrl: function() {
|
||||
var rows = [];
|
||||
angular.forEach(this.board, function(row){
|
||||
rows.push(row.join(','));
|
||||
});
|
||||
this.$location.search({board: rows.join(';') + '/' + this.nextMove});
|
||||
},
|
||||
readUrl: function(scope, value) {
|
||||
if (value) {
|
||||
value = value.split('/');
|
||||
this.nextMove = value[1];
|
||||
angular.forEach(value[0].split(';'), function(row, col){
|
||||
this.board[col] = row.split(',');
|
||||
}, this);
|
||||
this.grade();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<h3>Tic-Tac-Toe</h3>
|
||||
<div ng:controller="TicTacToeCntl">
|
||||
Next Player: {{nextMove}}
|
||||
<div class="winner" ng:show="winner">Player {{winner}} has won!</div>
|
||||
<table class="board">
|
||||
<tr ng:repeat="row in board" style="height:15px;">
|
||||
<td ng:repeat="cell in row" ng:style="cellStyle"
|
||||
ng:click="dropPiece($parent.$index, $index)">{{cell}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button ng:click="reset()">reset board</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should play a game', function() {
|
||||
piece(1, 1);
|
||||
expect(binding('nextMove')).toEqual('O');
|
||||
piece(3, 1);
|
||||
expect(binding('nextMove')).toEqual('X');
|
||||
piece(1, 2);
|
||||
piece(3, 2);
|
||||
piece(1, 3);
|
||||
expect(element('.winner').text()).toEqual('Player X has won!');
|
||||
});
|
||||
|
||||
function piece(row, col) {
|
||||
element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
|
||||
}
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Things to notice
|
||||
|
||||
* The controller is defined in JavaScript and has no reference to the rendering logic.
|
||||
* The controller is instantiated by <angular/> and injected into the view.
|
||||
* The controller can be instantiated in isolation (without a view) and the code will still execute.
|
||||
This makes it very testable.
|
||||
* The HTML view is a projection of the model. In the above example, the model is stored in the
|
||||
board variable.
|
||||
* All of the controller's properties (such as board and nextMove) are available to the view.
|
||||
* Changing the model changes the view.
|
||||
* The view can call any controller function.
|
||||
* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
|
||||
hash so the browser's back button will undo game steps. See deep-linking. This example calls {@link
|
||||
api/angular.scope.$watch $watch()} to set up a listener that invokes `readUrl()` when needed.
|
||||
@@ -0,0 +1,101 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Initializing Angular: Automatic Initialization
|
||||
@description
|
||||
|
||||
Angular initializes automatically when you load the angular script into your page, specifying
|
||||
angular's `ng:autobind` attribute with no arguments:
|
||||
|
||||
<script src="angular.js" ng:autobind>
|
||||
|
||||
From a high-level view, this is what happens during angular's automatic initialization process:
|
||||
|
||||
1. The browser loads the page, and then runs the angular script.
|
||||
|
||||
The `ng:autobind` attribute tells angular to compile and manage the whole HTML document. The
|
||||
compilation phase is initiated in the page's `onLoad()` handler. Angular doesn't begin processing
|
||||
the page until after the page load is complete.
|
||||
|
||||
2. Angular finds the root of the HTML document and creates the global variable `angular` in the
|
||||
global namespace. Everything that angular subsequently creates is bound to fields in this global
|
||||
object.
|
||||
|
||||
3. Angular walks the DOM looking for angular widgets, directives, and markup (such as `ng:init` or
|
||||
`ng:repeat`). As angular encounters these, it creates child scopes as necessary and attaches them
|
||||
to the DOM, registers listeners on those scopes, associates any controller functions with their
|
||||
data and their part of the view, and ultimately constructs a runnable application. The resulting
|
||||
app features two-way data-binding and a nice separation between data, presentation, and business
|
||||
logic.
|
||||
|
||||
4. For the duration of the application session (while the page is loaded), angular monitors the
|
||||
state of the application, and updates the view and the data model whenever the state of either one
|
||||
changes.
|
||||
|
||||
For details on how the compiler works, see {@link dev_guide.compiler Angular HTML Compiler}.
|
||||
|
||||
|
||||
## Initialization Options
|
||||
|
||||
The reason why `ng:autobind` exists is because angular should not assume that the entire HTML
|
||||
document should be processed just because the `angular.js` script is included. In order to compile
|
||||
only a part of the document, specify the ID of the element you want to use for angular's root
|
||||
element as the value of the `ng:autobind` attribute:
|
||||
|
||||
ng:autobind="angularContent"
|
||||
|
||||
|
||||
## Auto-bootstrap with `#autobind`
|
||||
|
||||
In some rare cases you can't define the `ng:` prefix before the script tag's attribute (for
|
||||
example, in some CMS systems). In those situations it is possible to auto-bootstrap angular by
|
||||
appending `#autobind` to the `<script src=...>` URL, like in this snippet:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="http://code.angularjs.org/angular.js#autobind"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div xmlns:ng="http://angularjs.org">
|
||||
Hello {{'world'}}!
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
As with `ng:autobind`, you can specify an element id that should be exclusively targeted for
|
||||
compilation as the value of the `#autobind`, for example: `#autobind=angularContent`.
|
||||
|
||||
If angular.js file is being combined with other scripts into a single script file, then all of the
|
||||
config options above apply to this processed script as well. That means if the contents of
|
||||
`angular.js` were appended to `all-my-scripts.js`, then the app can be bootstrapped as:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script src="http://myapp.com/all-my-scripts.js" ng:autobind></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
Hello {{'world'}}!
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
|
||||
## Global Angular Object
|
||||
|
||||
The angular script creates a single global variable `angular` in the global namespace. All angular
|
||||
APIs are bound to fields of this global object.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.bootstrap Initializing Angular}
|
||||
* {@link dev_guide.bootstrap.manual_bootstrap Manual Initialization}
|
||||
|
||||
## Related API
|
||||
|
||||
{@link api/angular.compile Compiler API}
|
||||
@@ -0,0 +1,46 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Initializing Angular: Manual Initialization
|
||||
@description
|
||||
|
||||
Letting angular handle the initialization process (bootstrapping) is a handy way to start using
|
||||
angular, but advanced users who want more control over the initialization process can choose to use
|
||||
the manual bootstrapping method instead.
|
||||
|
||||
The best way to get started with manual bootstrapping is to look at the what happens when you use
|
||||
{@link api/angular.directive.ng:autobind ng:autobind}, by showing each step of the process
|
||||
explicitly.
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script src="http://code.angularjs.org/angular.js"></script>
|
||||
<script>
|
||||
angular.element(document).ready(function() {
|
||||
angular.compile(document)().$apply();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Hello {{'World'}}!
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
This is the sequence that your code should follow if you bootstrap angular on your own:
|
||||
|
||||
1. After the page is loaded, find the root of the HTML template, which is typically the root of
|
||||
the document.
|
||||
2. Run angular's {@link dev_guide.compiler Angular HTML compiler}, which converts a template into
|
||||
an executable, bi-directionally bound application.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.bootstrap Initializing Angular}
|
||||
* {@link dev_guide.bootstrap.auto_bootstrap Automatic Initialization}
|
||||
* {@link dev_guide.compiler Angular HTML compiler}
|
||||
|
||||
## Related API
|
||||
|
||||
{@link api/angular.compile Compiler API}
|
||||
@@ -0,0 +1,64 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Initializing Angular
|
||||
@description
|
||||
|
||||
Initializing angular consists of loading the `angular.js` script in your page, and specifying how
|
||||
angular should process and manage the page. To initialize angular you do the following:
|
||||
|
||||
* Specify the angular namespace in the `<html>` page
|
||||
* Choose which flavor of angular script to load (debug or production)
|
||||
* Specify whether or not angular should process and manage the page automatically (`ng:autobind`)
|
||||
|
||||
The simplest way to initialize angular is to load the angular script and tell angular to compile
|
||||
and manage the whole page. You do this as follows:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
...
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
<script src="angular.js" ng:autobind>
|
||||
</body>
|
||||
</pre>
|
||||
|
||||
|
||||
## Specifying the Angular Namespace
|
||||
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
|
||||
You need to declare the angular namespace declaration in the following cases:
|
||||
|
||||
* For all types of browser if you are using XHTML.
|
||||
* For Internet Explorer older than version 9 (because older versions of IE do not render widgets
|
||||
properly for either HTML or XHTML).
|
||||
|
||||
|
||||
## Creating Your Own Namespaces
|
||||
|
||||
When you are ready to define your own {@link dev_guide.compiler.widgets widgets}, you must create
|
||||
your own namespace in addition to specifying the angular namespace. You use your own namespace to
|
||||
form the fully qualified name for widgets that you create.
|
||||
|
||||
For example, you could map the alias `my` to your domain, and create a widget called `my:widget`.
|
||||
To create your own namespace, simply add another `xmlns` tag to your page, create an alias, and set
|
||||
it to your unique domain:
|
||||
|
||||
<html xmlns:ng="http://angularjs.org" xmlns:my="http://mydomain.com">
|
||||
|
||||
|
||||
## Loading the Angular Bootstrap Script
|
||||
|
||||
The angular bootstrap script comes in two flavors; a debug script, and a production script:
|
||||
|
||||
* angular-[version].js - This is a human-readable file, suitable for development and debugging.
|
||||
* angular-[version].min.js - This is a compressed and obfuscated file, suitable for use in
|
||||
production.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.bootstrap.auto_bootstrap Automatic Initialization}
|
||||
* {@link dev_guide.bootstrap.manual_bootstrap Manual Initialization}
|
||||
@@ -0,0 +1,38 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Directives: Creating Custom Angular Directives
|
||||
@description
|
||||
|
||||
The following code snippet shows how to define a custom directive. You define a new directive by
|
||||
extending the {@link dev_guide.compiler Angular HTML compiler}. The code snippet below is a
|
||||
simplified definition of the built-in {@link api/angular.directive.ng:bind ng:bind} directive:
|
||||
|
||||
<pre>
|
||||
angular.directive('ng:bind', function(expression, compiledElement) {
|
||||
var compiler = this;
|
||||
return function(linkElement) {
|
||||
var currentScope = this;
|
||||
currentScope.$watch(expression, function(value) {
|
||||
linkElement.text(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
# Additional Compiler Methods for Custom Directives
|
||||
|
||||
The angular compiler exposes methods that you may need to use when writing your own widgets and
|
||||
directives. For example, the `descend()` method lets you control whether the compiler ignores or
|
||||
processes child elements of the element it is compiling. For information on this and other
|
||||
compiler methods, see the {@link api/angular.compile Compiler API doc}.
|
||||
|
||||
|
||||
## Related Docs
|
||||
|
||||
* {@link dev_guide.compiler.directives Understanding Angular Directives}
|
||||
* {@link dev_guide.compiler.directives_widgets Comparing Directives and Widgets}
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.directive Angular Directive API}.
|
||||
@@ -0,0 +1,46 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Understanding Angular Directives
|
||||
@description
|
||||
|
||||
An angular directive is a custom HTML attribute that angular knows how to process. You add them to
|
||||
a template element like any other attribute. Angular directives all have a `ng:` prefix. In the
|
||||
following example, the angular directive (`ng:controller`) is a div tag:
|
||||
|
||||
<div ng:controller>
|
||||
|
||||
You use angular directives to modify DOM element properties. The element you modify can be an
|
||||
existing HTML element type or a custom DOM element type that you created. You can use any number of
|
||||
directives per element.
|
||||
|
||||
You add angular directives to a standard HTML tag as in the following example, in which we have
|
||||
added the {@link api/angular.directive.ng:click ng:click} directive to a button tag:
|
||||
|
||||
<button ng:model="button1" ng:click="foo()">Click This</button>
|
||||
|
||||
In the example above, `name` is the standard HTML attribute, and `ng:click` is the angular
|
||||
directive. The `ng:click` directive lets you implement custom behavior in an associated controller
|
||||
function.
|
||||
|
||||
In the next example, we add the {@link api/angular.directive.ng:bind ng:bind} directive to a
|
||||
`<span>` tag:
|
||||
|
||||
<span ng:bind="1+2"></span>
|
||||
|
||||
The `ng:bind` directive tells angular to set up {@link dev_guide.templates.databinding data
|
||||
binding} between the data model and the view for the specified expression. When the angular {@link
|
||||
dev_guide.compiler compiler} encounters an `ng:bind` directive in a template, it passes the
|
||||
attribute value to the `ng:bind` function, which in turn sets up the data binding. On any change to
|
||||
the expression in the model, the view is updated to display the span text with the changed
|
||||
expression value.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
* {@link dev_guide.compiler.directives.creating_directives Creating Angular Directives}
|
||||
* {@link dev_guide.compiler.directives_widgets Comparing Directives and Widgets}
|
||||
|
||||
## Related API:
|
||||
|
||||
* {@link api/angular.directive Directive API}
|
||||
* {@link api/angular.widget Widget API}
|
||||
@@ -0,0 +1,47 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Comparing Directives and Attribute Widgets
|
||||
@description
|
||||
|
||||
Although directives and {@link dev_guide.compiler.widgets attribute widgets} appear the same in a
|
||||
template (`ng:init` is a directive, `ng:repeat` is an attribute widget), there is a difference in
|
||||
the order in which they are evaluated. The user of existing directives or widgets cannot determine
|
||||
the order of evaluation. The evaluation order is the responsibility of the developer creating
|
||||
custom directives and widgets.
|
||||
|
||||
For example, consider this piece of HTML, which uses the `ng:repeat`, `ng:init`, and `ng:bind`
|
||||
widget and directives:
|
||||
|
||||
<pre>
|
||||
<ul ng:init="people=['mike', 'mary']">
|
||||
<li ng:repeat="person in people"
|
||||
ng:init="a=a+1"
|
||||
ng:bind="person">
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
|
||||
Notice that the order of execution matters here. Because we want to run the `ng:init="a=a+1` and
|
||||
`ng:bind="person"` once for each `person in people`, we need to execute {@link
|
||||
api/angular.widget.@ng:repeat ng:repeat} to make copies of the `<li>` element before we run the
|
||||
{@link api/angular.directive.ng:init ng:init}, and {@link api/angular.directive.ng:bind ng:bind}
|
||||
for each of the `<li>`copies.
|
||||
|
||||
If you implemented `ng:repeat` as a directive, there would be no guarantee that the attributes
|
||||
`ng:repeat`, `ng:init`, and `ng:bind` would be evaluated in the order they are declared, because
|
||||
the order of element attributes in HTML is not significant to the browser.
|
||||
|
||||
So, when creating a custom HTML attribute, you will have to consider whether a directive or a
|
||||
widget is more appropriate. When the order of execution doesn't matter, directives are the right
|
||||
choice. In a situation where the order matters and one attribute should be processed with a higher
|
||||
priority than others, use a widget for the attribute that must be processed first.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler.directives Understanding Angular Directives}
|
||||
* {@link dev_guide.compiler.widgets Understanding Angular Widgets}
|
||||
|
||||
## Related API:
|
||||
|
||||
* {@link api/angular.directive Directive API}
|
||||
* {@link api/angular.widget Widget API}
|
||||
@@ -0,0 +1,96 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Extending the Angular Compiler
|
||||
@description
|
||||
|
||||
Let's say that we want to create a new DOM element called `<my:greeter/>` that displays a greeting.
|
||||
We want this HTML source:
|
||||
|
||||
<pre>
|
||||
<div ng:init="s='Hello'; n='World'">
|
||||
<my:greeter salutation="s" name="n"></my:greeter>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
to produce this DOM:
|
||||
|
||||
<pre>
|
||||
<div ng:init="s='Hello'; n='World'">
|
||||
<my:greeter salutation="s" name="n"/>
|
||||
<span class="salutation">Hello</span>
|
||||
<span class="name">World</span>!
|
||||
</my:greeter>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
That is, the new `<my:greeter></my:greeter>` tag's `salutation` and `name` attributes should be
|
||||
transformed by the compiler such that two `<span>` tags display the values of the attributes, with
|
||||
CSS classes applied to the output.
|
||||
|
||||
The following code snippet shows how to write a following widget definition that will be processed
|
||||
by the compiler. Note that you have to declare the {@link dev_guide.bootstrap namespace} `my` in
|
||||
the page:
|
||||
|
||||
<pre>
|
||||
angular.widget('my:greeter', function(compileElement){
|
||||
var compiler = this;
|
||||
compileElement.css('display', 'block');
|
||||
var salutationExp = compileElement.attr('salutation');
|
||||
var nameExp = compileElement.attr('name');
|
||||
return function(linkElement){
|
||||
var salutationSpan = angular.element('<span class="salutation"></span');
|
||||
var nameSpan = angular.element('<span class="name"></span>');
|
||||
linkElement.append(salutationSpan);
|
||||
linkElement.append(' ');
|
||||
linkElement.append(nameSpan);
|
||||
linkElement.append('!');
|
||||
this.$watch(salutationExp, function(value){
|
||||
salutationSpan.text(value);
|
||||
});
|
||||
this.$watch(nameExp, function(value){
|
||||
nameSpan.text(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
Note: For more about widgets, see {@link dev_guide.compiler.widgets Understanding Angular Widgets}
|
||||
and the {@link api/angular.widget widget API reference page}.
|
||||
|
||||
# Compilation process for `<my:greeter>`
|
||||
|
||||
Here are the steps that the compiler takes in processing the page that contains the widget
|
||||
definition above:
|
||||
|
||||
## Compile Phase
|
||||
|
||||
1. Recursively traverse the DOM depth-first.
|
||||
2. Find the angular.widget definition.
|
||||
3. Find and execute the widget's compileElement function, which includes the following steps:
|
||||
1. Add a style element with attribute display: block; to the template DOM so that the browser
|
||||
knows to treat the element as block element for rendering. (Note: because this style element was
|
||||
added on the template compileElement, this style is automatically applied to any clones of the
|
||||
template (i.e. any repeating elements)).
|
||||
2. Extract the salutation and name HTML attributes as angular expressions.
|
||||
4. Return the aggregate link function, which includes just one link function in this example.
|
||||
|
||||
## Link Phase
|
||||
|
||||
1. Execute the aggregate link function, which includes the following steps:
|
||||
1. Create a <span> element set to the salutation class
|
||||
2. Create a <span> element set to the name class.
|
||||
2. Add the span elements to the linkElement. (Note: be careful not to add them to the
|
||||
compileElement, because that's the template.)
|
||||
3. Set up watches on the expressions. When an expression changes, copy the data to the
|
||||
corresponding spans.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
* {@link dev_guide.compiler.understanding_compiler Understanding How the Compiler Works}
|
||||
* {@link dev_guide.compiler.testing_dom_element Testing a New DOM Element}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.compile angular.compile()}
|
||||
@@ -0,0 +1,92 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Understanding Angular Markup
|
||||
@description
|
||||
|
||||
Markup in angular is a feature that you can use in templates to transform the content of DOM
|
||||
elements prior to the compile phase (in which elements are compiled and link functions are
|
||||
returned. See the {@link dev_guide.compiler compiler docs} for details on how the compiler
|
||||
works.) The ability to make pre-compile changes to DOM elements lets you create shorthand for
|
||||
{@link api/angular.widget widget} and {@link api/angular.directive directive} declarations.
|
||||
|
||||
Angular provides one built-in markup feature: the double curly-braces used to declare binding
|
||||
points (between the model and view) for angular expressions. You can also create your own custom
|
||||
markup.
|
||||
|
||||
# Using Double Curly-brace Markup (`{{ }}`)
|
||||
|
||||
The double curly-brace (`{{ }}`) markup translates an enclosed expression into an {@link
|
||||
api/angular.directive.ng:bind ng:bind} directive:
|
||||
|
||||
<pre>
|
||||
{{expression}}
|
||||
</pre>
|
||||
|
||||
is transformed to:
|
||||
|
||||
<pre>
|
||||
<span ng:bind="expression"></span>
|
||||
</pre>
|
||||
|
||||
Markup is useful for the simple reason that `{{1+2}}` is easier to write and understand than `<span
|
||||
ng:bind="1+2"></span>`. After markup shorthand is expanded into the DOM elements it represents, the
|
||||
expanded elements are then {@link dev_guide.compiler compiled} normally.
|
||||
|
||||
|
||||
# Creating Custom Markup
|
||||
|
||||
Let's say you want to define markup that transforms `---` into a horizontal rule (`<hr/>`):
|
||||
|
||||
<pre>
|
||||
header
|
||||
---
|
||||
footer
|
||||
</pre>
|
||||
|
||||
should translate to:
|
||||
<pre>
|
||||
header
|
||||
<hr/>
|
||||
footer
|
||||
</pre>
|
||||
|
||||
Here is how you could extend the angular compiler to create the "---" markup:
|
||||
|
||||
<pre>
|
||||
angular.markup('---', function(text, textNode, parentElement) {
|
||||
var compiler = this;
|
||||
var index = text.indexOf('---');
|
||||
if (index > -1) {
|
||||
textNode.after(text.substring(index + 3));
|
||||
textNode.after(angular.element('<hr>'));
|
||||
textNode.after(text.substring(0, index));
|
||||
textNode.remove();
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
Unlike the way the compiler processes {@link api/angular.widget widgets} and {@link
|
||||
api/angular.directive directives} (matching the name of the handler function to a DOM element or
|
||||
attribute name), the compiler calls every markup handler for every text node, giving the handler a
|
||||
chance to transform the text. The markup handler needs to find all the matches in the text.
|
||||
|
||||
## Attribute Markup
|
||||
|
||||
Attribute markup extends the angular compiler in a very similar way to markup, except that it
|
||||
allows you to modify the state of attribute text rather then the content of a node.
|
||||
|
||||
<pre>
|
||||
angular.attrMarkup('extraClass', function(attrValue, attrName, element){
|
||||
if (attrName == 'additional-class') {
|
||||
element.addClass(attrValue);
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.compile Compiler API Reference}
|
||||
@@ -0,0 +1,26 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler
|
||||
@description
|
||||
|
||||
The core of angular is its HTML compiler. The compiler processes angular directives, widgets, and
|
||||
markup to transform a static HTML page into a dynamic web application.
|
||||
|
||||
The default HTML transformations that the angular compiler provides are useful for building generic
|
||||
apps, but you can also extend the compiler to create a domain-specific language for building
|
||||
specific types of web applications.
|
||||
|
||||
All compilation takes place in the web browser; no server is involved.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler.understanding_compiler Understanding How the Compiler Works}
|
||||
* {@link dev_guide.compiler.extending_compiler Extending the Angular Compiler}
|
||||
* {@link dev_guide.compiler.testing_dom_element Testing a New DOM Element}
|
||||
* {@link dev_guide.compiler.widgets Understanding Angular Widgets}
|
||||
* {@link dev_guide.compiler.directives Understanding Angular Directives}
|
||||
* {@link dev_guide.compiler.markup Understanding Angular Markup}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.compile Angular Compiler API}
|
||||
@@ -0,0 +1,17 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Testing a New DOM Element
|
||||
@description
|
||||
|
||||
|
||||
"Testing, testing, come in, over?"
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
* {@link dev_guide.compiler.understanding_compiler Understanding How the Compiler Works}
|
||||
* {@link dev_guide.compiler.extending_compiler Extending the Angular Compiler}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.compile angular.compile()}
|
||||
@@ -0,0 +1,68 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Understanding How the Compiler Works
|
||||
@description
|
||||
|
||||
Every {@link api/angular.widget widget}, {@link api/angular.directive directive} and {@link
|
||||
dev_guide.compiler.markup markup} is defined with a compile function, which the angular compiler
|
||||
executes on each widget or directive it encounters. The compile function optionally returns a link
|
||||
function. This compilation process happens automatically when the page is loaded when you specify
|
||||
`ng:autobind` in the script tag from which you load the angular script file. (See {@link
|
||||
dev_guide.bootstrap Initializing Angular}.)
|
||||
|
||||
The compile and link functions are related as follows:
|
||||
|
||||
* **compile function** — Registers a listener for the widget, directive, or markup expression. The
|
||||
compiler calls this function exactly once.
|
||||
* **link function** — Sets up the listener registered by the compile function. This function can be
|
||||
called multiple times, once per cloned DOM element. For example, in the case of the {@link
|
||||
api/angular.widget.@ng:repeat repeater widget} used in a list element (`<li ng:repeat="[item in
|
||||
dataset]"`), the link function gets called to set up a listener on each element in the list.
|
||||
|
||||
Note that angular's built-in widgets, directives, and markup have predefined compile and link
|
||||
functions that you don't need to modify. When you create your own widgets, directives, or markup,
|
||||
you must write compile and link functions for them. Refer to the {@link api/angular.compile
|
||||
Compiler API} for details.
|
||||
|
||||
When the angular compiler compiles a page, it proceeds through 3 phases: Compile, Create Root
|
||||
Scope, and Link:
|
||||
|
||||
1. Compile Phase
|
||||
|
||||
1. Recursively traverse the DOM, depth-first.
|
||||
2. Look for a matching compile function of type widget, then markup, then directive.
|
||||
3. If a compile function is found then execute it.
|
||||
4. When the compile function completes, it should return a link function. Aggregate this link
|
||||
function with all link functions returned previously by step 3.
|
||||
5. Repeat steps 3 and 4 for all compile functions found.
|
||||
|
||||
The result of the compilation phase is an aggregate link function, which comprises all of the
|
||||
individual link functions.
|
||||
|
||||
2. Create Root Scope Phase
|
||||
|
||||
* Inject all services into the root scope.
|
||||
|
||||
3. Link Phase
|
||||
|
||||
1. Execute the aggregate link function with the root scope. The aggregate link function calls
|
||||
all of the individual link functions that were generated in the compile phase.
|
||||
2. If there are any clones of the DOM caused by repeating elements, call the link function
|
||||
multiple times, one for each repeating item.
|
||||
|
||||
Note that while the compile function is executed exactly once, the link function can be executed
|
||||
multiple times, for example, once for each iteration in a repeater.
|
||||
|
||||
The angular compiler exposes methods that you will need to make use of when writing your own
|
||||
widgets and directives. For information on these methods, see the {@link api/angular.compile
|
||||
Compiler API doc}.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
* {@link dev_guide.compiler.extending_compiler Extending the Angular Compiler}
|
||||
* {@link dev_guide.compiler.testing_dom_element Testing a New DOM Element}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.compile angular.compile()}
|
||||
@@ -0,0 +1,95 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Widgets: Creating Custom Widgets
|
||||
@description
|
||||
|
||||
When you create your own widgets, you must set up your own namespace for them. (See
|
||||
dev_guide.bootstrap Initializing Angular} for information about namespaces in angular.)
|
||||
|
||||
Let's say we would like to create a new element type in the namespace `my` that can watch an
|
||||
expression and `alert()` the user with each new value:
|
||||
|
||||
<pre>
|
||||
// An element widget
|
||||
<my:watch exp="name"></my:watch>
|
||||
</pre>
|
||||
|
||||
You can implement `my:watch` like this:
|
||||
|
||||
<pre>
|
||||
angular.widget('my:watch', function(compileElement) {
|
||||
var compiler = this;
|
||||
var exp = compileElement.attr('exp');
|
||||
return function(linkElement) {
|
||||
var currentScope = this;
|
||||
currentScope.$watch(exp, function(value){
|
||||
alert(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
# Creating a Custom Attribute Widget
|
||||
|
||||
Let's implement the same widget as in the example in Defining an Element Widget, but this time as
|
||||
an attribute that can be added to any existing DOM element:
|
||||
|
||||
<pre>
|
||||
// An attribute widget (my:watch) in a div tag
|
||||
<div my:watch="name">text</div>
|
||||
</pre>
|
||||
You can implement `my:watch` attribute like this:
|
||||
<pre>
|
||||
angular.widget('@my:watch', function(expression, compileElement) {
|
||||
var compiler = this;
|
||||
return function(linkElement) {
|
||||
var currentScope = this;
|
||||
currentScope.$watch(expression, function(value) {
|
||||
alert(value);
|
||||
});
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
# Live Example of a Custom Element Widget
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.widget('my:time', function(compileElement){
|
||||
compileElement.css('display', 'block');
|
||||
return function(linkElement){
|
||||
function update() {
|
||||
linkElement.text('Current time is: ' + new Date());
|
||||
setTimeout(update, 1000);
|
||||
}
|
||||
update();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<my:time></my:time>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Additional Compiler Methods for Custom Widgets
|
||||
|
||||
The angular compiler exposes methods that you may need to use of when writing your own widgets and
|
||||
directives. For example, the `descend()` method lets you control whether the compiler ignores or
|
||||
processes child elements of the element it is compiling. For information on this and other
|
||||
compiler methods, see the {@link api/angular.compile Compiler API doc}.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
* {@link dev_guide.compiler.directives Angular Directives}
|
||||
* {@link dev_guide.compiler.widgets Angular Widgets}
|
||||
* {@link dev_guide.compiler.directives.creating_directives Creating Custom Directives}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.compile Compiler API}
|
||||
@@ -0,0 +1,35 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Understanding Angular Widgets
|
||||
@description
|
||||
|
||||
Widgets are DOM elements that the browser doesn't already understand. Angular provides some
|
||||
built-in widgets (such as {@link api/angular.widget.@ng:repeat ng:repeat}), and you can create your
|
||||
own custom widgets.
|
||||
|
||||
Widgets are intended to manipulate the DOM tree by adding new elements (unlike {@link
|
||||
dev_guide.compiler.directives angular directives}, which are intended to modify only element
|
||||
properties).
|
||||
|
||||
Widgets come in two types:
|
||||
|
||||
* Element Widget — A custom DOM element. An example of a custom element is shown in {@link
|
||||
dev_guide.compiler.widgets.creating_widgets Creating Custom Widgets}.
|
||||
|
||||
* Attribute Widget — A custom attribute on an existing DOM element. An attribute widget is similar
|
||||
to an angular directive, with the main difference being that an attribute widget will always be
|
||||
processed before any directives that are specified on the same element. Only one attribute widget
|
||||
is allowed per element. An example of an attribute widget is shown in {@link
|
||||
dev_guide.compiler.widgets.creating_widgets Creating Custom Widgets}.
|
||||
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
* {@link dev_guide.compiler.directives Angular Directives}
|
||||
* {@link dev_guide.compiler.widgets.creating_widgets Creating Custom Widgets}
|
||||
* {@link dev_guide.compiler.directives.creating_directives Creating Custom Directives}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.compile Compiler API}
|
||||
@@ -0,0 +1,32 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About Dependency Injection (DI)
|
||||
@description
|
||||
|
||||
Dependency Injection (DI) is an object-oriented software design pattern that supports the
|
||||
decoupling and dependency management of application components.
|
||||
|
||||
The idea behind DI is to decouple each component from all of the other components that it depends
|
||||
on to do its particular job. The way this is done in DI is by moving the responsibility for
|
||||
managing dependencies out of each individual component and into a provider component. The provider
|
||||
(or injector) component manages the life cycles and dependencies for all of the other components in
|
||||
an application.
|
||||
|
||||
Angular has a built-in dependency management subsystem that helps to make your applications easier
|
||||
to develop, understand, and test.
|
||||
|
||||
For more information on DI in general, see {@link http://en.wikipedia.org/wiki/Dependency_injection
|
||||
Dependency Injection} at Wikipedia, and {@link http://martinfowler.com/articles/injection.html
|
||||
Inversion of Control} by Martin Fowler, or read about DI in your favorite software design pattern
|
||||
book.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.di.understanding_di Understanding DI in Angular}
|
||||
* {@link dev_guide.services Angular Services}
|
||||
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.service Service API}
|
||||
* {@link api/angular.injector Angular Injector API}
|
||||
@@ -0,0 +1,105 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: DI: Understanding DI in Angular
|
||||
@description
|
||||
|
||||
|
||||
While DI is widely used in statically typed languages such as Java or C++, it has not been widely
|
||||
used in JavaScript. Angular brings the benefits of DI into JavaScript apps.
|
||||
|
||||
In angular, DI is implemented as a subsystem that manages dependencies between services,
|
||||
controllers, widgets, and filters. The most important of these are {@link api/angular.service
|
||||
services}.
|
||||
|
||||
Services are objects that handle common tasks in web applications. Angular provides several{@link
|
||||
api/angular.service built-in services}, and you can create your own custom services.
|
||||
|
||||
The main job of angular's DI subsystem is to provide services to angular components that depend on
|
||||
them. The way the DI subsystem provides services is as follows: all services are registered with
|
||||
angular's {@link api/angular.service service API}, and all components that depend on services
|
||||
define those dependencies as a property (`$inject`). With this information, the DI subsystem
|
||||
manages the creation of service objects and the provision of those objects to the components that
|
||||
need them, at the time they need them. The following illustration steps through the sequence of
|
||||
events:
|
||||
|
||||
<img src="img/guide/di_sequence_final.png">
|
||||
|
||||
In the illustration above, the dependency injection sequence proceeds as follows:
|
||||
|
||||
1. Service factory functions are registered with angular's service factory repository.
|
||||
2. `ng:autobind` triggers angular's bootstrap sequence, during which angular compiles the template,
|
||||
creates the root scope, and creates the dependency injector.
|
||||
3. The `ng:controller` directive implicitly creates a new child scope, augmented by the application
|
||||
of the `PhoneListCtrl` controller function.
|
||||
4. The Injector identifies the `$xhr` service as `PhoneListCtrl` controller's only dependency.
|
||||
5. The Injector checks if the `$xhr` service has already been instantiated, and if not uses the
|
||||
factory function from the service factory repository to construct it.
|
||||
6. DI provides the instance of $xhr service to the PhoneListCtrl controller constructor
|
||||
|
||||
|
||||
## How Scope Relates to DI
|
||||
|
||||
The {@link api/angular.injector injector} is responsible for resolving the service dependencies in
|
||||
the application. It gets created and configured with the creation of a root scope. The injector
|
||||
caches instances of services, with the services cache bound to the root scope.
|
||||
|
||||
Different root scopes have different instances of the injector. While typical angular applications
|
||||
will only have one root scope (and hence the services will act like application singletons), in
|
||||
tests it is important to not share singletons across test invocations for isolation reasons. We
|
||||
achieve the necessary isolation by having each test create its own separate root scope.
|
||||
|
||||
<pre>
|
||||
// create a root scope
|
||||
var rootScope = angular.scope();
|
||||
// access the service locator
|
||||
var myService = rootScope.$service('myService');
|
||||
</pre>
|
||||
|
||||
## Inferring dependencies from the signature of the factory function or constructor
|
||||
|
||||
**EXPERIMENTAL FEATURE**: This is an experimental feature. See the important note at the end of
|
||||
this section for drawbacks.
|
||||
|
||||
We resort to `$inject` and our own annotation because there is no way in JavaScript to get a list
|
||||
of arguments. Or is there? It turns out that calling `.toString()` on a function returns the
|
||||
function declaration along with the argument names as shown below:
|
||||
|
||||
<pre>
|
||||
function myFn(a,b){}
|
||||
expect(myFn.toString()).toEqual('function myFn(a,b){}');
|
||||
</pre>
|
||||
|
||||
This means that angular can infer the function names after all and use that information to generate
|
||||
the `$inject` annotation automatically. Therefore the following two function definitions are
|
||||
equivalent:
|
||||
|
||||
<pre>
|
||||
// given a user defined service
|
||||
angular.service('serviceA', ...);
|
||||
|
||||
// inject '$window', 'serviceA', curry 'name';
|
||||
function fnA($window, serviceA, name){};
|
||||
fnA.$inject = ['$window', 'serviceA'];
|
||||
|
||||
// inject '$window', 'serviceA', curry 'name';
|
||||
function fnB($window, serviceA_, name){};
|
||||
// implies: fnB.$inject = ['$window', 'serviceA'];
|
||||
</pre>
|
||||
|
||||
If angular does not find a `$inject` annotation on the function, then it calls the `.toString()`
|
||||
method and tries to infer what should be injected by using function argument names as dependency
|
||||
identifiers.
|
||||
|
||||
**IMPORTANT**
|
||||
Minifiers/obfuscators change the names of function arguments and will therefore break the `$inject`
|
||||
inference. For this reason, either explicitly declare the `$inject` or do not use
|
||||
minifiers/obfuscators. In the future, we may provide a pre-processor which will scan the source
|
||||
code and insert the `$inject` into the source code so that it can be minified/obfuscated.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.services Angular Services}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.service Services API}
|
||||
@@ -0,0 +1,53 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: DI: Using DI in Controllers
|
||||
@description
|
||||
|
||||
The most common place to use dependency injection in angular applications is in {@link
|
||||
dev_guide.mvc.understanding_controller controllers}. Here is a simple example:
|
||||
|
||||
<pre>
|
||||
function MyController($route){
|
||||
// configure the route service
|
||||
$route.when(...);
|
||||
}
|
||||
MyController.$inject = ['$route'];
|
||||
</pre>
|
||||
|
||||
In this example, the `MyController` constructor function takes one argument, the {@link
|
||||
api/angular.service.$route $route} service. Angular is then responsible for supplying the instance
|
||||
of `$route` to the controller when the constructor is instantiated. There are two ways to cause
|
||||
controller instantiation – by configuring routes with the `$route` service, or by referencing the
|
||||
controller from the HTML template, as follows:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" ng:controller="MyController">
|
||||
<script src="http://code.angularjs.org/angular.min.js" ng:autobind></script>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
When angular is instantiating your controller, it needs to know what services, if any, should be
|
||||
injected (passed in as arguments) into the controller. Since there is no reflection in JavaScript,
|
||||
we have to supply this information to angular in the form of an additional property on the
|
||||
controller constructor function called `$inject`. Think of it as annotations for JavaScript.
|
||||
|
||||
<pre>
|
||||
MyController.$inject = ['$route'];
|
||||
</pre>
|
||||
|
||||
The information in `$inject` is then used by the {@link api/angular.injector injector} to call the
|
||||
function with the correct arguments.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.di About Dependency Injection}
|
||||
* {@link dev_guide.di.understanding_di Understanding Dependency Injection in Angular}
|
||||
* {@link dev_guide.services Angular Services}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.injector Angular Injector API}
|
||||
@@ -0,0 +1,178 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: E2E Testing
|
||||
@description
|
||||
|
||||
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.
|
||||
|
||||
To solve this problem, we have built an Angular Scenario Runner which simulates user interactions
|
||||
that will help you verify the health of your Angular application.
|
||||
|
||||
# Overview
|
||||
You will write scenario tests in JavaScript, which 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.
|
||||
|
||||
<img src="img/guide/scenario_runner.png">
|
||||
|
||||
In addition to the above elements, scenarios may also contain helper functions to avoid duplicating
|
||||
code in the `it` blocks.
|
||||
|
||||
Here is an example of a simple scenario:
|
||||
<pre>
|
||||
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);
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
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 'user' input field, clicking
|
||||
the only button on the page, and then it verifies that there are 10 items listed. It then enters
|
||||
'Bees' in the 'filterText' input field and verifies that the list is reduced to a single item.
|
||||
|
||||
The API section below lists the available commands and expectations for the Runner.
|
||||
|
||||
# API
|
||||
Source: {@link https://github.com/angular/angular.js/blob/master/src/scenario/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 api/angular.service.$location $location.url()} of the currently loaded page in
|
||||
the test frame.
|
||||
|
||||
## browser().location().path()
|
||||
Returns the {@link api/angular.service.$location $location.path()} of the currently loaded page in
|
||||
the test frame.
|
||||
|
||||
## browser().location().search()
|
||||
Returns the {@link api/angular.service.$location $location.search()} of the currently loaded page
|
||||
in the test frame.
|
||||
|
||||
## browser().location().hash()
|
||||
Returns the {@link api/angular.service.$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')`
|
||||
|
||||
## 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 given `name`.
|
||||
|
||||
## input(name).check()
|
||||
Checks/unchecks the checkbox with the given `name`.
|
||||
|
||||
## input(name).select(value)
|
||||
Selects the given `value` in the radio button with the given `name`.
|
||||
|
||||
## input(name).val()
|
||||
Returns the current value of an input field with the given `name`.
|
||||
|
||||
## repeater(selector, label).count()
|
||||
Returns the number of rows in the repeater matching the given jQuery `selector`. The `label` is
|
||||
used for test ouput.
|
||||
|
||||
## 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 `name`.
|
||||
|
||||
## select(name).option(value1, value2...)
|
||||
Picks the options with the given `values` on the multi select with the given `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.
|
||||
|
||||
JavaScript is a dynamically typed language which comes with great power of expression, but it also
|
||||
come with almost no-help from the compiler. For this reason we feel very strongly that any code
|
||||
written in JavaScript needs to come with a strong set of tests. We have built many features into
|
||||
angular which makes testing your angular applications easy. So there is no excuse for not do it.
|
||||
@@ -0,0 +1,235 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Understanding Angular Expressions
|
||||
@description
|
||||
|
||||
Expressions are {@link dev_guide.templates.databinding bindings} that you write in HTML and embed
|
||||
in templates in order to create views in angular. Angular expressions are similar but not
|
||||
equivalent to JavaScript expressions.
|
||||
|
||||
For example, these are all valid expressions in angular:
|
||||
|
||||
* `1+2={{1+2}}`
|
||||
* `3*10|currency`
|
||||
* `Hello {{name}}!`
|
||||
* `Hello {{'World'}}!`
|
||||
|
||||
|
||||
## Angular Expressions vs. JS Expressions
|
||||
|
||||
It might be tempting to think of angular view expressions as JavaScript expressions, but that is
|
||||
not entirely correct. Angular does not use a simple JavaScript eval of the expression text. You can
|
||||
think of angular expressions as JavaScript expressions with these differences:
|
||||
|
||||
* **Attribute Evaluation:** evaluation of all attributes are against the current scope, not to the
|
||||
global window as in JavaScript.
|
||||
* **Forgiving:** expression evaluation is forgiving to undefined and null, unlike in JavaScript.
|
||||
* **No Control Flow Statements:** you cannot do the following from an angular expression:
|
||||
conditionals, loops, or throw.
|
||||
* **Type Augmentation:** the scope expression evaluator augments built-in types.
|
||||
* **Filters:** you can add filters to an expression, for example to convert raw data into a
|
||||
human-readable format.
|
||||
* **The $:** angular reserves this prefix to differentiate its API names from others.
|
||||
|
||||
If, on the other hand, you do want to run arbitrary JavaScript code, you should make it a
|
||||
controller method and call that. If you want to `eval()` an angular expression from JavaScript, use
|
||||
the `Scope:$eval()` method.
|
||||
|
||||
## Example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
1+2={{1+2}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate expression in binding', function() {
|
||||
expect(binding('1+2')).toEqual('3');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
You can try evaluating different expressions here:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function Cntl2() {
|
||||
this.exprs = [];
|
||||
this.expr = '3*10|currency';
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="Cntl2" class="expressions">
|
||||
Expression:
|
||||
<input type='text' ng:model="expr" size="80"/>
|
||||
<button ng:click="exprs.$add(expr)">Evaluate</button>
|
||||
<ul>
|
||||
<li ng:repeat="expr in exprs">
|
||||
[ <a href="" ng:click="exprs.$remove(expr)">X</a> ]
|
||||
<tt>{{expr}}</tt> => <span ng:bind="$parent.$eval(expr)"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should allow user expression testing', function() {
|
||||
element('.expressions :button').click();
|
||||
var li = using('.expressions ul').repeater('li');
|
||||
expect(li.count()).toBe(1);
|
||||
expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Attribute Evaluation
|
||||
|
||||
Evaluation of all attributes takes place against the current scope. Unlike JavaScript, where names
|
||||
default to global window properties, angular expressions have to use `$window` to refer to the
|
||||
global object. For example, if you want to call `alert()`, which is defined on `window`, an
|
||||
expression must use `$window.alert()`. This is done intentionally to prevent accidental access to
|
||||
the global state (a common source of subtle bugs).
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function Cntl1($window){
|
||||
this.name = 'World';
|
||||
|
||||
this.greet = function() {
|
||||
($window.mockWindow || $window).alert('Hello ' + this.name);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div class="example2" ng:controller="Cntl1">
|
||||
Name: <input ng:model="name" type="text"/>
|
||||
<button ng:click="greet()">Greet</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate expression in binding', function() {
|
||||
var alertText;
|
||||
this.addFutureAction('set mock', function($window, $document, done) {
|
||||
$window.mockWindow = {
|
||||
alert: function(text){ alertText = text; }
|
||||
};
|
||||
done();
|
||||
});
|
||||
element(':button:contains(Greet)').click();
|
||||
expect(this.addFuture('alert text', function(done) {
|
||||
done(null, alertText);
|
||||
})).toBe('Hello World');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
## Forgiving
|
||||
|
||||
Expression evaluation is forgiving to undefined and null. In JavaScript, evaluating `a.b.c` throws
|
||||
an exception if `a` is not an object. While this makes sense for a general purpose language, the
|
||||
expression evaluations are primarily used for data binding, which often look like this:
|
||||
|
||||
{{a.b.c}}
|
||||
|
||||
It makes more sense to show nothing than to throw an exception if `a` is undefined (perhaps we are
|
||||
waiting for the server response, and it will become defined soon). If expression evaluation wasn't
|
||||
forgiving we'd have to write bindings that clutter the code, for example: `{{((a||{}).b||{}).c}}`
|
||||
|
||||
Similarly, invoking a function `a.b.c()` on undefined or null simply returns undefined.
|
||||
|
||||
Assignments work the same way in reverse:
|
||||
|
||||
a.b.c = 10
|
||||
|
||||
...creates the intermediary objects even if a is undefined.
|
||||
|
||||
|
||||
## 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 in the view. If you need a
|
||||
conditional (including ternary operators), loop, or to throw from a view expression, delegate to a
|
||||
JavaScript method instead.
|
||||
|
||||
|
||||
## Type Augmentation
|
||||
|
||||
Built-in types have methods like `[].push()`, but the richness of these methods is limited.
|
||||
Consider the example below, which allows you to do a simple search over a canned set of contacts.
|
||||
The example would be much more complicated if we did not have the `Array:$filter()`. There is no
|
||||
built-in method on `Array` called {@link api/angular.Array.filter $filter} and angular doesn't add
|
||||
it to `Array.prototype` because that could collide with other JavaScript frameworks.
|
||||
|
||||
For this reason the scope expression evaluator augments the built-in types to make them act like
|
||||
they have extra methods. The actual method for `$filter()` is `angular.Array.filter()`. You can
|
||||
call it from JavaScript.
|
||||
|
||||
Extensions: You can further extend the expression vocabulary by adding new methods to
|
||||
`angular.Array` or `angular.String`, etc.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="friends = [
|
||||
{name:'John', phone:'555-1212'},
|
||||
{name:'Mary', phone:'555-9876'},
|
||||
{name:'Mike', phone:'555-4321'},
|
||||
{name:'Adam', phone:'555-5678'},
|
||||
{name:'Julie', phone:'555-8765'}]"></div>
|
||||
Search: <input ng:model="searchText"/>
|
||||
<table class="example3">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng:repeat="friend in friends.$filter(searchText)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should filter the list', function() {
|
||||
var tr = using('table.example3').repeater('tr.ng-attr-widget');
|
||||
expect(tr.count()).toBe(5);
|
||||
input('searchText').enter('a');
|
||||
expect(tr.count()).toBe(2);
|
||||
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
## Filters
|
||||
|
||||
When presenting data to the user, you might need to convert the data from its raw format to a
|
||||
user-friendly format. For example, you might have a data object that needs to be formatted
|
||||
according to the locale before displaying it to the user. You can pass expressions through a chain
|
||||
of filters like this:
|
||||
|
||||
name | uppercase
|
||||
|
||||
The expression evaluator simply passes the value of name to angular.filter.uppercase.
|
||||
|
||||
Chain filters using this syntax:
|
||||
|
||||
value | filter1 | filter2
|
||||
|
||||
You can also pass colon-delimited arguments to filters, for example, to display the number 123 with
|
||||
2 decimal points:
|
||||
|
||||
123 | number:2
|
||||
|
||||
# The $
|
||||
|
||||
You might be wondering, what is the significance of the $ prefix? It is simply a prefix that
|
||||
angular uses, to differentiate its API names from others. If angular didn't use $, then evaluating
|
||||
`a.length()` would return undefined because neither a nor angular define such a property.
|
||||
|
||||
Consider that in a future version of angular we might choose to add a length method, in which case
|
||||
the behavior of the expression would change. Worse yet, you the developer could create a length
|
||||
property and then we would have a collision. This problem exists because angular augments existing
|
||||
objects with additional behavior. By prefixing its additions with $ we are reserving our namespace
|
||||
so that angular developers and developers who use angular can develop in harmony without collisions.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler.markup Understanding Angular Markup}
|
||||
* {@link dev_guide.templates.filters Understanding Angular Filters}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.compile Angular Compiler API}
|
||||
@@ -0,0 +1,610 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Forms
|
||||
@description
|
||||
|
||||
# Overview
|
||||
|
||||
Forms allow users to enter data into your application. Forms represent the bidirectional data
|
||||
bindings in Angular.
|
||||
|
||||
Forms consist of all of the following:
|
||||
|
||||
- the individual widgets with which users interact
|
||||
- the validation rules for widgets
|
||||
- the form, a collection of widgets that contains aggregated validation information
|
||||
|
||||
|
||||
# Form
|
||||
|
||||
A form groups a set of widgets together into a single logical data-set. A form is created using
|
||||
the {@link api/angular.widget.form <form>} element that calls the
|
||||
{@link api/angular.service.$formFactory $formFactory} service. The form is responsible for managing
|
||||
the widgets and for tracking validation information.
|
||||
|
||||
A form is:
|
||||
|
||||
- The collection which contains widgets or other forms.
|
||||
- Responsible for marshaling data from the model into a widget. This is
|
||||
triggered by {@link api/angular.scope.$watch $watch} of the model expression.
|
||||
- Responsible for marshaling data from the widget into the model. This is
|
||||
triggered by the widget emitting the `$viewChange` event.
|
||||
- Responsible for updating the validation state of the widget, when the widget emits
|
||||
`$valid` / `$invalid` event. The validation state is useful for controlling the validation
|
||||
errors shown to the user in it consist of:
|
||||
|
||||
- `$valid` / `$invalid`: Complementary set of booleans which show if a widget is valid / invalid.
|
||||
- `$error`: an object which has a property for each validation key emited by the widget.
|
||||
The value of the key is always true. If widget is valid, then the `$error`
|
||||
object has no properties. For example if the widget emits
|
||||
`$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
|
||||
updated to `$error.REQUIRED == true`.
|
||||
|
||||
- Responsible for aggregating widget validation information into the form.
|
||||
|
||||
- `$valid` / `$invalid`: Complementary set of booleans which show if all the child widgets
|
||||
(or forms) are valid or if any are invalid.
|
||||
- `$error`: an object which has a property for each validation key emited by the
|
||||
child widget. The value of the key is an array of widgets which fired the invalid
|
||||
event. If all child widgets are valid then, then the `$error` object has no
|
||||
properties. For example if a child widget emits
|
||||
`$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
|
||||
updated to `$error.REQUIRED == [ widgetWhichEmitedInvalid ]`.
|
||||
|
||||
|
||||
# Widgets
|
||||
|
||||
In Angular, a widget is the term used for the UI with which the user input. Examples of
|
||||
bult-in Angular widgets are {@link api/angular.widget.input input} and
|
||||
{@link api/angular.widget.select select}. Widgets provide the rendering and the user
|
||||
interaction logic. Widgets should be declared inside a form, if no form is provided an implicit
|
||||
form {@link api/angular.service.$formFactory $formFactory.rootForm} form is used.
|
||||
|
||||
Widgets are implemented as Angular controllers. A widget controller:
|
||||
|
||||
- implements methods:
|
||||
|
||||
- `$render` - Updates the DOM from the internal state as represented by `$viewValue`.
|
||||
- `$parseView` - Translate `$viewValue` to `$modelValue`. (`$modelValue` will be assigned to
|
||||
the model scope by the form)
|
||||
- `$parseModel` - Translate `$modelValue` to `$viewValue`. (`$viewValue` will be assigned to
|
||||
the DOM inside the `$render` method)
|
||||
|
||||
- responds to events:
|
||||
|
||||
- `$validate` - Emitted by the form when the form determines that the widget needs to validate
|
||||
itself. There may be more then one listener on the `$validate` event. The widget responds
|
||||
by emitting `$valid` / `$invalid` event of its own.
|
||||
|
||||
- emits events:
|
||||
|
||||
- `$viewChange` - Emitted when the user interacts with the widget and it is necessary to update
|
||||
the model.
|
||||
- `$valid` - Emitted when the widget determines that it is valid (usually as a response to
|
||||
`$validate` event or inside `$parseView()` or `$parseModel()` method).
|
||||
- `$invalid` - Emitted when the widget determines that it is invalid (usually as a response to
|
||||
`$validate` event or inside `$parseView()` or `$parseModel()` method).
|
||||
- `$destroy` - Emitted when the widget element is removed from the DOM.
|
||||
|
||||
|
||||
# CSS
|
||||
|
||||
Angular-defined widgets and forms set `ng-valid` and `ng-invalid` classes on themselves to allow
|
||||
the web-designer a way to style them. If you write your own widgets, then their `$render()`
|
||||
methods must set the appropriate CSS classes to allow styling.
|
||||
(See {@link dev_guide.templates.css-styling CSS})
|
||||
|
||||
|
||||
# Example
|
||||
|
||||
The following example demonstrates:
|
||||
|
||||
- How an error is displayed when a required field is empty.
|
||||
- Error highlighting.
|
||||
- How form submission is disabled when the form is invalid.
|
||||
- The internal state of the widget and form in the the 'Debug View' area.
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<style>
|
||||
.ng-invalid { border: solid 1px red; }
|
||||
.ng-form {display: block;}
|
||||
</style>
|
||||
<script>
|
||||
function UserFormCntl() {
|
||||
this.state = /^\w\w$/;
|
||||
this.zip = /^\d\d\d\d\d$/;
|
||||
this.master = {
|
||||
customer: 'John Smith',
|
||||
address:{
|
||||
line1: '123 Main St.',
|
||||
city:'Anytown',
|
||||
state:'AA',
|
||||
zip:'12345'
|
||||
}
|
||||
};
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
UserFormCntl.prototype = {
|
||||
cancel: function() {
|
||||
this.form = angular.copy(this.master);
|
||||
},
|
||||
|
||||
save: function() {
|
||||
this.master = this.form;
|
||||
this.cancel();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="UserFormCntl">
|
||||
|
||||
<form name="userForm">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" name="customer" ng:model="form.customer" required/>
|
||||
<span class="error" ng:show="userForm.customer.$error.REQUIRED">
|
||||
Customer name is required!</span>
|
||||
<br/><br/>
|
||||
|
||||
<ng:form name="addressForm">
|
||||
<label>Address:</label> <br/>
|
||||
<input type="text" name="line1" size="33" required
|
||||
ng:model="form.address.line1"/> <br/>
|
||||
<input type="text" name="city" size="12" required
|
||||
ng:model="form.address.city"/>,
|
||||
<input type="text" name="state" ng:pattern="state" size="2" required
|
||||
ng:model="form.address.state"/>
|
||||
<input type="text" name="zip" ng:pattern="zip" size="5" required
|
||||
ng:model="form.address.zip"/><br/><br/>
|
||||
|
||||
<span class="error" ng:show="addressForm.$invalid">
|
||||
Incomplete address:
|
||||
<span class="error" ng:show="addressForm.state.$error.REQUIRED">
|
||||
Missing state!</span>
|
||||
<span class="error" ng:show="addressForm.state.$error.PATTERN">
|
||||
Invalid state!</span>
|
||||
<span class="error" ng:show="addressForm.zip.$error.REQUIRED">
|
||||
Missing zip!</span>
|
||||
<span class="error" ng:show="addressForm.zip.$error.PATTERN">
|
||||
Invalid zip!</span>
|
||||
</span>
|
||||
</ng:form>
|
||||
|
||||
<button ng:click="cancel()"
|
||||
ng:disabled="{{master.$equals(form)}}">Cancel</button>
|
||||
<button ng:click="save()"
|
||||
ng:disabled="{{userForm.$invalid || master.$equals(form)}}">
|
||||
Save</button>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
Debug View:
|
||||
<pre>form={{form}}</pre>
|
||||
<pre>master={{master}}</pre>
|
||||
<pre>userForm={{userForm}}</pre>
|
||||
<pre>addressForm={{addressForm}}</pre>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should enable save button', function() {
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
input('form.customer').enter('');
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
input('form.customer').enter('change');
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Save)').click();
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
});
|
||||
it('should enable cancel button', function() {
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
input('form.customer').enter('change');
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Cancel)').click();
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
expect(element(':input[ng\\:model="form.customer"]').val()).toEqual('John Smith');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
# Life-cycle
|
||||
|
||||
- The `<form>` element triggers creation of a new form {@link dev_guide.scopes scope} using the
|
||||
{@link api/angular.service.$formFactory $formfactory}. The new form scope is added to the
|
||||
`<form>` element using the jQuery `.data()` method for later retrieval under the key `$form`.
|
||||
The form also sets up these listeners:
|
||||
|
||||
- `$destroy` - This event is emitted by nested widget when it is removed from the view. It gives
|
||||
the form a chance to clean up any validation references to the destroyed widget.
|
||||
- `$valid` / `$invalid` - This event is emitted by the widget on validation state change.
|
||||
|
||||
- `<input>` element triggers the creation of the widget using the
|
||||
{@link api/angular.service.$formFactory $formfactory.$createWidget()} method. The `$createWidget()`
|
||||
creates new widget instance by calling the current scope {@link api/angular.scope.$new .$new()} and
|
||||
registers these listeners:
|
||||
|
||||
- `$watch` on the model scope.
|
||||
- `$viewChange` event on the widget scope.
|
||||
- `$validate` event on the widget scope.
|
||||
- Element `change` event when the user enters data.
|
||||
|
||||
<img class="center" src="img/form_data_flow.png" border="1" />
|
||||
|
||||
|
||||
- When the user interacts with the widget:
|
||||
|
||||
1. The DOM element fires the `change` event which the widget intercepts. Widget then emits
|
||||
a `$viewChange` event which includes the new user-entered value. (Remember that the DOM events
|
||||
are outside of the Angular environment so the widget must emit its event within the
|
||||
{@link api/angular.scope.$apply $apply} method).
|
||||
2. The form's `$viewChange` listener copies the user-entered value to the widget's `$viewValue`
|
||||
property. Since the `$viewValue` is the raw value as entered by user, it may need to be
|
||||
translated to a different format/type (for example, translating a string to a number).
|
||||
If you need your widget to translate between the internal `$viewValue` and the external
|
||||
`$modelValue` state, you must declare a `$parseView()` method. The `$parseView()` method
|
||||
will copy `$viewValue` to `$modelValue` and perform any necessary translations.
|
||||
3. The `$modelValue` is written into the application model.
|
||||
4. The form then emits a `$validate` event, giving the widget's validators chance to validate the
|
||||
input. There can be any number of validators registered. Each validator may in turn
|
||||
emit a `$valid` / `$invalid` event with the validator's validation key. For example `REQUIRED`.
|
||||
5. Form listens to `$valid`/`$invalid` events and updates both the form as well as the widget
|
||||
scope with the validation state. The validation updates the `$valid` and `$invalid`, property
|
||||
as well as `$error` object. The widget's `$error` object is updated with the validation key
|
||||
such that `$error.REQUIRED == true` when the validation emits `$invalid` with `REQUIRED`
|
||||
validation key. Similarly the form's `$error` object gets updated, but instead of boolean
|
||||
`true` it contains an array of invalid widgets (widgets which fired `$invalid` event with
|
||||
`REQUIRED` validation key).
|
||||
|
||||
- When the model is updated:
|
||||
|
||||
1. The model `$watch` listener assigns the model value to `$modelValue` on the widget.
|
||||
2. The form then calls `$parseModel` method on widget if present. The method converts the
|
||||
value to renderable format and assigns it to `$viewValue` (for example converting number to a
|
||||
string.)
|
||||
3. The form then emits a `$validate` which behaves as described above.
|
||||
4. The form then calls `$render` method on the widget to update the DOM structure from the
|
||||
`$viewValue`.
|
||||
|
||||
|
||||
|
||||
# Writing Your Own Widget
|
||||
|
||||
This example shows how to implement a custom HTML editor widget in Angular.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function EditorCntl() {
|
||||
this.htmlContent = '<b>Hello</b> <i>World</i>!';
|
||||
}
|
||||
|
||||
function HTMLEditorWidget(element) {
|
||||
var self = this;
|
||||
var htmlFilter = angular.filter('html');
|
||||
|
||||
this.$parseModel = function() {
|
||||
// need to protect for script injection
|
||||
try {
|
||||
this.$viewValue = htmlFilter(
|
||||
this.$modelValue || '').get();
|
||||
if (this.$error.HTML) {
|
||||
// we were invalid, but now we are OK.
|
||||
this.$emit('$valid', 'HTML');
|
||||
}
|
||||
} catch (e) {
|
||||
// if HTML not parsable invalidate form.
|
||||
this.$emit('$invalid', 'HTML');
|
||||
}
|
||||
}
|
||||
|
||||
this.$render = function() {
|
||||
element.html(this.$viewValue);
|
||||
}
|
||||
|
||||
element.bind('keyup', function() {
|
||||
self.$apply(function() {
|
||||
self.$emit('$viewChange', element.html());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
angular.directive('ng:html-editor-model', function() {
|
||||
function linkFn($formFactory, element) {
|
||||
var exp = element.attr('ng:html-editor-model'),
|
||||
form = $formFactory.forElement(element),
|
||||
widget;
|
||||
element.attr('contentEditable', true);
|
||||
widget = form.$createWidget({
|
||||
scope: this,
|
||||
model: exp,
|
||||
controller: HTMLEditorWidget,
|
||||
controllerArgs: [element]});
|
||||
// if the element is destroyed, then we need to
|
||||
// notify the form.
|
||||
element.bind('$destroy', function() {
|
||||
widget.$destroy();
|
||||
});
|
||||
}
|
||||
linkFn.$inject = ['$formFactory'];
|
||||
return linkFn;
|
||||
});
|
||||
</script>
|
||||
<form name='editorForm' ng:controller="EditorCntl">
|
||||
<div ng:html-editor-model="htmlContent"></div>
|
||||
<hr/>
|
||||
HTML: <br/>
|
||||
<textarea ng:model="htmlContent" cols="80"></textarea>
|
||||
<hr/>
|
||||
<pre>editorForm = {{editorForm}}</pre>
|
||||
</form>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should enter invalid HTML', function() {
|
||||
expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-valid/);
|
||||
input('htmlContent').enter('<');
|
||||
expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-invalid/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
|
||||
# HTML Inputs
|
||||
|
||||
The most common widgets you will use will be in the form of the
|
||||
standard HTML set. These widgets are bound using the `name` attribute
|
||||
to an expression. In addition, they can have `required` attribute to further control their
|
||||
validation.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function Ctrl() {
|
||||
this.input1 = '';
|
||||
this.input2 = '';
|
||||
this.input3 = 'A';
|
||||
this.input4 = false;
|
||||
this.input5 = 'c';
|
||||
this.input6 = [];
|
||||
}
|
||||
</script>
|
||||
<table style="font-size:.9em;" ng:controller="Ctrl">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Format</th>
|
||||
<th>HTML</th>
|
||||
<th>UI</th>
|
||||
<th ng:non-bindable>{{input#}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>text</th>
|
||||
<td>String</td>
|
||||
<td><tt><input type="text" ng:model="input1"></tt></td>
|
||||
<td><input type="text" ng:model="input1" size="4"></td>
|
||||
<td><tt>{{input1|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>textarea</th>
|
||||
<td>String</td>
|
||||
<td><tt><textarea ng:model="input2"></textarea></tt></td>
|
||||
<td><textarea ng:model="input2" cols='6'></textarea></td>
|
||||
<td><tt>{{input2|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>radio</th>
|
||||
<td>String</td>
|
||||
<td><tt>
|
||||
<input type="radio" ng:model="input3" value="A"><br>
|
||||
<input type="radio" ng:model="input3" value="B">
|
||||
</tt></td>
|
||||
<td>
|
||||
<input type="radio" ng:model="input3" value="A">
|
||||
<input type="radio" ng:model="input3" value="B">
|
||||
</td>
|
||||
<td><tt>{{input3|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>checkbox</th>
|
||||
<td>Boolean</td>
|
||||
<td><tt><input type="checkbox" ng:model="input4"></tt></td>
|
||||
<td><input type="checkbox" ng:model="input4"></td>
|
||||
<td><tt>{{input4|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>pulldown</th>
|
||||
<td>String</td>
|
||||
<td><tt>
|
||||
<select ng:model="input5"><br>
|
||||
<option value="c">C</option><br>
|
||||
<option value="d">D</option><br>
|
||||
</select><br>
|
||||
</tt></td>
|
||||
<td>
|
||||
<select ng:model="input5">
|
||||
<option value="c">C</option>
|
||||
<option value="d">D</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><tt>{{input5|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>multiselect</th>
|
||||
<td>Array</td>
|
||||
<td><tt>
|
||||
<select ng:model="input6" multiple size="4"><br>
|
||||
<option value="e">E</option><br>
|
||||
<option value="f">F</option><br>
|
||||
</select><br>
|
||||
</tt></td>
|
||||
<td>
|
||||
<select ng:model="input6" multiple size="4">
|
||||
<option value="e">E</option>
|
||||
<option value="f">F</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><tt>{{input6|json}}</tt></td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
|
||||
it('should exercise text', function() {
|
||||
input('input1').enter('Carlos');
|
||||
expect(binding('input1')).toEqual('"Carlos"');
|
||||
});
|
||||
it('should exercise textarea', function() {
|
||||
input('input2').enter('Carlos');
|
||||
expect(binding('input2')).toEqual('"Carlos"');
|
||||
});
|
||||
it('should exercise radio', function() {
|
||||
expect(binding('input3')).toEqual('"A"');
|
||||
input('input3').select('B');
|
||||
expect(binding('input3')).toEqual('"B"');
|
||||
input('input3').select('A');
|
||||
expect(binding('input3')).toEqual('"A"');
|
||||
});
|
||||
it('should exercise checkbox', function() {
|
||||
expect(binding('input4')).toEqual('false');
|
||||
input('input4').check();
|
||||
expect(binding('input4')).toEqual('true');
|
||||
});
|
||||
it('should exercise pulldown', function() {
|
||||
expect(binding('input5')).toEqual('"c"');
|
||||
select('input5').option('d');
|
||||
expect(binding('input5')).toEqual('"d"');
|
||||
});
|
||||
it('should exercise multiselect', function() {
|
||||
expect(binding('input6')).toEqual('[]');
|
||||
select('input6').options('e');
|
||||
expect(binding('input6')).toEqual('["e"]');
|
||||
select('input6').options('e', 'f');
|
||||
expect(binding('input6')).toEqual('["e","f"]');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
#Testing
|
||||
|
||||
When unit-testing a controller it may be desirable to have a reference to form and to simulate
|
||||
different form validation states.
|
||||
|
||||
This example demonstrates a login form, where the login button is enabled only when the form is
|
||||
properly filled out.
|
||||
<pre>
|
||||
<div ng:controller="LoginController">
|
||||
<form name="loginForm">
|
||||
<input type="text" ng:model="username" required/>
|
||||
<input type="password" ng:model="password" required/>
|
||||
<button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login>
|
||||
</form>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
In the unit tests we do not have access to the DOM, and therefore the `loginForm` reference does
|
||||
not get set on the controller. This example shows how it can be unit-tested, by creating a mock
|
||||
form.
|
||||
<pre>
|
||||
function LoginController() {
|
||||
this.disableLogin = function() {
|
||||
return this.loginForm.$invalid;
|
||||
};
|
||||
}
|
||||
|
||||
describe('LoginController', function() {
|
||||
it('should disable login button when form is invalid', function() {
|
||||
var scope = angular.scope();
|
||||
var loginController = scope.$new(LoginController);
|
||||
|
||||
// In production the 'loginForm' form instance gets set from the view,
|
||||
// but in unit-test we have to set it manually.
|
||||
loginController.loginForm = scope.$service('$formFactory')();
|
||||
|
||||
expect(loginController.disableLogin()).toBe(false);
|
||||
|
||||
// Now simulate an invalid form
|
||||
loginController.loginForm.$emit('$invalid', 'MyReason');
|
||||
expect(loginController.disableLogin()).toBe(true);
|
||||
|
||||
// Now simulate a valid form
|
||||
loginController.loginForm.$emit('$valid', 'MyReason');
|
||||
expect(loginController.disableLogin()).toBe(false);
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
## Custom widgets
|
||||
|
||||
This example demonstrates a login form, where the password has custom validation rules.
|
||||
<pre>
|
||||
<div ng:controller="LoginController">
|
||||
<form name="loginForm">
|
||||
<input type="text" ng:model="username" required/>
|
||||
<input type="@StrongPassword" ng:model="password" required/>
|
||||
<button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login>
|
||||
</form>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
In the unit tests we do not have access to the DOM, and therefore the `loginForm` and custom
|
||||
input type reference does not get set on the controller. This example shows how it can be
|
||||
unit-tested, by creating a mock form and a mock custom input type.
|
||||
<pre>
|
||||
function LoginController(){
|
||||
this.disableLogin = function() {
|
||||
return this.loginForm.$invalid;
|
||||
};
|
||||
|
||||
this.StrongPassword = function(element) {
|
||||
var widget = this;
|
||||
element.attr('type', 'password'); // act as password.
|
||||
this.$on('$validate', function(){
|
||||
widget.$emit(widget.$viewValue.length > 5 ? '$valid' : '$invalid', 'PASSWORD');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
describe('LoginController', function() {
|
||||
it('should disable login button when form is invalid', function() {
|
||||
var scope = angular.scope();
|
||||
var loginController = scope.$new(LoginController);
|
||||
var input = angular.element('<input>');
|
||||
|
||||
// In production the 'loginForm' form instance gets set from the view,
|
||||
// but in unit-test we have to set it manually.
|
||||
loginController.loginForm = scope.$service('$formFactory')();
|
||||
|
||||
// now instantiate a custom input type
|
||||
loginController.loginForm.$createWidget({
|
||||
scope: loginController,
|
||||
model: 'password',
|
||||
alias: 'password',
|
||||
controller: loginController.StrongPassword,
|
||||
controllerArgs: [input]
|
||||
});
|
||||
|
||||
// Verify that the custom password input type sets the input type to password
|
||||
expect(input.attr('type')).toEqual('password');
|
||||
|
||||
expect(loginController.disableLogin()).toBe(false);
|
||||
|
||||
// Now simulate an invalid form
|
||||
loginController.loginForm.password.$emit('$invalid', 'PASSWORD');
|
||||
expect(loginController.disableLogin()).toBe(true);
|
||||
|
||||
// Now simulate a valid form
|
||||
loginController.loginForm.password.$emit('$valid', 'PASSWORD');
|
||||
expect(loginController.disableLogin()).toBe(false);
|
||||
|
||||
// Changing model state, should also influence the form validity
|
||||
loginController.password = 'abc'; // too short so it should be invalid
|
||||
scope.$digest();
|
||||
expect(loginController.loginForm.password.$invalid).toBe(true);
|
||||
|
||||
// Changeing model state, should also influence the form validity
|
||||
loginController.password = 'abcdef'; // should be valid
|
||||
scope.$digest();
|
||||
expect(loginController.loginForm.password.$valid).toBe(true);
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: i18n and l10n
|
||||
@description
|
||||
|
||||
# I18n and L10n in AngularJS
|
||||
|
||||
**What is i18n and l10n?**
|
||||
|
||||
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?**
|
||||
|
||||
Currently, Angular supports i18n/l10n for {@link
|
||||
http://docs.angularjs.org/#!/api/angular.filter.date datetime}, {@link
|
||||
http://docs.angularjs.org/#!/api/angular.filter.number number} and {@link
|
||||
http://docs.angularjs.org/#!/api/angular.filter.currency currency} filters.
|
||||
|
||||
Additionally, Angular supports localizable pluralization support provided by the {@link
|
||||
api/angular.widget.ng:pluralize ng:pluralize widget}.
|
||||
|
||||
All localizable Angular components depend on locale-specific rule sets managed by the {@link
|
||||
api/angular.service.$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 {@link
|
||||
https://github.com/angular/angular.js/tree/master/i18n/e2e Github} or in the i18n/e2e folder of
|
||||
Angular development package.
|
||||
|
||||
**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 {@link
|
||||
http://userguide.icu-project.org/locale ICU } website for more information about using locale IDs.
|
||||
|
||||
**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 {@link
|
||||
https://github.com/angular/angular.js/tree/master/i18n/locale here}
|
||||
# Providing locale rules to Angular
|
||||
|
||||
There are two approaches to providing locale rules to Angular:
|
||||
|
||||
**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.
|
||||
|
||||
For example on *nix, to create a an angular.js file that contains localization rules for german
|
||||
locale, you can do the following:
|
||||
|
||||
`cat angular.js i18n/angular-locale_de-ge.js > angular_de-ge.js`
|
||||
|
||||
When the application containing `angular_de-ge.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**
|
||||
|
||||
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-ge.html which will look something like this:
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<head>
|
||||
….
|
||||
<script src="angular.js" ng:autobind></script>
|
||||
<script src="i18n/angular-locale_de-ge.js"></script>
|
||||
….
|
||||
</head>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
**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
|
||||
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.
|
||||
|
||||
|
||||
# "Gotchas"
|
||||
|
||||
**Currency symbol "gotcha"**
|
||||
|
||||
Angular's {@link http://docs.angularjs.org/#!/api/angular.filter.currency currency filter} allows
|
||||
you to use the default currency symbol from the {@link api/angular.service.$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.
|
||||
|
||||
For example, if you want to display account balance of 1000 dollars with the following binding
|
||||
containing currency filter: `{{ 1000 | currency }}`, and your app is currently in en-US locale.
|
||||
'$1000.00' will be shown. However, if someone in a different local (say, Japan) views your app, her
|
||||
browser will specify the locale as ja, and the balance of '¥1000.00' will be shown instead. This
|
||||
will really upset your client.
|
||||
|
||||
In this case, you need to override the default currency symbol by providing the {@link
|
||||
http://docs.angularjs.org/#!/api/angular.filter.currency currency filter} 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.
|
||||
|
||||
**Translation length "gotcha"**
|
||||
|
||||
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.
|
||||
|
||||
|
||||
**Timezones**
|
||||
|
||||
Keep in mind that Angular datetime filter uses the time zone settings of the browser. So 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
|
||||
displaying the date with a timezone specified by the developer.
|
||||
@@ -0,0 +1,42 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Introduction
|
||||
@description
|
||||
|
||||
Angular is pure client-side technology, written entirely in JavaScript. It works with the
|
||||
long-established technologies of the web (HTML, CSS, and JavaScript) to make the development of web
|
||||
apps easier and faster than ever before.
|
||||
|
||||
One important way that angular simplifies web development is by increasing the level of abstraction
|
||||
between the developer and most low-level web app development tasks. Angular automatically takes
|
||||
care of many of these tasks, including:
|
||||
|
||||
* DOM Manipulation
|
||||
* Setting Up Listeners and Notifiers
|
||||
* Input Validation
|
||||
|
||||
Because angular handles much of the work involved in these tasks, developers can concentrate more
|
||||
on application logic and less on repetitive, error-prone, lower-level coding.
|
||||
|
||||
At the same time that angular simplifies the development of web apps, it brings relatively
|
||||
sophisticated techniques to the client-side, including:
|
||||
|
||||
* Separation of data, application logic, and presentation components
|
||||
* Data Binding between data and presentation components
|
||||
* Services (common web app operations, implemented as substitutable objects)
|
||||
* Dependency Injection (used primarily for wiring together services)
|
||||
* An extensible HTML compiler (written entirely in JavaScript)
|
||||
* Ease of Testing
|
||||
|
||||
These techniques have been for the most part absent from the client-side for far too long.
|
||||
|
||||
## Single-page / Round-trip Applications
|
||||
|
||||
You can use angular to develop both single-page and round-trip apps, but angular is designed
|
||||
primarily for developing single-page apps. Angular supports browser history, forward and back
|
||||
buttons, and bookmarking in single-page apps.
|
||||
|
||||
You normally wouldn't want to load angular with every page change, as would be the case with using
|
||||
angular in a round-trip app. However, it would make sense to do so if you were adding a subset of
|
||||
angular's features (for example, templates to leverage angular's data-binding feature) to an
|
||||
existing round-trip app. You might follow this course of action if you were migrating an older app
|
||||
to a single-page angular app.
|
||||
@@ -0,0 +1,24 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About MVC in Angular
|
||||
@description
|
||||
|
||||
While Model-View-Controller (MVC) has acquired different shades of meaning over the years since it
|
||||
first appeared, angular incorporates the basic principles behind the original {@link
|
||||
http://en.wikipedia.org/wiki/Model–view–controller MVC} software design pattern into its way of
|
||||
building client-side web applications.
|
||||
|
||||
The MVC pattern greatly summarized:
|
||||
|
||||
* Separate applications into distinct presentation, data, and logic components
|
||||
* Encourage loose coupling between these components
|
||||
|
||||
Along with {@link dev_guide.services services} and {@link dev_guide.di dependency injection}, MVC
|
||||
makes angular applications better structured, easier to maintain and more testable.
|
||||
|
||||
The following topics explain how angular incorporates the MVC pattern into the angular way of
|
||||
developing web applications:
|
||||
|
||||
* {@link dev_guide.mvc.understanding_model Understanding the Model Component}
|
||||
* {@link dev_guide.mvc.understanding_controller Understanding the Controller Component}
|
||||
* {@link dev_guide.mvc.understanding_view Understanding the View Component}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About MVC in Angular: Understanding the Controller Component
|
||||
@description
|
||||
|
||||
In angular, a controller is a JavaScript function(type/class) that is used to augment instances of
|
||||
angular {@link dev_guide.scopes Scope}, excluding the root scope. When you or angular create a new
|
||||
child scope object via the {@link api/angular.scope.$new scope.$new} API , there is an
|
||||
option to pass in a controller as a method argument. This will tell angular to associate the
|
||||
controller with the new scope and to augment its behavior.
|
||||
|
||||
Use controllers to:
|
||||
|
||||
- Set up the initial state of a scope object.
|
||||
- Add behavior to the scope object.
|
||||
|
||||
# Setting up the initial state of a scope object
|
||||
|
||||
Typically, when you create an application you need to set up an initial state for an angular scope.
|
||||
|
||||
Angular applies (in the sense of JavaScript's `Function#apply`) the controller constructor function
|
||||
to a new angular scope object, which sets up an initial scope state. This means that angular never
|
||||
creates instances of the controller type (by invoking the `new` operator on the controller
|
||||
constructor). Constructors are always applied to an existing scope object.
|
||||
|
||||
You set up the initial state of a scope by creating model properties. For example:
|
||||
|
||||
function GreetingCtrl() {
|
||||
this.greeting = 'Hola!';
|
||||
}
|
||||
|
||||
The `GreetingCtrl` controller creates a `greeting` model which can be referred to in a template.
|
||||
|
||||
When a controller function is applied to an angular scope object, the `this` of the controller
|
||||
function becomes the scope of the angular scope object, so any assignment to `this` within the
|
||||
controller function happens on the angular scope object.
|
||||
|
||||
# Adding Behavior to a Scope Object
|
||||
|
||||
Behavior on an angular scope object is in the form of scope method properties available to the
|
||||
template/view. This behavior interacts with and modifies the application model.
|
||||
|
||||
As discussed in the {@link dev_guide.mvc.understanding_model Model} section of this guide, any
|
||||
objects (or primitives) assigned to the scope become model properties. Any functions assigned to
|
||||
the scope, along with any prototype methods of the controller type, become functions available in
|
||||
the template/view, and can be invoked via angular expressions and `ng:` event handlers (e.g. {@link
|
||||
api/angular.directive.ng:click ng:click}). These controller methods are always evaluated within the
|
||||
context of the angular scope object that the controller function was applied to (which means that
|
||||
the `this` keyword of any controller method is always bound to the scope that the controller
|
||||
augments). This is how the second task of adding behavior to the scope is accomplished.
|
||||
|
||||
|
||||
# Using Controllers Correctly
|
||||
|
||||
In general, a controller shouldn't try to do too much. It should contain only the business logic
|
||||
needed for a single view.
|
||||
|
||||
The most common way to keep controllers slim is by encapsulating work that doesn't belong to
|
||||
controllers into services and then using these services in controllers via dependency injection.
|
||||
This is discussed in the {@link dev_guide.di Dependency Injection} {@link dev_guide.services
|
||||
Services} sections of this guide.
|
||||
|
||||
Do not use controllers for:
|
||||
|
||||
- Any kind of DOM manipulation — Controllers should contain only business logic. DOM
|
||||
manipulation—the presentation logic of an application—is well known for being hard to test.
|
||||
Putting any presentation logic into controllers significantly affects testability of the business
|
||||
logic. Angular offers {@link dev_guide.templates.databinding} for automatic DOM manipulation. If
|
||||
you have to perform your own manual DOM manipulation, encapsulate the presentation logic in {@link
|
||||
dev_guide.compiler.widgets widgets} and {@link dev_guide.compiler.directives directives}.
|
||||
- Input formatting — Use {@link dev_guide.forms angular form widgets} instead.
|
||||
- Output filtering — Use {@link dev_guide.templates.filters angular filters} instead.
|
||||
- Run stateless or stateful code shared across controllers — Use {@link dev_guide.services angular
|
||||
services} instead.
|
||||
- Instantiate or manage the life-cycle of other components (for example, to create service
|
||||
instances).
|
||||
|
||||
|
||||
# Associating Controllers with Angular Scope Objects
|
||||
|
||||
You can associate controllers with scope objects explicitly via the {@link api/angular.scope.$new
|
||||
scope.$new} api or implicitly via the {@link api/angular.directive.ng:controller ng:controller
|
||||
directive} or {@link api/angular.service.$route $route service}.
|
||||
|
||||
|
||||
## Controller Constructor and Methods Example
|
||||
|
||||
To illustrate how the controller component works in angular, let's create a little app with the
|
||||
following components:
|
||||
|
||||
- A {@link dev_guide.templates template} with two buttons and a simple message
|
||||
- A model consisting of a string named `spice`
|
||||
- A controller with two functions that set the value of `spice`
|
||||
|
||||
The message in our template contains a binding to the `spice` model, which by default is set to the
|
||||
string "very". Depending on which button is clicked, the `spice` model is set to `chili` or
|
||||
`jalapeño`, and the message is automatically updated by data-binding.
|
||||
|
||||
|
||||
## A Spicy Controller Example
|
||||
|
||||
<pre>
|
||||
<body ng:controller="SpicyCtrl">
|
||||
<button ng:click="chiliSpicy()">Chili</button>
|
||||
<button ng:click="jalapenoSpicy()">Jalapeño</button>
|
||||
<p>The food is {{spice}} spicy!</p>
|
||||
</body>
|
||||
|
||||
function SpicyCtrl() {
|
||||
this.spice = 'very';
|
||||
this.chiliSpicy = function() {
|
||||
this.spice = 'chili';
|
||||
}
|
||||
}
|
||||
|
||||
SpicyCtrl.prototype.jalapenoSpicy = function() {
|
||||
this.spice = 'jalapeño';
|
||||
}
|
||||
</pre>
|
||||
|
||||
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".
|
||||
- The JavaScript keyword `this` in the `SpicyCtrl` function is bound to the scope that the
|
||||
controller augments.
|
||||
- Assigning a property to `this` creates or updates the model.
|
||||
- Controller methods can be created through direct assignment to scope (the `chiliSpicy` method) or
|
||||
as prototype methods of the controller constructor function(the `jalapenoSpicy` method)
|
||||
- Both controller methods are available in the template (for the `body` element and and its
|
||||
children).
|
||||
|
||||
Controller methods can also take arguments, as demonstrated in the following variation of the
|
||||
previous example.
|
||||
|
||||
## Controller Method Arguments Example
|
||||
|
||||
<pre>
|
||||
<body ng:controller="SpicyCtrl">
|
||||
<input ng:model="customSpice" value="wasabi">
|
||||
<button ng:click="spicy('chili')">Chili</button>
|
||||
<button ng:click="spicy(customSpice)">Custom spice</button>
|
||||
<p>The food is {{spice}} spicy!</p>
|
||||
</body>
|
||||
|
||||
function SpicyCtrl() {
|
||||
this.spice = 'very';
|
||||
this.spicy = function(spice) {
|
||||
this.spice = spice;
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
Notice that the `SpicyCtrl` controller now defines just one method called `spicy`, which takes one
|
||||
argument called `spice`. The template then refers to this controller method and passes in a string
|
||||
constant `'chili'` in the binding for the first button and a model property `spice` (bound to an
|
||||
input box) in the second button.
|
||||
|
||||
|
||||
## Controller Inheritance Example
|
||||
|
||||
Controller inheritance in angular is based on {@link api/angular.scope Scope} inheritance. Let's
|
||||
have a look at an example:
|
||||
|
||||
<pre>
|
||||
<body ng:controller="MainCtrl">
|
||||
<p>Good {{timeOfDay}}, {{name}}!</p>
|
||||
<div ng:controller="ChildCtrl">
|
||||
<p>Good {{timeOfDay}}, {{name}}!</p>
|
||||
<p ng:controller="BabyCtrl">Good {{timeOfDay}}, {{name}}!</p>
|
||||
</body>
|
||||
|
||||
function MainCtrl() {
|
||||
this.timeOfDay = 'morning';
|
||||
this.name = 'Nikki';
|
||||
}
|
||||
|
||||
function ChildCtrl() {
|
||||
this.name = 'Mattie';
|
||||
}
|
||||
|
||||
function BabyCtrl() {
|
||||
this.timeOfDay = 'evening';
|
||||
this.name = 'Gingerbreak Baby';
|
||||
}
|
||||
</pre>
|
||||
|
||||
Notice how we nested three `ng:controller` directives in our template. This template construct will
|
||||
result in 4 scopes being created for our view:
|
||||
|
||||
- The root scope
|
||||
- The `MainCtrl` scope, which contains `timeOfDay` and `name` models
|
||||
- The `ChildCtrl` scope, which shadows the `name` model from the previous scope and inherits the
|
||||
`timeOfDay` model
|
||||
- The `BabyCtrl` scope, which shadows both the `timeOfDay` model defined in `MainCtrl` and `name`
|
||||
model defined in the ChildCtrl
|
||||
|
||||
Inheritance works between controllers in the same way as it does with models. So in our previous
|
||||
examples, all of the models could be replaced with controller methods that return string values.
|
||||
|
||||
Note: Standard prototypical inheritance between two controllers doesn't work as one might expect,
|
||||
because as we mentioned earlier, controllers are not instantiated directly by angular, but rather
|
||||
are applied to the scope object.
|
||||
|
||||
|
||||
## Testing Controllers
|
||||
|
||||
The way to test a controller depends upon how complicated the controller is.
|
||||
|
||||
- If your controller doesn't use DI or scope methods — create the controller with the `new`
|
||||
operator and test away. For example:
|
||||
|
||||
Controller Function:
|
||||
<pre>
|
||||
function myController() {
|
||||
this.spices = [{"name":"pasilla", "spiciness":"mild"},
|
||||
{"name":"jalapeno", "spiceiness":"hot hot hot!"},
|
||||
{"name":"habanero", "spiceness":"LAVA HOT!!"}];
|
||||
|
||||
this.spice = "habanero";
|
||||
}
|
||||
</pre>
|
||||
|
||||
Controller Test:
|
||||
<pre>
|
||||
describe('myController function', function() {
|
||||
|
||||
describe('myController', function() {
|
||||
var ctrl;
|
||||
|
||||
beforeEach(function() {
|
||||
ctrl = new myController();
|
||||
});
|
||||
|
||||
it('should create "spices" model with 3 spices', function() {
|
||||
expect(ctrl.spices.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should set the default value of spice', function() {
|
||||
expect(ctrl.spice).toBe('habanero');
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
- If your controller does use DI or scope methods — create a root scope, then create the controller
|
||||
in the root scope with `scope.$new(MyController)`. Test the controller using `$eval`, if necessary.
|
||||
- If you need to test a nested controller that depends on its parent's state — create a root scope,
|
||||
create a parent scope, create a child scope, and test the controller using $eval if necessary.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.mvc About MVC in Angular}
|
||||
* {@link dev_guide.mvc.understanding_model Understanding the Model Component}
|
||||
* {@link dev_guide.mvc.understanding_view Understanding the View Component}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About MVC in Angular: Understanding the Model Component
|
||||
@description
|
||||
|
||||
Depending on the context of the discussion in angular documentation, the term _model_ can refer to
|
||||
either a single object representing one entity (for example, a model called "phones" with its value
|
||||
being an array of phones) or the entire data model for the application (all entities).
|
||||
|
||||
In angular, a model is any data that is reachable as a property of an angular {@link
|
||||
dev_guide.scopes Scope} object. The name of the property is the model identifier and the value is
|
||||
any JavaScript object (including arrays and primitives).
|
||||
|
||||
The only requirement for a JavaScript object to be a model in angular is that the object must be
|
||||
referenced by an angular scope as a property of that scope object. This property reference can be
|
||||
created explicitly or implicitly.
|
||||
|
||||
You can create models by explicitly creating scope properties referencing JavaScript objects in the
|
||||
following ways:
|
||||
|
||||
* Make a direct property assignment to the scope object in JavaScript code; this most commonly
|
||||
occurs in controllers:
|
||||
|
||||
function MyCtrl() {
|
||||
// create property 'foo' on the MyCtrl's scope
|
||||
// and assign it an initial value 'bar'
|
||||
this.foo = 'bar';
|
||||
}
|
||||
|
||||
* Use an {@link dev_guide.expressions angular expression} with an assignment operator in templates:
|
||||
|
||||
<button ng:click="{{foos='ball'}}">Click me</button>
|
||||
|
||||
* Use {@link api/angular.directive.ng:init ng:init directive} in templates (for toy/example apps
|
||||
only, not recommended for real applications):
|
||||
|
||||
<body ng:init=" foo = 'bar' ">
|
||||
|
||||
Angular creates models implicitly (by creating a scope property and assigning it a suitable value)
|
||||
when processing the following template constructs:
|
||||
|
||||
* Form input, select, textarea and other form elements:
|
||||
|
||||
<input ng:model="query" value="fluffy cloud">
|
||||
|
||||
The code above creates a model called "query" on the current scope with the value set to "fluffy
|
||||
cloud".
|
||||
|
||||
* An iterator declaration in {@link api/angular.widget.@ng:repeat ng:repeater}:
|
||||
|
||||
<p ng:repeat="phone in phones"></p>
|
||||
|
||||
The code above creates one child scope for each item in the "phones" array and creates a "phone"
|
||||
object (model) on each of these scopes with its value set to the value of "phone" in the array.
|
||||
|
||||
In angular, a JavaScript object stops being a model when:
|
||||
|
||||
* No angular scope contains a property that references the object.
|
||||
|
||||
* All angular scopes that contain a property referencing the object become stale and eligible for
|
||||
garbage collection.
|
||||
|
||||
The following illustration shows a simple data model created implicitly from a simple template:
|
||||
|
||||
<img src="img/guide/about_model_final.png">
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.mvc About MVC in Angular}
|
||||
* {@link dev_guide.mvc.understanding_controller Understanding the Controller Component}
|
||||
* {@link dev_guide.mvc.understanding_view Understanding the View Component}
|
||||
@@ -0,0 +1,22 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About MVC in Angular: Understanding the View Component
|
||||
@description
|
||||
|
||||
In angular, the view is the DOM loaded and rendered in the browser, after angular has transformed
|
||||
the DOM based on information in the template, controller and model.
|
||||
|
||||
<img src="img/guide/about_view_final.png">
|
||||
|
||||
In the angular implementation of MVC, the view has knowledge of both the model and the controller.
|
||||
The view knows about the model where two-way data-binding occurs. The view has knowledge of the
|
||||
controller through angular directives, such as {@link api/angular.directive.ng:controller
|
||||
ng:controller} and {@link api/angular.widget.ng:view ng:view}, and through bindings of this form:
|
||||
`{{someControllerFunction()}}`. In these ways, the view can call functions in an associated
|
||||
controller function.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.mvc About MVC in Angular}
|
||||
* {@link dev_guide.mvc.understanding_model Understanding the Model Component}
|
||||
* {@link dev_guide.mvc.understanding_controller Understanding the Controller Component}
|
||||
@@ -0,0 +1,242 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Overview
|
||||
@description
|
||||
|
||||
|
||||
# What Is Angular?
|
||||
|
||||
The short answer: angular is a new, powerful, client-side technology that makes it much easier for
|
||||
you to create dynamic web sites and complex web apps, all without leaving the comfort of your HTML
|
||||
/ JavaScript home.
|
||||
|
||||
The long answer: it depends on where you're coming from...
|
||||
|
||||
* If you're a web designer, you might perceive angular to be a sweet {@link dev_guide.templates
|
||||
templating} system, that doesn't get in your way and provides you with lots of nice built-ins that
|
||||
make it easier to do what you want to do.
|
||||
|
||||
* If you're a web developer, you might be thrilled that angular functions as an excellent web
|
||||
framework, one that assists you all the way through the development cycle.
|
||||
|
||||
* If you want to go deeper, you can immerse yourself in angular's extensible HTML {@link
|
||||
dev_guide.compiler compiler} that runs in your browser. The angular compiler teaches your browser
|
||||
new tricks.
|
||||
|
||||
Angular is not just a templating system, but you can create fantastic templates with it. Angular is
|
||||
not just a web framework, but it features a very nice framework. Angular is not just an extensible
|
||||
HTML compiler, but the compiler is at the core of Angular. Angular includes all of these
|
||||
components, along with others. Angular is far greater than the sum of its parts. It is a new,
|
||||
better way to develop web applications!
|
||||
|
||||
|
||||
## An Introductory Angular Example
|
||||
|
||||
Let's say that you are a web designer, and you've spent many thous — erm, hundreds of hours
|
||||
designing web sites. But at this point, the thought of manipulating the DOM, writing listeners and
|
||||
input validators, all just to implement a simple form? No. You either don't want to go there in
|
||||
the first place or you've been there and the thrill is gone.
|
||||
|
||||
So look over the following simple example written using angular. Note that it features only the
|
||||
templating aspect of angular, but this should suffice for now to quickly demonstrate how much
|
||||
easier a web developer's life can if they're using angular:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function InvoiceCntl() {
|
||||
this.qty = 1;
|
||||
this.cost = 19.95;
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="InvoiceCntl">
|
||||
<b>Invoice:</b>
|
||||
<br />
|
||||
<br />
|
||||
<table>
|
||||
<tr><td> </td><td> </td>
|
||||
<tr><td>Quantity</td><td>Cost</td></tr>
|
||||
<tr>
|
||||
<td><input type="integer" min="0" ng:model="qty" required ></td>
|
||||
<td><input type="number" ng:model="cost" required ></td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr />
|
||||
<b>Total:</b> {{qty * cost | currency}}
|
||||
</div>
|
||||
</doc:source>
|
||||
<!--
|
||||
<doc:scenario>
|
||||
it('should show of angular binding', function() {
|
||||
expect(binding('qty * cost')).toEqual('$19.95');
|
||||
input('qty').enter('2');
|
||||
input('cost').enter('5.00');
|
||||
expect(binding('qty * cost')).toEqual('$10.00');
|
||||
});
|
||||
</doc:scenario>
|
||||
-->
|
||||
</doc:example>
|
||||
|
||||
Try out the Live Preview above, and then let's walk through the example and describe what's going
|
||||
on.
|
||||
|
||||
In the `<html>` tag, we add an attribute to let the browser know about the angular namespace:
|
||||
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
|
||||
This ensures angular runs nicely in all major browsers.
|
||||
|
||||
In the `<script>` tag we do two angular setup tasks:
|
||||
|
||||
1. We load `angular.js`.
|
||||
2. The angular {@link api/angular.directive.ng:autobind ng:autobind} directive tells angular to
|
||||
{@link dev_guide.compiler compile} and manage the whole HTML document.
|
||||
|
||||
`<script src="http://code.angularjs.org/0.9.15/angular-0.9.15.min.js"
|
||||
ng:autobind></script>`
|
||||
|
||||
From the `name` attribute of the `<input>` tags, angular automatically sets up two-way data
|
||||
binding, and we also demonstrate some easy input validation:
|
||||
|
||||
Quantity: <input type="integer" min="0" ng:model="qty" required >
|
||||
Cost: <input type="number" ng:model="cost" required >
|
||||
|
||||
These input widgets look normal enough, but consider these points:
|
||||
|
||||
* When this page loaded, angular bound the names of the input widgets (`qty` and `cost`) to
|
||||
variables of the same name. Think of those variables as the "Model" component of the
|
||||
Model-View-Controller design pattern.
|
||||
* Note the angular/HTML widget, {@link api/angular.widget.input input}.
|
||||
You may have noticed that when you enter invalid data
|
||||
or leave the the input fields blank, the borders turn red color, and the display value disappears.
|
||||
These widgets make it easier to implement field validation than coding them in JavaScript,
|
||||
no? Yes.
|
||||
|
||||
And finally, the mysterious `{{ double curly braces }}`:
|
||||
|
||||
Total: {{qty * cost | currency}}
|
||||
|
||||
This notation, `{{ _expression_ }}`, is a bit of built-in angular {@link dev_guide.compiler.markup
|
||||
markup}, a shortcut for displaying data to the user. The expression within curly braces gets
|
||||
transformed by the angular compiler into an angular directive ({@link api/angular.directive.ng:bind
|
||||
ng:bind}). The expression itself can be a combination of both an expression and a {@link
|
||||
dev_guide.templates.filters filter}: `{{ expression | filter }}`. Angular provides filters for
|
||||
formatting display data.
|
||||
|
||||
In the example above, the expression in double-curly braces directs angular to, "Bind the data we
|
||||
got from the input widgets to the display, multiply them together, and format the resulting number
|
||||
into output that looks like money."
|
||||
|
||||
|
||||
# The Angular Philosophy
|
||||
|
||||
Angular is built around the belief that declarative code is better than imperative when it comes to
|
||||
building UIs and wiring software components together, while imperative code is excellent for
|
||||
expressing business logic.
|
||||
|
||||
Not to put too fine a point on it, but if you wanted to add a new label to your application, you
|
||||
could do so by simply adding text to the HTML template, saving the code, and refreshing your
|
||||
browser:
|
||||
|
||||
<pre>
|
||||
<span class="label">Hello</span>
|
||||
</pre>
|
||||
|
||||
Or, as in programmatic systems (like {@link http://code.google.com/webtoolkit/ GWT}), you would
|
||||
have to write the code and then run the code like this:
|
||||
|
||||
<pre>
|
||||
var label = new Label();
|
||||
label.setText('Hello');
|
||||
label.setClass('label');
|
||||
parent.addChild(label);
|
||||
</pre>
|
||||
|
||||
That's one line of markup versus four times as much code.
|
||||
|
||||
|
||||
## More Angular Philosophy
|
||||
|
||||
* It is a very good idea to decouple DOM manipulation from app logic. This dramatically improves
|
||||
the testability of the code.
|
||||
* It is a really, _really_ good idea to regard app testing as equal in importance to app writing.
|
||||
Testing difficulty is dramatically affected by the way the code is structured.
|
||||
* It is an excellent idea to decouple the client side of an app from the server side. This allows
|
||||
development work to progress in parallel, and allows for reuse of both sides.
|
||||
* It is very helpful indeed if the framework guides developers through the entire journey of
|
||||
building an app: from designing the UI, through writing the business logic, to testing.
|
||||
* It is always good to make common tasks trivial and difficult tasks possible.
|
||||
|
||||
Now that we're homing in on what angular is, perhaps now would be a good time to list a few things
|
||||
that angular is not:
|
||||
|
||||
* It's not a Library. You don't just call its functions, although it does provide you with some
|
||||
utility APIs.
|
||||
* It's not a DOM Manipulation Library. Angular uses jQuery to manipulate the DOM behind the scenes,
|
||||
rather than give you functions to manipulate the DOM yourself.
|
||||
* It's not a Widget Library. There are lots of existing widget libraries that you can integrate
|
||||
with angular.
|
||||
* It's not "Just Another Templating System". A part of angular is a templating system. The
|
||||
templating subsystem of angular is different from the traditional approach for these reasons:
|
||||
* It Uses HTML/CSS syntax: This makes it easy to read and can be edited with existing HTML/CSS
|
||||
authoring tools.
|
||||
* It Extends HTML vocabulary: Angular allows you to create new HTML tags, which expand into
|
||||
dynamic UI components.
|
||||
* It Executes in the browser: Removes the round trip to the server for many operations and
|
||||
creates instant feedback for users as well as developers.
|
||||
* It Has Bidirectional data binding: The model is the single source of truth. Programmatic
|
||||
changes to the model are automatically reflected in the view. Any changes by the user to the view
|
||||
are automatically reflected in the model.
|
||||
|
||||
|
||||
# Why You Want Angular
|
||||
|
||||
Angular frees you from the following pain:
|
||||
|
||||
* **Registering callbacks:** Registering callbacks clutters your code, making it hard to see the
|
||||
forest for the trees. Removing common boilerplate code such as callbacks is a good thing. It vastly
|
||||
reduces the amount of JavaScript coding _you_ have to do, and it makes it easier to see what your
|
||||
application does.
|
||||
* **Manipulating HTML DOM programatically:** Manipulating HTML DOM is a cornerstone of AJAX
|
||||
applications, but it's cumbersome and error-prone. By declaratively describing how the UI should
|
||||
change as your application state changes, you are freed from low level DOM manipulation tasks. Most
|
||||
applications written with angular never have to programatically manipulate the DOM, although you
|
||||
can if you want to.
|
||||
* **Marshaling data to and from the UI:** CRUD operations make up the majority of AJAX
|
||||
applications. The flow of marshaling data from the server to an internal object to an HTML form,
|
||||
allowing users to modify the form, validating the form, displaying validation errors, returning to
|
||||
an internal model, and then back to the server, creates a lot of boilerplate code. Angular
|
||||
eliminates almost all of this boilerplate, leaving code that describes the overall flow of the
|
||||
application rather than all of the implementation details.
|
||||
* **Writing tons of initialization code just to get started:** Typically you need to write a lot of
|
||||
plumbing just to get a basic "Hello World" AJAX app working. With angular you can bootstrap your
|
||||
app easily using services, which are auto-injected into your application in a {@link
|
||||
http://code.google.com/p/google-guice/ Guice}-like dependency-injection style. This allows you to
|
||||
get started developing features quickly. As a bonus, you get full control over the initialization
|
||||
process in automated tests.
|
||||
|
||||
|
||||
# Watch a Presentation About Angular
|
||||
|
||||
Here is an early presentation on angular, but note that substantial development has occurred since
|
||||
the talk was given in July of 2010.
|
||||
|
||||
<object width="480" height="385">
|
||||
<param name="movie" value="http://www.youtube.com/v/elvcgVSynRg&hl=en_US&fs=1"></param>
|
||||
<param name="allowFullScreen" value="true"></param>
|
||||
<param name="allowscriptaccess" value="always"></param>
|
||||
<embed src="http://www.youtube.com/v/elvcgVSynRg&hl=en_US&fs=1"
|
||||
type="application/x-shockwave-flash" allowscriptaccess="always"
|
||||
allowfullscreen="true" width="480" height="385"></embed>
|
||||
</object>
|
||||
|
||||
{@link
|
||||
|
||||
https://docs.google.com/present/edit?id=0Abz6S2TvsDWSZDQ0OWdjaF8yNTRnODczazdmZg&hl=en&authkey=CO-b7oID
|
||||
|
||||
Presentation}
|
||||
|
|
||||
{@link
|
||||
|
||||
https://docs.google.com/document/edit?id=1ZHVhqC0apbzPRQcgnb1Ye-bAUbNJ-IlFMyPBPCZ2cYU&hl=en&authkey=CInnwLYO
|
||||
|
||||
Source}
|
||||
@@ -0,0 +1,225 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Scopes: Scope Internals
|
||||
@description
|
||||
|
||||
## What is a scope?
|
||||
|
||||
A scope is an execution context for {@link dev_guide.expressions expressions}. You can think of a
|
||||
scope as a JavaScript object that has an extra set of APIs for registering change listeners and for
|
||||
managing its own life cycle. In Angular's implementation of the model-view-controller design
|
||||
pattern, a scope's properties comprise both the model and the controller methods.
|
||||
|
||||
|
||||
### Scope characteristics
|
||||
- Scopes provide APIs ({@link api/angular.scope.$watch $watch}) to observe model mutations.
|
||||
- Scopes provide APIs ({@link api/angular.scope.$apply $apply}) to propagate any model changes
|
||||
through the system into the view from outside of the "Angular realm" (controllers, services,
|
||||
Angular event handlers).
|
||||
- Scopes can be nested to isolate application components while providing access to shared model
|
||||
properties. A scope (prototypically) inherits properties from its parent scope.
|
||||
- In some parts of the system (such as controllers, services and directives), the scope is made
|
||||
available as `this` within the given context. (Note: This api will change before 1.0 is released.)
|
||||
|
||||
|
||||
### Root scope
|
||||
|
||||
Every application has a root scope, which is the ancestor of all other scopes. The root scope is
|
||||
responsible for creating the injector which is assigned to the {@link api/angular.scope.$service
|
||||
$service} property, and initializing the services.
|
||||
|
||||
### What is scope used for?
|
||||
|
||||
{@link dev_guide.expressions Expressions} in the view are {@link api/angular.scope.$eval evaluated}
|
||||
against the current scope. When HTML DOM elements are attached to a scope, expressions in those
|
||||
elements are evaluated against the attached scope.
|
||||
|
||||
There are two kinds of expressions:
|
||||
|
||||
- Binding expressions, which are observations of property changes. Property changes are reflected
|
||||
in the view during the {@link api/angular.scope.$digest digest cycle}.
|
||||
- Action expressions, which are expressions with side effects. Typically, the side effects cause
|
||||
execution of a method in a controller in response to a user action, such as clicking on a button.
|
||||
|
||||
|
||||
### Scope inheritance
|
||||
|
||||
A scope (prototypically) inherits properties from its parent scope. Since a given property may not
|
||||
reside on a child scope, if a property read does not find the property on a scope, the read will
|
||||
recursively check the parent scope, grandparent scope, etc. all the way to the root scope before
|
||||
defaulting to undefined.
|
||||
|
||||
{@link api/angular.directive Directives} associated with elements (ng:controller, ng:repeat,
|
||||
ng:include, etc.) create new child scopes that inherit properties from the current parent scope.
|
||||
Any code in Angular is free to create a new scope. Whether or not your code does so is an
|
||||
implementation detail of the directive, that is, you can decide when or if this happens.
|
||||
Inheritance typically mimics HTML DOM element nesting, but does not do so with the same
|
||||
granularity.
|
||||
|
||||
A property write will always write to the current scope. This means that a write can hide a parent
|
||||
property within the scope it writes to, as shown in the following example.
|
||||
|
||||
<pre>
|
||||
var root = angular.scope();
|
||||
var child = root.$new();
|
||||
|
||||
root.name = 'angular';
|
||||
expect(child.name).toEqual('angular');
|
||||
expect(root.name).toEqual('angular');
|
||||
|
||||
child.name = 'super-heroic framework';
|
||||
expect(child.name).toEqual('super-heroic framework');
|
||||
expect(root.name).toEqual('angular');
|
||||
</pre>
|
||||
|
||||
|
||||
### Scope life cycle
|
||||
1. **Creation**
|
||||
|
||||
* You can create the root scope via {@link api/angular.scope angular.scope()}.
|
||||
* To create a child scopes, you should call {@link api/angular.scope.$new parentScope.$new()}.
|
||||
|
||||
2. **Watcher registration**
|
||||
|
||||
Watcher registration can happen at any time and on any scope (root or child) via {@link
|
||||
api/angular.scope.$watch scope.$watch()} API.
|
||||
|
||||
3. **Model mutation**
|
||||
|
||||
For mutations to be properly observed, you should make them only within the execution of the
|
||||
function passed into {@link api/angular.scope.$apply scope.$apply()} call. (Angular apis do this
|
||||
implicitly, so no extra `$apply` call is needed when doing synchronous work in controllers, or
|
||||
asynchronous work with {@link api/angular.service.$xhr $xhr} or {@link api/angular.service.$defer
|
||||
$defer} services.
|
||||
|
||||
4. **Mutation observation**
|
||||
|
||||
At the end of each `$apply` call {@link api/angular.scope.$digest $digest} cycle is started on
|
||||
the root scope, which then propagates throughout all child scopes.
|
||||
|
||||
During the `$digest` cycle, all `$watch-ers` expressions or functions are checked for model
|
||||
mutation and if a mutation is detected, the `$watch-er` listener is called.
|
||||
|
||||
5. **Scope destruction**
|
||||
|
||||
When child scopes are no longer needed, it is the responsibility of the child scope creator to
|
||||
destroy them via {@link api/angular.scope.$destroy scope.$destroy()} API. This will stop
|
||||
propagation of `$digest` calls into the child scope and allow for memory used by the child scope
|
||||
models to be reclaimed by the garbage collector.
|
||||
|
||||
The root scope can't be destroyed via the `$destroy` API. Instead, it is enough to remove all
|
||||
references from your application to the scope object and garbage collector will do its magic.
|
||||
## Scopes in Angular applications
|
||||
To understand how Angular applications work, you need to understand how scopes work within an
|
||||
application. This section describes the typical life cycle of an application so you can see how
|
||||
scopes come into play throughout and get a sense of their interactions.
|
||||
### How scopes interact in applications
|
||||
|
||||
1. At application compile time, a root scope is created and is attached to the root `<HTML>` DOM
|
||||
element.
|
||||
1. The root scope creates an {@link api/angular.injector injector} which is assigned to the
|
||||
{@link api/angular.scope.$service $service} property of the root scope.
|
||||
2. Any eager {@link api/angular.scope.$service services} are initialized at this point.
|
||||
2. During the compilation phase, the {@link dev_guide.compiler compiler} matches {@link
|
||||
api/angular.directive directives} against the DOM template. The directives usually fall into one of
|
||||
two categories:
|
||||
- Observing {@link api/angular.directive directives}, such as double-curly expressions
|
||||
`{{expression}}`, register listeners using the {@link api/angular.scope.$watch $watch()} method.
|
||||
This type of directive needs to be notified whenever the expression changes so that it can update
|
||||
the view.
|
||||
- Listener directives, such as {@link api/angular.directive.ng:click ng:click}, register a
|
||||
listener with the DOM. When the DOM listener fires, the directive executes the associated
|
||||
expression and updates the view using the {@link api/angular.scope.$apply $apply()} method.
|
||||
3. When an external event (such as a user action, timer or XHR) is received, the associated {@link
|
||||
dev_guide.expressions expression} must be applied to the scope through the {@link
|
||||
api/angular.scope.$apply $apply()} method so that all listeners are updated correctly.
|
||||
|
||||
|
||||
### Directives that create scopes
|
||||
In most cases, {@link api/angular.directive directives} and scopes interact but do not create new
|
||||
instances of scope. However, some directives, such as {@link api/angular.directive.ng:controller
|
||||
ng:controller} and {@link api/angular.widget.@ng:repeat ng:repeat}, create new child scopes using
|
||||
the {@link api/angular.scope.$new $new()} method and then attach the child scope to the
|
||||
corresponding DOM element. You can retrieve a scope for any DOM element by using an
|
||||
`angular.element(aDomElement).scope()` method call.)
|
||||
|
||||
|
||||
### Controllers and scopes
|
||||
Scopes and controllers interact with each other in the following situations:
|
||||
- Controllers use scopes to expose controller methods to templates (see {@link
|
||||
api/angular.directive.ng:controller ng:controller}).
|
||||
- Controllers define methods (behavior) that can mutate the model (properties on the scope).
|
||||
- Controllers may register {@link api/angular.scope.$watch watches} on the model. These watches
|
||||
execute immediately after the controller behavior executes, but before the DOM gets updated.
|
||||
|
||||
See the {@link dev_guide.mvc.understanding_controller controller docs} for more information.
|
||||
|
||||
### Updating scope properties
|
||||
You can update a scope by calling its {@link api/angular.scope.$apply $apply()} method with an
|
||||
expression or a function as the function argument. However it is typically not necessary to do this
|
||||
explicitly. In most cases, angular intercepts all external events (such as user interactions, XHRs,
|
||||
and timers) and wraps their callbacks into the `$apply()` method call on the scope object for you
|
||||
at the right time. The only time you might need to call `$apply()` explicitly is when you create
|
||||
your own custom asynchronous widget or service.
|
||||
|
||||
The reason it is unnecessary to call `$apply()` from within your controller functions when you use
|
||||
built-in angular widgets and services is because your controllers are typically called from within
|
||||
an `$apply()` call already.
|
||||
|
||||
When a user inputs data, angularized widgets invoke `$apply()` on the current scope and evaluate an
|
||||
angular expression or execute a function on this scope. Afterwards `$apply` will trigger `$digest`
|
||||
call on the root scope, to propagate your changes through the entire system, which results in
|
||||
$watch-ers firing and view getting updated. Similarly, when a request to fetch data from a server
|
||||
is made and the response comes back, the data is written into the model (scope) within an $apply,
|
||||
which then pushes updates through to the view and any other dependents.
|
||||
|
||||
A widget that creates scopes (such as {@link api/angular.widget.@ng:repeat ng:repeat}) via `$new`,
|
||||
doesn't need to worry about propagating the `$digest` call from the parent scope to child scopes.
|
||||
This happens automatically.
|
||||
|
||||
## Scopes in unit-testing
|
||||
You can create scopes, including the root scope, in tests using the {@link api/angular.scope
|
||||
angular.scope()} API. This allows you to mimic the run-time environment and have full control over
|
||||
the life cycle of the scope so that you can assert correct model transitions. Since these scopes
|
||||
are created outside the normal compilation process, their life cycles must be managed by the test.
|
||||
|
||||
### Using scopes in unit-testing
|
||||
The following example demonstrates how the scope life cycle needs to be manually triggered from
|
||||
within the unit-tests.
|
||||
|
||||
<pre>
|
||||
// example of a test
|
||||
var scope = angular.scope();
|
||||
scope.$watch('name', function(scope, name){
|
||||
scope.greeting = 'Hello ' + name + '!';
|
||||
});
|
||||
|
||||
scope.name = 'angular';
|
||||
// The watch does not fire yet since we have to manually trigger the digest phase.
|
||||
expect(scope.greeting).toEqual(undefined);
|
||||
|
||||
// manually trigger digest phase from the test
|
||||
scope.$digest();
|
||||
expect(scope.greeting).toEqual('Hello Angular!');
|
||||
</pre>
|
||||
|
||||
|
||||
### Dependency injection in Tests
|
||||
|
||||
When you find it necessary to inject your own mocks in your tests, use a scope to override the
|
||||
service instances, as shown in the following example.
|
||||
|
||||
<pre>
|
||||
var myLocation = {};
|
||||
var scope = angular.scope(angular.service, {$location: myLocation});
|
||||
expect(scope.$service('$location')).toEqual(myLocation);
|
||||
</pre>
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.scopes Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.understanding_scopes Understanding Scopes}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.scope Angular Scope API}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Scopes
|
||||
@description
|
||||
|
||||
|
||||
An Angular scope is a JavaScript object with additional APIs useful for watching property changes,
|
||||
Angular scope is the model in Model-View-Controller paradigm. Instances of scope serve as the
|
||||
context within which all {@link dev_guide.expressions expressions} get evaluated.
|
||||
|
||||
You can think of Angular scope objects as the medium through which the model, view, and controller
|
||||
communicate. Scopes are linked during the compilation process with the view. This linkage provides
|
||||
the contexts in which Angular creates data-bindings between the model and the view.
|
||||
|
||||
In addition to providing the context in which data is evaluated, Angular scope objects watch for
|
||||
model changes. The scope objects also notify all components interested in any model changes (for
|
||||
example, functions registered through {@link api/angular.scope.$watch $watch}, bindings created by
|
||||
{@link api/angular.directive.ng:bind ng:bind}, or HTML input elements).
|
||||
|
||||
Angular scope objects:
|
||||
|
||||
* Link the model, controller and view template together.
|
||||
* Provide the mechanism to watch for model changes ({@link api/angular.scope.$watch $watch}).
|
||||
* Apply model changes to the system ({@link api/angular.scope.$apply $apply}).
|
||||
* Provide the context in which expressions are evaluated ({@link api/angular.scope.$eval $eval}).
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.scopes.understanding_scopes Understanding Scopes}
|
||||
* {@link dev_guide.scopes.internals Scopes Internals}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.scope Angular Scope API}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Scopes: Understanding Scopes
|
||||
@description
|
||||
|
||||
Angular automatically creates a root scope during initialization, and attaches it to the page's
|
||||
root DOM element (usually `<html>`). The root scope object, along with any of its child scope
|
||||
objects, serves as the infrastructure on which your data model is built. The data model (JavaScript
|
||||
objects, arrays, or primitives) is attached to angular scope properties. Angular binds the property
|
||||
values to the DOM where bindings are specified in the template. Angular attaches any controller
|
||||
functions you have created to their respective scope objects.
|
||||
|
||||
<img src="img/guide/simple_scope_final.png">
|
||||
|
||||
Angular scopes can be nested, so a child scope has a parent scope upstream in the DOM. When you
|
||||
display an angular expression in the view, angular walks the DOM tree looking in the closest
|
||||
attached scope object for the specified data. If it doesn't find the data in the closest attached
|
||||
scope, it looks further up the scope hierarchy until it finds the data.
|
||||
|
||||
A child scope object inherits properties from its parents. For example, in the following snippet of
|
||||
code, observe how the value of `name` changes, based on the HTML element it is displayed in:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<ul ng:init="name='Hank'; names=['Igor', 'Misko', 'Gail', 'Kai']">
|
||||
<li ng:repeat="name in names">
|
||||
Name = {{name}}!
|
||||
</li>
|
||||
</ul>
|
||||
<pre>Name={{name}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should override the name property', function() {
|
||||
expect(using('.doc-example-live').repeater('li').row(0)).
|
||||
toEqual(['Igor']);
|
||||
expect(using('.doc-example-live').repeater('li').row(1)).
|
||||
toEqual(['Misko']);
|
||||
|
||||
expect(using('.doc-example-live').repeater('li').row(2)).
|
||||
toEqual(['Gail']);
|
||||
expect(using('.doc-example-live').repeater('li').row(3)).
|
||||
toEqual(['Kai']);
|
||||
expect(using('.doc-example-live').element('pre').text()).
|
||||
toBe('Name=Hank');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
The angular {@link api/angular.widget.@ng:repeat ng:repeat} directive creates a new scope for each
|
||||
element that it repeats (in this example the elements are list items). In the `<ul>` element, we
|
||||
initialized `name` to "Hank", and we created an array called `names` to use as the data source for
|
||||
the list items. In each `<li>` element, `name` is overridden. Outside of the `<li>` repeater, the
|
||||
original value of `name` is displayed.
|
||||
|
||||
The following illustration shows the DOM and angular scopes for the example above:
|
||||
|
||||
<img src="img/guide/dom_scope_final.png">
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.scopes Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.internals Scopes Internals}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.scope Angular Scope API}
|
||||
@@ -0,0 +1,642 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Using $location
|
||||
@description
|
||||
|
||||
# What does it do?
|
||||
|
||||
The `$location` service parses the URL in the browser address bar (based on the {@link
|
||||
https://developer.mozilla.org/en/window.location window.location}) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into $location service and
|
||||
changes to $location are reflected into the browser address bar.
|
||||
|
||||
**The $location service:**
|
||||
|
||||
- Exposes the current URL in the browser address bar, so you can
|
||||
- Watch and observe the URL.
|
||||
- Change the URL.
|
||||
- Synchronizes the URL with the browser when the user
|
||||
- Changes the address bar.
|
||||
- Clicks the back or forward button (or clicks a History link).
|
||||
- Clicks on a link.
|
||||
- Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
|
||||
|
||||
|
||||
## Comparing $location to window.location
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<td class="empty-corner-lt"></td>
|
||||
<td>window.location</td>
|
||||
<td>$location service</td>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="head">purpose</td>
|
||||
<td>allow read/write access to the current browser location</td>
|
||||
<td>same</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">API</td>
|
||||
<td>exposes "raw" object with properties that can be directly modified</td>
|
||||
<td>exposes jQuery-style getters and setters</td>
|
||||
</tr>
|
||||
|
||||
<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>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">seamless integration with HTML5 API</td>
|
||||
<td>no</td>
|
||||
<td>yes (with a fallback for legacy browsers)</td>
|
||||
</tr>
|
||||
|
||||
<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>yes - $location.path() returns "/actual/path"</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## When should I use $location?
|
||||
Any time your application needs to react to a change in the current URL or if you want to change
|
||||
the current URL in the browser.
|
||||
|
||||
## What does it not do?
|
||||
Does not cause a full page reload when the browser URL is changed. To reload the page after
|
||||
changing the URL, use the lower-level API, `$window.location.href`.
|
||||
|
||||
|
||||
# General overview of the API
|
||||
|
||||
The `$location` service can behave differently, depending on the configuration that was provided to
|
||||
it when it was instantiated. The default configuration is suitable for many applications, for
|
||||
others customizing the configuration can enable new features.
|
||||
|
||||
Once the `$location` service is instantiated, you can interact with it via jQuery-style getter and
|
||||
setter methods that allow you to get or change the current URL in the browser.
|
||||
|
||||
## $location service configuration
|
||||
|
||||
To configure the `$location` service, you define the `$locationConfig` service which is an object
|
||||
with configuration properties:
|
||||
|
||||
- **html5Mode**: {boolean}<br />
|
||||
`true` - see HTML5 mode<br />
|
||||
`false` - see Hashbang mode<br />
|
||||
default: `false`
|
||||
|
||||
- **hashPrefix**: {string}<br />
|
||||
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br />
|
||||
default: `'!'`
|
||||
|
||||
### Example configuration
|
||||
<pre>
|
||||
angular.service('$locationConfig', function() {
|
||||
return {
|
||||
html5mode: true,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
## Getter and setter methods
|
||||
|
||||
`$location` service provides getter methods for read-only parts of the URL (absUrl, protocol, host,
|
||||
port) and getter / setter methods for url, path, search, hash:
|
||||
<pre>
|
||||
// get the current path
|
||||
$location.path();
|
||||
|
||||
// change the path
|
||||
$location.path('/newValue')
|
||||
</pre>
|
||||
|
||||
All of the setter methods return the same `$location` object to allow chaining. For example, to
|
||||
change multiple segments in one go, chain setters like this:
|
||||
<pre>$location.path('/newValue').search({key: value});</pre>
|
||||
|
||||
There is a special `replace` method which can be used to tell the $location service that the next
|
||||
time the $location service is synced with the browser, the last history record should be replaced
|
||||
instead of creating a new one. This is useful when you want to implement redirection, which would
|
||||
otherwise break the back button (navigating back would retrigger the redirection). To change the
|
||||
current URL without creating a new browser history record you can call:
|
||||
<pre>
|
||||
$location.path('/someNewPath');
|
||||
$location.replace();
|
||||
// or you can chain these as: $location.path('/someNewPath').replace();
|
||||
</pre>
|
||||
|
||||
Note that the setters don't update `window.location` immediately. Instead, `$location` service is
|
||||
aware of the {@link api/angular.scope scope} life-cycle and coalesces multiple `$location`
|
||||
mutations into one "commit" to the `window.location` object during the scope `$digest` phase. Since
|
||||
multiple changes to the $location's state will be pushed to the browser as a single change, it's
|
||||
enough to call the `replace()` method just once to make the entire "commit" a replace operation
|
||||
rather than addition to the browser history. Once the browser is updated, the $location service
|
||||
resets the flag set by `replace()` method and future mutations will create new history records,
|
||||
unless `replace()` is called again.
|
||||
|
||||
### Setters and character encoding
|
||||
You can pass special characters to `$location` service and it will encode them according to rules
|
||||
specified in {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. When you access the methods:
|
||||
|
||||
- All values that are passed to `$location` setter methods, `path()`, `search()`, `hash()`, are
|
||||
encoded.
|
||||
- Getters (calls to methods without parameters) return decoded values for the following methods
|
||||
`path()`, `search()`, `hash()`.
|
||||
- When you call the `absUrl()` method, the returned value is a full url with its segments encoded.
|
||||
- When you call the `url()` method, the returned value is path, search and hash, in the form
|
||||
`/path?search=a&b=c#hash`. The segments are encoded as well.
|
||||
|
||||
|
||||
# Hashbang and HTML5 Modes
|
||||
|
||||
`$location` service has two configuration modes which control the format of the URL in the browser
|
||||
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
|
||||
HTML5 {@link http://www.w3.org/TR/html5/history.html History API}. Applications use the same API in
|
||||
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
|
||||
facilitate the browser URL change and history management.
|
||||
|
||||
<img src="img/guide/hashbang_vs_regular_url.jpg">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<td class="empty-corner-lt"></td>
|
||||
<td>Hashbang mode</td>
|
||||
<td>HTML5 mode</td>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="head">configuration</td>
|
||||
<td>the default</td>
|
||||
<td>{ html5Mode: true }</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">URL format</td>
|
||||
<td>hashbang URLs in all browsers</td>
|
||||
<td>regular URLs in modern browser, hashbang URLs in old browser</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head"><a href=""> link rewriting</td>
|
||||
<td>no</td>
|
||||
<td>yes</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">requires server-side configuration</td>
|
||||
<td>no</td>
|
||||
<td>yes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Hashbang mode (default mode)
|
||||
|
||||
In this mode, `$location` uses Hashbang URLs in all browsers.
|
||||
|
||||
### Example
|
||||
|
||||
<pre>
|
||||
angular.service('$locationConfig', function() {
|
||||
return {
|
||||
html5Mode: false,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
|
||||
// open http://host.com/base/index.html#!/a
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/a'
|
||||
$location.path() == '/a'
|
||||
|
||||
$location.path('/foo')
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/foo'
|
||||
|
||||
$location.search() == {}
|
||||
$location.search({a: 'b', c: true});
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/foo?a=b&c'
|
||||
|
||||
$location.path('/new').search('x=y');
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/new?x=y'
|
||||
</pre>
|
||||
|
||||
### Crawling your app
|
||||
|
||||
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
|
||||
your document:
|
||||
<pre><meta name="fragment" content="!" /></pre>
|
||||
|
||||
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 {@link http://code.google.com/web/ajaxcrawling/docs/specification.html Making AJAX Applications
|
||||
Crawlable}.
|
||||
|
||||
## HTML5 mode
|
||||
|
||||
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
|
||||
through the HTML5 history API, which allows for use of regular URL path and search segments,
|
||||
instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the
|
||||
`$location` service will fall back to using the hashbang URLs automatically. This frees you from
|
||||
having to worry about whether the browser displaying your app supports the history API or not; the
|
||||
`$location` service transparently uses the best available option.
|
||||
|
||||
- Opening a regular URL in a legacy browser -> redirects to a hashbang URL
|
||||
- Opening hashbang URL in a modern browser -> rewrites to a regular URL
|
||||
|
||||
### Example
|
||||
|
||||
<pre>
|
||||
angular.service('$locationConfig', function() {
|
||||
return {
|
||||
html5Mode: true,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
|
||||
// in browser with HTML5 history support:
|
||||
// open http://host.com/#!/a -> rewrite to http://host.com/a
|
||||
// (replacing the http://host.com/#!/a history record)
|
||||
$location.path() == '/a'
|
||||
|
||||
$location.path('/foo');
|
||||
$location.absUrl() == 'http://host.com/foo'
|
||||
|
||||
$location.search() == {}
|
||||
$location.search({a: 'b', c: true});
|
||||
$location.absUrl() == 'http://host.com/foo?a=b&c'
|
||||
|
||||
$location.path('/new').search('x=y');
|
||||
$location.url() == 'new?x=y'
|
||||
$location.absUrl() == 'http://host.com/new?x=y'
|
||||
|
||||
// in browser without html5 history support:
|
||||
// open http://host.com/new?x=y -> redirect to http://host.com/#!/new?x=y
|
||||
// (again replacing the http://host.com/new?x=y history item)
|
||||
$location.path() == '/new'
|
||||
$location.search() == {x: 'y'}
|
||||
|
||||
$location.path('/foo/bar');
|
||||
$location.path() == '/foo/bar'
|
||||
$location.url() == '/foo/bar?x=y'
|
||||
$location.absUrl() == 'http://host.com/#!/foo/bar?x=y'
|
||||
</pre>
|
||||
|
||||
### Fallback for legacy browsers
|
||||
|
||||
For browsers that support the HTML5 history API, `$location` uses the HTML5 history API to write
|
||||
path and search. If the history API is not supported by a browser, `$location` supplies a Hasbang
|
||||
URL. This frees you from having to worry about whether the browser viewing your app supports the
|
||||
history API or not; the `$location` service makes this transparent to you.
|
||||
|
||||
### Html link rewriting
|
||||
|
||||
When you use the history API mode, you will need different links in different browser, but all you
|
||||
have to do is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
|
||||
When a user clicks on this link,
|
||||
|
||||
- In a legacy browser, the URL changes to `/index.html#!/some?foo=bar`
|
||||
- In a modern browser, the URL changes to `/some?foo=bar`
|
||||
|
||||
|
||||
In cases like the following, links are not rewritten; instead, the browser will perform a full page
|
||||
reload to the original link.
|
||||
|
||||
- Links with an `ng:ext-link` directive<br />
|
||||
Example: `<a href="/ext/link?a=b" ng:ext-link>link</a>`
|
||||
- Links that contain `target="_blank"`<br />
|
||||
Example: `<a href="/ext/link?a=b" target="_blank">link</a>`
|
||||
- Absolute links that go to a different domain<br />
|
||||
Example: `<a href="http://angularjs.org/">link</a>`
|
||||
|
||||
### Server side
|
||||
|
||||
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
|
||||
to entry point of your application (e.g. index.html)
|
||||
|
||||
### Crawling your app
|
||||
|
||||
If you want your AJAX application to be indexed by web crawlers, you rill need to add the following
|
||||
meta tag to the HEAD section of your document:
|
||||
<pre><meta name="fragment" content="!" /></pre>
|
||||
|
||||
This statement causes a crawler to request links with empty `_escaped_fragment_` parameter so that
|
||||
your server can recognize the crawler and serve it HTML snapshots. For more information about this
|
||||
technique, see {@link http://code.google.com/web/ajaxcrawling/docs/specification.html Making AJAX
|
||||
Applications Crawlable}.
|
||||
|
||||
### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. You must use an absolute path because the
|
||||
path is going to be rewritten. You can use `<base href="" />` tag as well.
|
||||
|
||||
Running Angular apps with the History API enabled from document root is strongly encouraged as it
|
||||
takes care of all relative link issues. **Otherwise you have to specify <base href="" /> !**
|
||||
|
||||
### Sending links among different browsers
|
||||
|
||||
Because of rewriting capability in HTML5 mode, your users will be able to open regular url links in
|
||||
legacy browsers and hashbang links in modern browser:
|
||||
|
||||
- Modern browser will rewrite hashbang URLs to regular URLs.
|
||||
- Older browsers will redirect regular URLs to hashbang URLs.
|
||||
|
||||
### Example
|
||||
|
||||
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
|
||||
that you can see the differences. These `$location` services are connected to a fake browsers. Each
|
||||
input represents address bar of the browser.
|
||||
|
||||
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
|
||||
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
|
||||
= on page reload.
|
||||
|
||||
In this examples we use `<base href="/base/index.html" />`
|
||||
|
||||
<ul class="doc-example">
|
||||
<li ng:non-bindable class="html5-hashbang-example">
|
||||
<div id="html5-mode" ng:controller="Html5Cntl">
|
||||
<h3>Browser with History API</h3>
|
||||
<ng:address-bar browser="html5"></ng:address-bar><br /><br />
|
||||
$location.protocol() = {{$location.protocol()}}<br />
|
||||
$location.host() = {{$location.host()}}<br />
|
||||
$location.port() = {{$location.port()}}<br />
|
||||
$location.path() = {{$location.path()}}<br />
|
||||
$location.search() = {{$location.search()}}<br />
|
||||
$location.hash() = {{$location.hash()}}<br />
|
||||
<a href="/base/first?a=b">/base/first?a=b</a> | <a
|
||||
href="sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/base/another?search"
|
||||
ng:ext-link>external</a>
|
||||
</div>
|
||||
|
||||
<div id="hashbang-mode" ng:controller="HashbangCntl">
|
||||
<h3>Browser without History API</h3>
|
||||
<ng:address-bar browser="hashbang"></ng:address-bar><br /><br />
|
||||
$location.protocol() = {{$location.protocol()}}<br />
|
||||
$location.host() = {{$location.host()}}<br />
|
||||
$location.port() = {{$location.port()}}<br />
|
||||
$location.path() = {{$location.path()}}<br />
|
||||
$location.search() = {{$location.search()}}<br />
|
||||
$location.hash() = {{$location.hash()}}<br />
|
||||
<a href="/base/first?a=b">/base/first?a=b</a> | <a
|
||||
href="sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/base/another?search"
|
||||
ng:ext-link>external</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script type="text/javascript">
|
||||
function FakeBrowser(initUrl, baseHref) {
|
||||
this.onUrlChange = function(fn) {
|
||||
this.urlChange = fn;
|
||||
};
|
||||
|
||||
this.url = function() {
|
||||
return initUrl;
|
||||
};
|
||||
|
||||
this.defer = function(fn, delay) {
|
||||
setTimeout(function() { fn(); }, delay || 0);
|
||||
};
|
||||
|
||||
this.baseHref = function() {
|
||||
return baseHref;
|
||||
};
|
||||
|
||||
this.hover = angular.noop;
|
||||
this.notifyWhenOutstandingRequests = angular.noop;
|
||||
}
|
||||
|
||||
var browsers = {
|
||||
html5: new FakeBrowser('http://www.host.com/base/path?a=b#h', '/base/index.html'),
|
||||
hashbang: new FakeBrowser('http://www.host.com/base/index.html#!/path?a=b#h',
|
||||
'/base/index.html')
|
||||
};
|
||||
|
||||
function Html5Cntl($location) {
|
||||
this.$location = $location;
|
||||
}
|
||||
|
||||
function HashbangCntl($location) {
|
||||
this.$location = $location;
|
||||
}
|
||||
|
||||
angular.widget('ng:address-bar', function(tpl) {
|
||||
return function(elm) {
|
||||
var browser = browsers[elm.attr('browser')],
|
||||
input = angular.element('<input type="text" />').val(browser.url()),
|
||||
delay;
|
||||
|
||||
input.bind('keypress keyup keydown', function() {
|
||||
if (!delay) {
|
||||
delay = setTimeout(fireUrlChange, 250);
|
||||
}
|
||||
});
|
||||
|
||||
browser.url = function(url) {
|
||||
return input.val(url);
|
||||
};
|
||||
|
||||
elm.append('Address: ').append(input);
|
||||
|
||||
function fireUrlChange() {
|
||||
delay = null;
|
||||
browser.urlChange(input.val());
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function initEnv(name) {
|
||||
var root = angular.element(document.getElementById(name + '-mode'));
|
||||
var scope = angular.scope(null, {
|
||||
$locationConfig: {html5Mode: true, hashPrefix: '!'},
|
||||
$browser: browsers[name],
|
||||
$document: root,
|
||||
$sniffer: {history: name == 'html5'}
|
||||
});
|
||||
|
||||
angular.compile(root)(scope).$apply();
|
||||
root.bind('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
initEnv('html5');
|
||||
initEnv('hashbang');
|
||||
</script>
|
||||
|
||||
|
||||
# Caveats
|
||||
|
||||
## Page reload navigation
|
||||
|
||||
The `$location` service allows you to change only the URL; it does not allow you to reload the
|
||||
page. When you need to change the URL and reload the page or navigate to a different page, please
|
||||
use a lower level API, {@link api/angular.service.$window $window.location.href}.
|
||||
|
||||
## Using $location outside of the scope life-cycle
|
||||
|
||||
`$location` knows about Angular's {@link api/angular.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.
|
||||
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.
|
||||
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.
|
||||
|
||||
## $location.path() and ! or / prefixes
|
||||
|
||||
A path should always begin with forward slash (`/`); the `$location.path()` setter will add the
|
||||
forward slash if it is missing.
|
||||
|
||||
Note that the `!` prefix in the hashbang mode is not part of `$location.path()`; it is actually
|
||||
hashPrefix.
|
||||
|
||||
|
||||
# Testing with the $location service
|
||||
|
||||
When using `$location` service during testing, you are outside of the angular's {@link
|
||||
api/angular.scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`.
|
||||
|
||||
<pre>
|
||||
angular.service('$serviceUnderTest', function($location) {
|
||||
// whatever it does...
|
||||
};
|
||||
|
||||
describe('$serviceUnderTest', function() {
|
||||
var scope, $location, $sut;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = angular.scope();
|
||||
$location = scope.$service('$location');
|
||||
$sut = scope.$service('$serviceUnderTest');
|
||||
});
|
||||
|
||||
it('should...', function() {
|
||||
$location.path('/new/path');
|
||||
scope.$apply();
|
||||
|
||||
// test whatever the service should do...
|
||||
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
# Migrating from earlier AngularJS releases
|
||||
|
||||
In earlier releases of Angular, `$location` used `hashPath` or `hashSearch` to process path and
|
||||
search methods. With this release, the `$location` service processes path and search methods and
|
||||
then uses the information it obtains to compose hashbang URLs (such as
|
||||
`http://server.com/#!/path?search=a`), when necessary.
|
||||
|
||||
## Changes to your code
|
||||
|
||||
<table>
|
||||
<tr class="head">
|
||||
<td>Navigation inside the app</td>
|
||||
<td>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.hash = value<br />$location.update(value)<br
|
||||
/>$location.updateHash(value)</td>
|
||||
<td>$location.path(path).search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath = path</td>
|
||||
<td>$location.path(path)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch = search</td>
|
||||
<td>$location.search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Navigation outside the app</td>
|
||||
<td>Use lower level API</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.update(value)</td>
|
||||
<td>$window.location.href = value</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location[protocol | host | port | path | search]</td>
|
||||
<td>$window.location[protocol | host | port | path | search]</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Read access</td>
|
||||
<td>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath</td>
|
||||
<td>$location.path()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch</td>
|
||||
<td>$location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href<br />$location.protocol<br />$location.host<br />$location.port<br
|
||||
/>$location.hash</td>
|
||||
<td>$location.absUrl()<br />$location.protocol()<br />$location.host()<br />$location.port()<br
|
||||
/>$location.path() + $location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.path<br />$location.search</td>
|
||||
<td>$window.location.path<br />$window.location.search</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Two-way binding to $location
|
||||
|
||||
The Angular's compiler currently does not support two-way binding for methods (see {@link
|
||||
https://github.com/angular/angular.js/issues/404 issue}). If you should require two-way binding,
|
||||
you will need to specify an extra property that has two watchers. For example:
|
||||
<pre>
|
||||
<!-- html -->
|
||||
<input type="text" ng:model="locationPath" />
|
||||
</pre>
|
||||
<pre>
|
||||
// js - controller
|
||||
this.$watch('locationPath', function(scope, path) {
|
||||
$location.path(path);
|
||||
});
|
||||
|
||||
this.$watch('$location.path()', function(scope, path) {
|
||||
scope.locationPath = path;
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
# Related API
|
||||
|
||||
* {@link api/angular.service.$location $location API}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Creating Angular Services
|
||||
@description
|
||||
|
||||
While angular offers several useful services, for any nontrivial application you'll find it useful
|
||||
to write your own custom services. To do this you begin by registering a service factory function
|
||||
that angular's DI will use to create the service object when it is needed.
|
||||
|
||||
The `angular.service` method accepts three parameters:
|
||||
|
||||
- `{string} name` - Name of the service.
|
||||
- `{function()} factory` - Factory function(called just once by DI).
|
||||
- `{Object} config` - Configuration object with the following properties:
|
||||
- `$inject` - {Array.<string>} - Array of service ids this service depends on. These services
|
||||
will be passed as arguments into the factory function in the same order specified in the `$inject`
|
||||
array. Defaults to `[]`.
|
||||
- `$eager` - {boolean} - If true, the service factory will be called and the service will be
|
||||
instantiated when angular boots. If false, the service will be lazily instantiated when it is first
|
||||
requested during instantiation of a dependant. Defaults to `false`.
|
||||
|
||||
The `this` of the factory function is bound to the root scope of the angular application.
|
||||
|
||||
All angular services participate in {@link dev_guide.di dependency injection (DI)} by registering
|
||||
themselves with angular's DI system (injector) under a `name` (id) as well as by declaring
|
||||
dependencies which need to be provided for the factory function of the registered service. The
|
||||
ability to swap dependencies for mocks/stubs/dummies in tests allows for services to be highly
|
||||
testable.
|
||||
|
||||
Following is an example of a very simple service. This service depends on the `$window` service
|
||||
(which is passed as a parameter to the factory function) and is just a function. The service simply
|
||||
stores all notifications; after the third one, the service displays all of the notifications by
|
||||
window alert.
|
||||
|
||||
<pre>
|
||||
angular.service('notify', function(win) {
|
||||
var msgs = [];
|
||||
return function(msg) {
|
||||
msgs.push(msg);
|
||||
if (msgs.length == 3) {
|
||||
win.alert(msgs.join("\n"));
|
||||
msgs = [];
|
||||
}
|
||||
};
|
||||
}, {$inject: ['$window']});
|
||||
</pre>
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.services.understanding_services Understanding Angular Services}
|
||||
* {@link dev_guide.services.registering_services Registering Angular Services}
|
||||
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
|
||||
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers }
|
||||
* {@link dev_guide.services.testing_services Testing Angular Services}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.service Angular Service API}
|
||||
@@ -0,0 +1,78 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Injecting Services Into Controllers
|
||||
@description
|
||||
|
||||
Using services as dependencies for controllers is very similar to using services as dependencies
|
||||
for another service.
|
||||
|
||||
Since JavaScript is a dynamic language, DI can't figure out which services to inject by static
|
||||
types (like in static typed languages). Therefore, you must specify the service name by using the
|
||||
`$inject` property, which is an array containing strings with names of services to be injected.
|
||||
The name must match the corresponding service ID registered with angular. The order of the service
|
||||
IDs matters: the order of the services in the array will be used when calling the factory function
|
||||
with injected parameters. The names of parameters in factory function don't matter, but by
|
||||
convention they match the service IDs.
|
||||
|
||||
<pre>
|
||||
function myController($loc, $log) {
|
||||
this.firstMethod = function() {
|
||||
// use $location service
|
||||
$loc.setHash();
|
||||
};
|
||||
this.secondMethod = function() {
|
||||
// use $log service
|
||||
$log.info('...');
|
||||
};
|
||||
}
|
||||
// which services to inject ?
|
||||
myController.$inject = ['$location', '$log'];
|
||||
</pre>
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script type="text/javascript">
|
||||
angular.service('notify', function(win) {
|
||||
var msgs = [];
|
||||
return function(msg) {
|
||||
msgs.push(msg);
|
||||
if (msgs.length == 3) {
|
||||
win.alert(msgs.join("\n"));
|
||||
msgs = [];
|
||||
}
|
||||
};
|
||||
}, {$inject: ['$window']});
|
||||
|
||||
function myController(notifyService) {
|
||||
this.callNotify = function(msg) {
|
||||
notifyService(msg);
|
||||
};
|
||||
}
|
||||
|
||||
myController.$inject = ['notify'];
|
||||
</script>
|
||||
|
||||
<div ng:controller="myController">
|
||||
<p>Let's try this simple notify service, injected into the controller...</p>
|
||||
<input ng:init="message='test'" type="text" ng:model="message" />
|
||||
<button ng:click="callNotify(message);">NOTIFY</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should test service', function() {
|
||||
expect(element(':input[ng\\:model="message"]').val()).toEqual('test');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
{@link dev_guide.services.understanding_services Understanding Angular Services}
|
||||
{@link dev_guide.services.creating_services Creating Angular Services}
|
||||
{@link dev_guide.services.registering_services Registering Angular Services}
|
||||
{@link dev_guide.services.managing_dependencies Managing Service Dependencies}
|
||||
{@link dev_guide.services.testing_services Testing Angular Services}
|
||||
|
||||
## Related API
|
||||
|
||||
{@link api/angular.service Angular Service API}
|
||||
@@ -0,0 +1,84 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Managing Service Dependencies
|
||||
@description
|
||||
|
||||
Angular allows services to declare other services as dependencies needed for construction of their
|
||||
instances.
|
||||
|
||||
To declare dependencies, you specify them in the factory function signature and via the `$inject`
|
||||
property, as an array of string identifiers. Optionally the `$inject` property declaration can be
|
||||
dropped (see "Inferring `$inject`" but note that that is currently an experimental feature).
|
||||
|
||||
Here is an example of two services that depend on each other, as well as on other services that are
|
||||
provided by angular's web framework:
|
||||
|
||||
<pre>
|
||||
/**
|
||||
* batchLog service allows for messages to be queued in memory and flushed
|
||||
* to the console.log every 50 seconds.
|
||||
*
|
||||
* @param {*} message Message to be logged.
|
||||
*/
|
||||
angular.service('batchLog', function($defer, $log) {
|
||||
var messageQueue = [];
|
||||
|
||||
function log() {
|
||||
if (messageQueue.length) {
|
||||
$log('batchLog messages: ', messageQueue);
|
||||
messageQueue = [];
|
||||
}
|
||||
$defer(log, 50000);
|
||||
}
|
||||
|
||||
// start periodic checking
|
||||
log();
|
||||
|
||||
return function(message) {
|
||||
messageQueue.push(message);
|
||||
}
|
||||
}, {$inject: ['$defer', '$log']});
|
||||
// note how we declared dependency on built-in $defer and $log services above
|
||||
|
||||
/**
|
||||
* routeTemplateMonitor monitors each $route change and logs the current
|
||||
* template via the batchLog service.
|
||||
*/
|
||||
angular.service('routeTemplateMonitor', function($route, batchLog) {
|
||||
this.$on('$afterRouteChange', function() {
|
||||
batchLog($route.current ? $route.current.template : null);
|
||||
});
|
||||
}, {$inject: ['$route', 'batchLog'], $eager: true});
|
||||
</pre>
|
||||
|
||||
Things to notice in this example:
|
||||
|
||||
* The `batchLog` service depends on the built-in {@link api/angular.service.$defer $defer} and
|
||||
{@link api/angular.service.$log $log} services, and allows messages to be logged into the
|
||||
`console.log` in batches.
|
||||
* The `routeTemplateMonitor` service depends on the built-in {@link api/angular.service.$route
|
||||
$route} service as well as our custom `batchLog` service.
|
||||
* The `routeTemplateMonitor` service is declared to be eager, so that it is started as soon as the
|
||||
application starts.
|
||||
* To underline the need for the eager instantiation of the `routeTemplateMonitor` service, nothing
|
||||
else in the application depends on this service, and in this particular case the factory function
|
||||
of this service doesn't return anything at all.
|
||||
* Both of our services use the factory function signature as well as the `$inject` property to
|
||||
declare their dependencies. It is important that the order of the string identifiers in the array
|
||||
associated with the `$inject` property is the same as the order of argument names in the signature
|
||||
of the factory function. Unless the dependencies are inferred from the function signature, it is
|
||||
this array with IDs and their order that the injector uses to determine which services and in which
|
||||
order to inject.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.services.understanding_services Understanding Angular Services}
|
||||
* {@link dev_guide.services.creating_services Creating Angular Services}
|
||||
* {@link dev_guide.services.registering_services Registering Services}
|
||||
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers }
|
||||
* {@link dev_guide.services.testing_services Testing Angular Services}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.service Angular Service API}
|
||||
* {@link api/angular.injector Angular Injector API}
|
||||
@@ -0,0 +1,22 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services
|
||||
@description
|
||||
|
||||
Services are a feature that angular brings to client-side web apps from the server side, where
|
||||
services have been commonly used for a long time. Services in angular apps are substitutable
|
||||
objects that are wired together using {@link dev_guide.di dependency injection (DI)}. Services are
|
||||
most often used with {@link dev_guide.di dependency injection}, also a key feature of angular apps.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.services.understanding_services Understanding Angular Services}
|
||||
* {@link dev_guide.services.creating_services Creating Angular Services}
|
||||
* {@link dev_guide.services.registering_services Registering Angular Services}
|
||||
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
|
||||
* {@link dev_guide.services.injecting_controllers Injecting Services Into Conrollers}
|
||||
* {@link dev_guide.services.testing_services Testing Angular Services}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.service Angular Service API}
|
||||
@@ -0,0 +1,69 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Registering Angular Services
|
||||
@description
|
||||
|
||||
To register a service, register a factory function that creates the service with angular's
|
||||
Injector. The Injector is exposed as {@link api/angular.scope.$service scope.$service}. The
|
||||
following pseudo-code shows a simple service registration:
|
||||
|
||||
<pre>
|
||||
angular.service('service id', function() {
|
||||
var shinyNewServiceInstance;
|
||||
//factory function body that constructs shinyNewServiceInstance
|
||||
return shinyNewServiceInstance;
|
||||
});
|
||||
</pre>
|
||||
|
||||
Note that you are not registering a service instance, but rather a factory function that will
|
||||
create this instance when called.
|
||||
|
||||
# Instantiating Angular Services
|
||||
|
||||
A service can be instantiated eagerly or lazily. By default angular instantiates services lazily,
|
||||
which means that a service will be created only when it is needed for instantiation of a service or
|
||||
an application component that depends on it. In other words, angular won't instantiate lazy
|
||||
services unless they are requested directly or indirectly by the application.
|
||||
|
||||
Eager services on the other hand, are instantiated right after the injector itself is created,
|
||||
which happens when the angular {@link dev_guide.bootstrap application initializes}.
|
||||
|
||||
To override the default, you can request that a service is eagerly instantiated as follows:
|
||||
|
||||
<pre>
|
||||
angular.service('service id', function() {
|
||||
var shinyNewServiceInstance;
|
||||
//factory function body that constructs shinyNewServiceInstance
|
||||
return shinyNewServiceInstance;
|
||||
}, {$eager: true});
|
||||
</pre>
|
||||
|
||||
While it is tempting to declare services as eager, only in few cases it is actually useful. If you
|
||||
are unsure whether to make a service eager, it likely doesn't need to be. To be more specific, a
|
||||
service should be declared as eager only if it fits one of these scenarios:
|
||||
|
||||
* Nothing in your application declares this service as its dependency, and this service affects the
|
||||
state or configuration of the application (e.g. a service that configures `$route` or `$resource`
|
||||
services)
|
||||
* A guarantee is needed that the service will be instantiated at application boot time, usually
|
||||
because the service passively observes the application and it is optional for other application
|
||||
components to depend on it. An example of this scenario is a service that monitors and logs
|
||||
application memory usage.
|
||||
|
||||
Lastly, it is important to realize that all angular services are applicaiton singletons. This means
|
||||
that there is only one instance of a given service per injector. Since angular is lethally allergic
|
||||
to the global state, it is possible to create multiple injectors, each with its own instance of a
|
||||
given service, but that is rarely needed, except in tests where this property is crucially
|
||||
important.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.services.understanding_services Understanding Angular Services}
|
||||
* {@link dev_guide.services.creating_services Creating Angular Services}
|
||||
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
|
||||
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers }
|
||||
* {@link dev_guide.services.testing_services Testing Angular Services}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.service Angular Service API}
|
||||
@@ -0,0 +1,58 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Testing Angular Services
|
||||
@description
|
||||
|
||||
Following is a unit test for the service in the example in {@link
|
||||
dev_guide.services.creating_services Creating Angular Services}. The unit test example uses Jasmine
|
||||
spy (mock) instead of a real browser alert.
|
||||
|
||||
<pre>
|
||||
var mock, notify;
|
||||
|
||||
beforeEach(function() {
|
||||
mock = {alert: jasmine.createSpy()};
|
||||
notify = angular.service('notify')(mock);
|
||||
});
|
||||
|
||||
it('should not alert first two notifications', function() {
|
||||
notify('one');
|
||||
notify('two');
|
||||
|
||||
expect(mock.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should alert all after third notification', function() {
|
||||
notify('one');
|
||||
notify('two');
|
||||
notify('three');
|
||||
|
||||
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
|
||||
});
|
||||
|
||||
it('should clear messages after alert', function() {
|
||||
notify('one');
|
||||
notify('two');
|
||||
notify('third');
|
||||
notify('more');
|
||||
notify('two');
|
||||
notify('third');
|
||||
|
||||
expect(mock.alert.callCount).toEqual(2);
|
||||
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.services.understanding_services Understanding Angular Services}
|
||||
* {@link dev_guide.services.creating_services Creating Angular Services}
|
||||
* {@link dev_guide.services.registering_services Registering Angular Services}
|
||||
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
|
||||
* {@link dev_guide.services.injecting_controllers Injecting Services Into Conrollers}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.service Angular Service API}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Understanding Angular Services
|
||||
@description
|
||||
|
||||
Angular services are singletons that carry out specific tasks common to web apps, such as the
|
||||
{@link api/angular.service.$xhr $xhr service} that provides low level access to the browser's
|
||||
`XMLHttpRequest` object.
|
||||
|
||||
To use an angular service, you identify it as a dependency for the dependent (a controller, or
|
||||
another service) that depends on the service. Angular's dependency injection subsystem takes care
|
||||
of the rest. The angular injector subsystem is in charge of service instantiation, resolution of
|
||||
dependencies, and provision of dependencies to factory functions as requested.
|
||||
|
||||
Angular injects dependencies using "constructor" injection (the service is passed in via a factory
|
||||
function). Because JavaScript is a dynamically typed language, angular's dependency injection
|
||||
subsystem cannot use static types to identify service dependencies. For this reason a dependent
|
||||
must explicitly define its dependencies by using the `$inject` property. For example:
|
||||
|
||||
myController.$inject = ['$location'];
|
||||
|
||||
The angular web framework provides a set of services for common operations. Like other core angular
|
||||
variables and identifiers, the built-in services always start with `$` (such as `$xhr` mentioned
|
||||
above). You can also create your own custom services.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.di About Angular Dependency Injection}
|
||||
* {@link dev_guide.services.creating_services Creating Angular Services}
|
||||
* {@link dev_guide.services.registering_services Registering Angular Services}
|
||||
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
|
||||
* {@link dev_guide.services.testing_services Testing Angular Services}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.service Angular Service API}
|
||||
* {@link api/angular.injector Injector API}
|
||||
@@ -0,0 +1,34 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Working With CSS in Angular
|
||||
@description
|
||||
|
||||
|
||||
Angular sets these CSS classes. It is up to your application to provide useful styling.
|
||||
|
||||
# CSS classes used by angular
|
||||
|
||||
* `ng-invalid`, `ng-valid`
|
||||
- **Usage:** angular applies this class to an input widget element if that element's input does
|
||||
notpass validation. (see {@link api/angular.widget.input input} widget).
|
||||
|
||||
* `ng-pristine`, `ng-dirty`
|
||||
- **Usage:** angular {@link api/angular.widget.input input} widget applies `ng-pristine` class
|
||||
to a new input widget element which did not have user interaction. Once the user interacts with
|
||||
the input widget the class is changed to `ng-dirty`.
|
||||
|
||||
# Marking CSS classes
|
||||
|
||||
* `ng-widget`, `ng-directive`
|
||||
- **Usage:** angular sets these class on elements where {@link api/angular.widget widget} or
|
||||
{@link api/angular.directive directive} has bound to.
|
||||
|
||||
|
||||
* Old browser support
|
||||
- Pre v9, IE browsers could not select `ng:include` elements in CSS, because of the `:`
|
||||
character. For this reason angular also sets `ng-include` class on any element which has `:`
|
||||
character in the name by replacing `:` with `-`.
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates Angular Templates}
|
||||
* {@link dev_guide.forms Angular Forms}
|
||||
@@ -0,0 +1,38 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Data Binding in Angular
|
||||
@description
|
||||
|
||||
Data-binding in angular web apps is the automatic syncing of data between the model and view
|
||||
components. The way that angular implements data-binding lets you treat the model as the
|
||||
single-source-of-truth in your application. The view is a projection of the model at all times.
|
||||
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"/>
|
||||
Most templating systems bind data in only one direction: they merge template and model components
|
||||
together into a view, as illustrated in the diagram. After the merge occurs, changes to the model
|
||||
or related sections of the view are NOT automatically reflected in the view. Worse, any changes
|
||||
that the user makes to the view are not reflected in the model. This means that the developer has
|
||||
to write code that constantly syncs the view with the model and the model with the view.
|
||||
|
||||
## Data Binding in Angular Templates
|
||||
|
||||
<img class="right" src="img/Two_Way_Data_Binding.png"/>
|
||||
The way angular templates works is different, as illustrated in the diagram. They are different
|
||||
because first the template (which is the uncompiled HTML along with any additional markup or
|
||||
directives) is compiled on the browser, and second, the compilation step produces a live view. We
|
||||
say live because any changes to the view are immediately reflected in the model, and any changes in
|
||||
the model are propagated to the view. This makes the model always the single-source-of-truth for
|
||||
the application state, greatly simplifying the programing model for the developer. You can think of
|
||||
the view as simply an instant projection of your model.
|
||||
|
||||
Because the view is just a projection of the model, the controller is completely separated from the
|
||||
view and unaware of it. This makes testing a snap because it is easy to test your controller in
|
||||
isolation without the view and the related DOM/browser dependency.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.scopes Angular Scopes}
|
||||
* {@link dev_guide.templates Angular Templates}
|
||||
@@ -0,0 +1,68 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Filters: Creating Angular Filters
|
||||
@description
|
||||
|
||||
Writing your own filter is very easy: just define a JavaScript function on the `angular.filter`
|
||||
object.
|
||||
The framework passes in the input value as the first argument to your function. Any filter
|
||||
arguments are passed in as additional function arguments.
|
||||
|
||||
You can use these variables in the function:
|
||||
|
||||
* `this` — The current scope.
|
||||
* `this.$element` — The DOM element containing the binding. The `$element` variable allows the
|
||||
filter to manipulate the DOM.
|
||||
|
||||
The following sample filter reverses a text string. In addition, it conditionally makes the
|
||||
text upper-case and assigns color.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script type="text/javascript">
|
||||
angular.filter('reverse', function(input, uppercase, color) {
|
||||
var out = "";
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
out = input.charAt(i) + out;
|
||||
}
|
||||
// conditional based on optional argument
|
||||
if (uppercase) {
|
||||
out = out.toUpperCase();
|
||||
}
|
||||
// DOM manipulation using $element
|
||||
if (color) {
|
||||
this.$element.css('color', color);
|
||||
}
|
||||
return out;
|
||||
});
|
||||
|
||||
function Ctrl() {
|
||||
this.greeting = 'hello';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div ng:controller="Ctrl">
|
||||
<input ng:model="greeting" type="greeting"><br>
|
||||
No filter: {{greeting}}<br>
|
||||
Reverse: {{greeting|reverse}}<br>
|
||||
Reverse + uppercase: {{greeting|reverse:true}}<br>
|
||||
Reverse + uppercase + blue: {{greeting|reverse:true:"blue"}}
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should reverse greeting', function() {
|
||||
expect(binding('greeting|reverse')).toEqual('olleh');
|
||||
input('greeting').enter('ABC');
|
||||
expect(binding('greeting|reverse')).toEqual('CBA');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates.filters Understanding Angular Filters}
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.filter Angular Filter API}
|
||||
@@ -0,0 +1,27 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Understanding Angular Filters
|
||||
@description
|
||||
|
||||
Angular filters format data for display to the user. In addition to formatting data, filters can
|
||||
also modify the DOM. This allows filters to handle tasks such as conditionally applying CSS styles
|
||||
to filtered output.
|
||||
|
||||
For example, you might have a data object that needs to be formatted according to the locale before
|
||||
displaying it to the user. You can pass expressions through a chain of filters like this:
|
||||
|
||||
name | uppercase
|
||||
|
||||
The expression evaluator simply passes the value of name to `angular.filter.uppercase()`.
|
||||
|
||||
In addition to formatting data, filters can also modify the DOM. This allows filters to handle
|
||||
tasks such as conditionally applying CSS styles to filtered output.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates.filters.using_filters Using Angular Filters}
|
||||
* {@link dev_guide.templates.filters.creating_filters Creating Angular Filters}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.filter Angular Filter API}
|
||||
@@ -0,0 +1,40 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Filters: Using Angular Filters
|
||||
@description
|
||||
|
||||
Filters can be part of any {@link api/angular.scope} evaluation but are typically used to format
|
||||
expressions in bindings in your templates:
|
||||
|
||||
{{ expression | filter }}
|
||||
|
||||
Filters typically transform the data to a new data type, formatting the data in the process.
|
||||
Filters can also be chained, and can take optional arguments.
|
||||
|
||||
You can chain filters using this syntax:
|
||||
|
||||
{{ expression | filter1 | filter2 }}
|
||||
|
||||
You can also pass colon-delimited arguments to filters, for example, to display the number 123 with
|
||||
2 decimal points:
|
||||
|
||||
123 | number:2
|
||||
|
||||
Here are some examples that show values before and after applying different filters to an
|
||||
expression in a binding:
|
||||
|
||||
* No filter: `{{1234.5678}}` => `1234.5678`
|
||||
* Number filter: `{{1234.5678|number}}` => `1,234.57`. Notice the "," and rounding to two
|
||||
significant digits.
|
||||
* Filter with arguments: `{{1234.5678|number:5}}` => `1,234.56780`. Filters can take optional
|
||||
arguments, separated by colons in a binding. For example, the "number" filter takes a number
|
||||
argument that specifies how many digits to display to the right of the decimal point.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates.filters Understanding Angular Filters}
|
||||
* {@link dev_guide.templates.filters.creating_filters Creating Angular Filters}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.filter Angular Filter API}
|
||||
@@ -0,0 +1,59 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Understanding Angular Templates
|
||||
@description
|
||||
|
||||
An angular template is the declarative specification that, along with information from the model
|
||||
and controller, becomes the rendered view that a user sees in the browser. It is the static DOM,
|
||||
containing HTML, CSS, and angular-specific elements and angular-specific element attributes. The
|
||||
angular elements and attributes direct angular to add behavior and transform the template DOM into
|
||||
the dynamic view DOM.
|
||||
|
||||
These are the types of angular elements and element attributes you can use in a template:
|
||||
|
||||
* {@link dev_guide.compiler.directives Directive} — An attribute that augments an existing DOM
|
||||
element.
|
||||
* {@link dev_guide.compiler.widgets Widget} — A custom DOM element. An example of a built-in widget
|
||||
is {@link api/angular.widget.@ng:repeat ng:repeat}.
|
||||
* {@link dev_guide.compiler.markup Markup} — Shorthand for a widget or a directive. The double
|
||||
curly brace notation `{{ }}` to bind expressions to elements is built-in angular markup.
|
||||
* {@link dev_guide.templates.filters Filter} — Formats your data for display to the user.
|
||||
* {@link dev_guide.forms Form widgets} — Lets you validate user input.
|
||||
|
||||
Note: In addition to declaring the elements above in templates, you can also access these elements
|
||||
in JavaScript code.
|
||||
|
||||
The following code snippet shows a simple angular template made up of standard HTML tags along with
|
||||
angular {@link dev_guide.compiler.directives directives}, {@link dev_guide.compiler.markup markup},
|
||||
and {@link dev_guide.expressions expressions}:
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<!-- Body tag augmented with ng:controller directive -->
|
||||
<body ng:controller="MyController">
|
||||
<input ng:model="foo" value="bar">
|
||||
<!-- Button tag with ng:click directive, and
|
||||
string expression 'buttonText'
|
||||
wrapped in "{{ }}" markup -->
|
||||
<button ng:click="changeFoo()">{{buttonText}}</button>
|
||||
<script src="angular.js" ng:autobind>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
In a simple single-page app, the template consists of HTML, CSS, and angular directives contained
|
||||
in just one HTML file (usually `index.html`). In a more complex app, you can display multiple views
|
||||
within one main page using "partials", which are segments of template located in separate HTML
|
||||
files. You "include" the partials in the main page using the {@link api/angular.service.$route
|
||||
$route} service in conjunction with the {@link api/angular.widget.ng:view ng:view} directive. An
|
||||
example of this technique is shown in the {@link tutorial/ angular tutorial}, in steps seven and
|
||||
eight.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates.filters Angular Filters}
|
||||
* {@link dev_guide.forms Angular Forms}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/index API Reference}
|
||||
@@ -0,0 +1,279 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Unit Testing
|
||||
@description
|
||||
|
||||
JavaScript is a dynamically typed language which comes with great power of expression, but it also
|
||||
come with almost no-help from the compiler. For this reason we feel very strongly that any code
|
||||
written in JavaScript needs to come with a strong set of tests. We have built many features into
|
||||
angular which makes testing your angular applications easy. So there is no excuse for not do it.
|
||||
# It is all about NOT mixing concerns
|
||||
Unit testing as the name implies is about testing individual units of code. Unit tests try to
|
||||
answer the question: Did I think about the logic correctly. Does the sort function order the list
|
||||
in the right order. In order to answer such question it is very important that we can isolate it.
|
||||
That is because when we are testing the sort function we don't want to be forced into crating
|
||||
related pieces such as the DOM elements, or making any XHR calls in getting the data to sort. While
|
||||
this may seem obvious it usually is very difficult to be able to call an individual function on a
|
||||
typical project. The reason is that the developers often time mix concerns, and they end up with a
|
||||
piece of code which does everything. It reads the data from XHR, it sorts it and then it
|
||||
manipulates the DOM. With angular we try to make it easy for you to do the right thing, and so we
|
||||
provide dependency injection for your XHR (which you can mock out) and we crated abstraction which
|
||||
allow you to sort your model without having to resort to manipulating the DOM. So that in the end,
|
||||
it is easy to write a sort function which sorts some data, so that your test can create a data set,
|
||||
apply the function, and assert that the resulting model is in the correct order. The test does not
|
||||
have to wait for XHR, or create the right kind of DOM, or assert that your function has mutated the
|
||||
DOM in the right way. Angular is written with testability in mind, but it still requires that you
|
||||
do the right thing. We tried to make the right thing easy, but angular is not magic, which means if
|
||||
you don't follow these, you may very well end up with an untestable application.
|
||||
|
||||
## Dependency Inject
|
||||
There are several ways in which you can get a hold of a dependency:
|
||||
1. You could create it using the `new` operator.
|
||||
2. You could look for it in a well know place, also known as global singleton.
|
||||
3. You could ask a registry (also known as service registry) for it. (But how do you get a hold of
|
||||
the registry? Must likely by looking it up in a well know place. See #2)
|
||||
4. You could expect that the it be handed to you.
|
||||
|
||||
Out of the list above only the last of is testable. Lets look at why:
|
||||
|
||||
### Using the `new` operator
|
||||
|
||||
While there is nothing wrong with the `new` operator fundamentally the issue is that calling a new
|
||||
on a constructor permanently binds the call site to the type. For example lets say that we are
|
||||
trying to instantiate an `XHR` so that we can get some data from the server.
|
||||
|
||||
<pre>
|
||||
function MyClass() {
|
||||
this.doWork = function() {
|
||||
var xhr = new XHR();
|
||||
xhr.open(method, url, true);
|
||||
xhr.onreadystatechange = function() {...}
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
The issue becomes, that in tests, we would very much like to instantiate a `MockXHR` which would
|
||||
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
|
||||
permanently bound to the actual one, and there is no good way to replace it. Yes there is monkey
|
||||
patching, that is a bad idea for many reasons, which is outside the scope of this document.
|
||||
|
||||
The class above is hard to test since we have to resort to monkey patching:
|
||||
<pre>
|
||||
var oldXHR = XHR;
|
||||
XHR = function MockXHR() {};
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that MockXHR got called with the right arguments
|
||||
XHR = oldXHR; // if you forget this bad things will happen
|
||||
</pre>
|
||||
|
||||
|
||||
### Global look-up:
|
||||
Another way to approach the problem is look for the service in a well known location.
|
||||
|
||||
<pre>
|
||||
function MyClass() {
|
||||
this.doWork = function() {
|
||||
global.xhr({
|
||||
method:'...',
|
||||
url:'...',
|
||||
complete:function(response){ ... }
|
||||
})
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
While no new instance of dependency is being created, it is fundamentally the same as `new`, in
|
||||
that there is no good way to intercept the call to `global.xhr` for testing purposes, other then
|
||||
through monkey patching. The basic issue for testing is that global variable needs to be mutated in
|
||||
order to replace it with call to a mock method. For further explanation why this is bad see: {@link
|
||||
http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/ Brittle Global
|
||||
State & Singletons}
|
||||
|
||||
The class above is hard to test since we have to change global state:
|
||||
<pre>
|
||||
var oldXHR = glabal.xhr;
|
||||
glabal.xhr = function mockXHR() {};
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
global.xhr = oldXHR; // if you forget this bad things will happen
|
||||
</pre>
|
||||
|
||||
|
||||
### Service Registry:
|
||||
|
||||
It may seem as that this can be solved by having a registry for all of the services, and then
|
||||
having the tests replace the services as needed.
|
||||
|
||||
<pre>
|
||||
function MyClass() {
|
||||
var serviceRegistry = ????;
|
||||
this.doWork = function() {
|
||||
var xhr = serviceRegistry.get('xhr');
|
||||
xhr({
|
||||
method:'...',
|
||||
url:'...',
|
||||
complete:function(response){ ... }
|
||||
})
|
||||
}
|
||||
</pre>
|
||||
|
||||
However, where dose the serviceRegistry come from? if it is:
|
||||
* `new`-ed up, the the test has no chance to reset the services for testing
|
||||
* global look-up, then the service returned is global as well (but resetting is easier, since
|
||||
there is only one global variable to be reset).
|
||||
|
||||
The class above is hard to test since we have to change global state:
|
||||
<pre>
|
||||
var oldServiceLocator = glabal.serviceLocator;
|
||||
glabal.serviceLocator.set('xhr', function mockXHR() {});
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
glabal.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
|
||||
</pre>
|
||||
|
||||
|
||||
### Passing in Dependencies:
|
||||
Lastly the dependency can be passed in.
|
||||
|
||||
<pre>
|
||||
function MyClass(xhr) {
|
||||
this.doWork = function() {
|
||||
xhr({
|
||||
method:'...',
|
||||
url:'...',
|
||||
complete:function(response){ ... }
|
||||
})
|
||||
}
|
||||
</pre>
|
||||
|
||||
This is the preferred way since the code makes no assumptions as to where the `xhr` comes from,
|
||||
rather that whoever created the class was responsible for passing it in. Since the creator of the
|
||||
class should be different code than the user of the class, it separates the responsibility of
|
||||
creation from the logic, and that is what dependency-injection is in a nutshell.
|
||||
|
||||
The class above is very testable, since in the test we can write:
|
||||
<pre>
|
||||
function xhrMock(args) {...}
|
||||
var myClass = new MyClass(xhrMock);
|
||||
myClass.doWork();
|
||||
// assert that xhrMock got called with the right arguments
|
||||
</pre>
|
||||
|
||||
Notice that no global variables were harmed in the writing of this test.
|
||||
|
||||
Angular comes with {@link dev_guide.di dependency-injection} built in which makes the right thing
|
||||
easy to do, but you still need to do it if you wish to take advantage of the testability story.
|
||||
|
||||
## Controllers
|
||||
What makes each application unique is its logic, which is what we would like to test. If the logic
|
||||
for your application is mixed in with DOM manipulation, it will be hard to test as in the example
|
||||
below:
|
||||
|
||||
<pre>
|
||||
function PasswordController() {
|
||||
// get references to DOM elements
|
||||
var msg = $('.ex1 span');
|
||||
var input = $('.ex1 input');
|
||||
var strength;
|
||||
|
||||
this.grade = function() {
|
||||
msg.removeClass(strength);
|
||||
var pwd = input.val();
|
||||
password.text(pwd);
|
||||
if (pwd.length > 8) {
|
||||
strength = 'strong';
|
||||
} else if (pwd.length > 3) {
|
||||
strength = 'medium';
|
||||
} else {
|
||||
strength = 'weak';
|
||||
}
|
||||
msg
|
||||
.addClass(strength)
|
||||
.text(strength);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
The code above is problematic from testability, since it requires your test to have the right kind
|
||||
of DOM present when the code executes. The test would look like this:
|
||||
|
||||
<pre>
|
||||
var input = $('<input type="text"/>');
|
||||
var span = $('<span>');
|
||||
$('body').html('<div class="ex1">')
|
||||
.find('div')
|
||||
.append(input)
|
||||
.append(span);
|
||||
var pc = new PasswordController();
|
||||
input.val('abc');
|
||||
pc.grade();
|
||||
expect(span.text()).toEqual('weak');
|
||||
$('body').html('');
|
||||
</pre>
|
||||
|
||||
In angular the controllers are strictly separated from the DOM manipulation logic which results in
|
||||
a much easier testability story as can be seen in this example:
|
||||
|
||||
<pre>
|
||||
function PasswordCntrl() {
|
||||
this.password = '';
|
||||
this.grade = function() {
|
||||
var size = this.password.length;
|
||||
if (size > 8) {
|
||||
this.strength = 'strong';
|
||||
} else if (size > 3) {
|
||||
this.strength = 'medium';
|
||||
} else {
|
||||
this.strength = 'weak';
|
||||
}
|
||||
};
|
||||
}
|
||||
</pre>
|
||||
|
||||
and the tests is straight forward
|
||||
|
||||
<pre>
|
||||
var pc = new PasswordController();
|
||||
pc.password('abc');
|
||||
pc.grade();
|
||||
expect(span.strength).toEqual('weak');
|
||||
</pre>
|
||||
|
||||
Notice that the test is not only much shorter but it is easier to follow what is going on. We say
|
||||
that such a test tells a story, rather then asserting random bits which don't seem to be related.
|
||||
|
||||
|
||||
## Filters
|
||||
{@link api/angular.filter Filters} are functions which transform the data into user readable
|
||||
format. They are important because they remove the formatting responsibility from the application
|
||||
logic, further simplifying the application logic.
|
||||
|
||||
<pre>
|
||||
angular.filter('length', function(text){
|
||||
return (''+(text||'')).length;
|
||||
});
|
||||
|
||||
var length = angular.filter('length');
|
||||
expect(length(null)).toEqual(0);
|
||||
expect(length('abc')).toEqual(3);
|
||||
</pre>
|
||||
|
||||
## Directives
|
||||
Directives in angular are responsible for updating the DOM when the state of the model changes.
|
||||
|
||||
|
||||
## Mocks
|
||||
oue
|
||||
## Global State Isolation
|
||||
oue
|
||||
# Preferred way of Testing
|
||||
uo
|
||||
## JavaScriptTestDriver
|
||||
ou
|
||||
## Jasmine
|
||||
ou
|
||||
## Sample project
|
||||
uoe
|
||||
@@ -0,0 +1,57 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide
|
||||
@description
|
||||
|
||||
Welcome to the angular Developer Guide. If you are here to learn the details of how to use angular
|
||||
to develop web apps, you've come to the right place.
|
||||
|
||||
If you are completely or relatively unfamiliar with angular, you may want to check out one or both
|
||||
of the following documents before returning here to the Developer Guide:
|
||||
|
||||
* {@link misc/started Getting Started}
|
||||
* {@link tutorial/index Angular Tutorial}
|
||||
|
||||
<hr>
|
||||
|
||||
## {@link dev_guide.overview Overview of Angular}
|
||||
|
||||
## {@link dev_guide.bootstrap Initializing Angular}
|
||||
|
||||
* {@link dev_guide.bootstrap.auto_bootstrap Understanding Automatic Initialization}
|
||||
* {@link dev_guide.bootstrap.manual_bootstrap Understanding Manual Initialization}
|
||||
|
||||
## {@link dev_guide.mvc About MVC in Angular}
|
||||
|
||||
* {@link dev_guide.mvc.understanding_model Understanding the Model Component}
|
||||
* {@link dev_guide.mvc.understanding_controller Understanding the Controller Component}
|
||||
* {@link dev_guide.mvc.understanding_view Understanding the View Component}
|
||||
|
||||
## {@link dev_guide.scopes Angular Scope Objects}
|
||||
|
||||
* {@link dev_guide.scopes.understanding_scopes Understanding Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.internals Angular Scope Internals}
|
||||
|
||||
## {@link dev_guide.compiler Angular HTML Compiler}
|
||||
|
||||
* {@link dev_guide.compiler.directives Understanding Angular Directives}
|
||||
* {@link dev_guide.compiler.widgets Understanding Angular Widgets}
|
||||
* {@link dev_guide.compiler.directives_widgets Comparing Directives and Widgets}
|
||||
* {@link dev_guide.compiler.markup Understanding Angular Markup}
|
||||
|
||||
## {@link dev_guide.templates Angular Templates}
|
||||
|
||||
* {@link dev_guide.templates.filters Understanding Angular Filters}
|
||||
* {@link dev_guide.forms Understanding Angular Forms}
|
||||
|
||||
## {@link dev_guide.services Angular Services}
|
||||
|
||||
* {@link dev_guide.services.understanding_services Understanding Angular Services}
|
||||
* {@link dev_guide.services.creating_services Creating Angular Services}
|
||||
* {@link dev_guide.services.registering_services Registering Angular Services}
|
||||
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
|
||||
* {@link dev_guide.services.testing_services Testing Angular Services}
|
||||
|
||||
## {@link dev_guide.di About Dependency Injection}
|
||||
|
||||
* {@link dev_guide.di.understanding_di Understanding DI in Angular}
|
||||
* {@link dev_guide.di.using_di_controllers Using DI in Controllers}
|
||||
@@ -0,0 +1,262 @@
|
||||
@ngdoc overview
|
||||
@name Contributing
|
||||
@description
|
||||
|
||||
|
||||
* <a href="#H1_1">License</a>
|
||||
* <a href="#H1_2">Contributing to Source Code</a>
|
||||
* <a href="#H1_3">Applying Code Standards</a>
|
||||
* <a href="#H1_4">Checking Out and Building `Angular`</a>
|
||||
* <a href="#H1_5">Submitting Your Changes</a>
|
||||
|
||||
|
||||
<a name="H1_1"></a>
|
||||
# License
|
||||
|
||||
`Angular` is an open source project licensed under the {@link
|
||||
http://github.com/angular/angular.js/blob/master/LICENSE MIT license}. Your contributions are
|
||||
always welcome. When working with `angular` source base, please follow the guidelines provided on
|
||||
this page.
|
||||
|
||||
|
||||
<a name="H1_2"></a>
|
||||
# Contributing to Source Code
|
||||
|
||||
We'd love for you to contribute to our source code and to make `angular` even better than it is
|
||||
today! Here are the guidelines we'd like you to use:
|
||||
|
||||
* Major changes that you intend to contribute to the project must be discussed first on our {@link
|
||||
https://groups.google.com/forum/?hl=en#!forum/angular mailing list} so that we can better
|
||||
coordinate our efforts, prevent duplication of work, and help you to craft the change so that it
|
||||
is successfully accepted upstream.
|
||||
|
||||
* Small changes and bug fixes can be crafted and submitted to Github as a <a href="#H1_5">pull
|
||||
request</a>.
|
||||
|
||||
|
||||
|
||||
<a name="H1_3"></a>
|
||||
# Applying Code Standards
|
||||
|
||||
To ensure consistency throughout the source code, keep these rules in mind as you are working:
|
||||
|
||||
* All features or bug fixes must be tested by one or more <a href="#unit-tests">specs</a>.
|
||||
|
||||
* All public API methods must be documented with ngdoc, an extended version of jsdoc (we added
|
||||
support for markdown and templating via `@ngdoc` tag). To see how we document our APIs, please
|
||||
check out the existing ngdocs.
|
||||
|
||||
* With the exceptions listed below, we follow the rules contained in {@link
|
||||
http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml Google's JavaScript Style
|
||||
Guide}:
|
||||
|
||||
* Do not use namespaces: Instead, we wrap the entire `angular` code base in an anonymous closure
|
||||
and export our API explicitly rather than implicitly.
|
||||
|
||||
* Wrap all code at 100 characters.
|
||||
|
||||
* Instead of complex inheritance hierarchies, we prefer simple objects. We use prototypical
|
||||
inheritance only when absolutely necessary.
|
||||
|
||||
* We love functions and closures and, whenever possible, prefer them over objects.
|
||||
|
||||
* To write concise code that can be better minified, internally we use aliases that map to the
|
||||
external API. See our existing code to see what we mean.
|
||||
|
||||
* We don't go crazy with type annotations for private internal APIs unless it's an internal API
|
||||
that is used throughout `angular`. The best guidance is to do what makes the most sense.
|
||||
|
||||
|
||||
<a name="H1_4"></a>
|
||||
# Checking Out and Building Angular
|
||||
|
||||
The `angular` source code is hosted at {@link http://github.com Github}, which we also use to
|
||||
accept code contributions. Several steps are needed to check out and build `angular`:
|
||||
|
||||
|
||||
## Installation Dependencies
|
||||
|
||||
Before you can build `angular`, you must install or configure the following dependencies on your
|
||||
machine:
|
||||
|
||||
* {@link http://rake.rubyforge.org Rake}: We use Rake as our build system, which is pre-installed
|
||||
on most Macintosh and Linux machines. If that is not true in your case, you can grab it from the
|
||||
Rake website.
|
||||
|
||||
* {@link http://nodejs.org Node.js}: We use Node to generate the documentation and to run a
|
||||
development web server. Depending on your system, you can install Node either from source or as a
|
||||
pre-packaged bundle.
|
||||
|
||||
You'll also need npm and the following npm modules:
|
||||
|
||||
* install npm: `curl http://npmjs.org/install.sh | sh`
|
||||
* install q: `npm install q`
|
||||
* install qq: `npm install qq`
|
||||
* install q-fs: `npm install q-fs`
|
||||
* install jasmine-node: `npm install jasmine`
|
||||
|
||||
|
||||
|
||||
* Java: The Java runtime is used to run {@link http://code.google.com/p/js-test-driver
|
||||
JsTestDriver} (JSTD), which we use to run our unit test suite. JSTD binaries are part of the
|
||||
`angular` source base, which means there is no need to install or configure it separately.
|
||||
|
||||
* Git: The {@link http://help.github.com/mac-git-installation Github Guide to Installing Git} is
|
||||
quite a good source for information on Git.
|
||||
|
||||
|
||||
## Creating a Github Account and Forking Angular
|
||||
|
||||
To create a Github account, follow the instructions {@link https://github.com/signup/free here}.
|
||||
Afterwards, go ahead and {@link http://help.github.com/forking fork} the {@link
|
||||
https://github.com/angular/angular.js main angular repository}.
|
||||
|
||||
|
||||
## Building `Angular`
|
||||
|
||||
To build `angular`, you check out the source code and use Rake to generate the non-minified and
|
||||
minified `angular` files:
|
||||
|
||||
1. To clone your Github repository, run:
|
||||
|
||||
git clone git@github.com:<github username>/angular.js.git
|
||||
|
||||
2. To go to the `angular` directory, run:
|
||||
|
||||
cd angular.js
|
||||
|
||||
3. To add the main `angular` repository as an upstream remote to your repository, run:
|
||||
|
||||
git remote add upstream https://github.com/angular/angular.js.git
|
||||
|
||||
4. To build `angular`, run:
|
||||
|
||||
rake package
|
||||
|
||||
The build output can be located under the `build` directory. It consists of the following files and
|
||||
directories:
|
||||
|
||||
* `angular-<version>.tgz` — This is the complete tarball, which contains all of the release build
|
||||
artifacts.
|
||||
|
||||
* `angular.js` — The non-minified `angular` script.
|
||||
|
||||
* `angular.min.js` — The minified `angular` script.
|
||||
|
||||
* `angular-scenario.js` — The `angular` End2End test runner.
|
||||
|
||||
* `angular-ie-compat.js` — The Internet Explorer compatibility patch file.
|
||||
|
||||
* `docs/` — A directory that contains all of the files needed to run `docs.angularjs.org`.
|
||||
|
||||
* `docs/index.html` — The main page for the documentation.
|
||||
|
||||
* `docs/docs-scenario.html` — The End2End test runner for the documentation application.
|
||||
|
||||
|
||||
## Running a Local Development Web Server
|
||||
|
||||
To debug or test code, it is often useful to have a local HTTP server. For this purpose, we have
|
||||
made available a local web server based on Node.js.
|
||||
|
||||
1. To start the web server, run:
|
||||
|
||||
./nodeserver.sh
|
||||
|
||||
2. To access the local server, go to this website:
|
||||
|
||||
http://localhost:8000/
|
||||
|
||||
By default, it serves the contents of the `angular` project directory.
|
||||
|
||||
|
||||
<a name="unit-tests"></a>
|
||||
## Running the Unit Test Suite
|
||||
|
||||
Our unit and integration tests are written with Jasmine and executed with JsTestDriver. To run the
|
||||
tests:
|
||||
|
||||
1. To start the JSTD server, run:
|
||||
|
||||
./server.sh
|
||||
|
||||
2. To capture one or more browsers, go to this website:
|
||||
|
||||
http://localhost:9876/
|
||||
|
||||
3. To trigger a test execution, run:
|
||||
|
||||
./test.sh
|
||||
|
||||
4. To automatically run the test suite each time one or more of the files in the project directory
|
||||
is changed, you can install `watchr` and then run:
|
||||
|
||||
watchr watchr.rb
|
||||
|
||||
5. To view the output of each test run, you can tail this log file:
|
||||
|
||||
./logs/jstd.log
|
||||
|
||||
|
||||
## Running the End2End Test Suite
|
||||
|
||||
To run the End2End test suite:
|
||||
|
||||
1. Start the local web server.
|
||||
2. In a browser, go to:
|
||||
|
||||
http://localhost:8000/build/docs/docs-scenario.html
|
||||
|
||||
The tests are executed automatically.
|
||||
|
||||
|
||||
|
||||
<a name="H1_5"></a>
|
||||
# Submitting Your Changes
|
||||
|
||||
To create and submit a change:
|
||||
|
||||
1. Create a new branch off the master for your changes:
|
||||
|
||||
git branch my-fix-branch
|
||||
|
||||
2. Check out the branch:
|
||||
|
||||
git checkout my-fix-branch
|
||||
|
||||
3. Create your patch, make sure to have plenty of tests (that pass).
|
||||
|
||||
4. Commit your changes:
|
||||
|
||||
git commit -a
|
||||
|
||||
5. Run JavaScript Lint and be sure to address all new warnings and errors:
|
||||
|
||||
rake lint
|
||||
|
||||
6. Push your branch to Github:
|
||||
|
||||
git push origin my-fix-branch
|
||||
|
||||
7. In Github, send a pull request to `angular:master`.
|
||||
|
||||
8. When the patch is reviewed and merged, delete your branch and pull yours — and other — changes
|
||||
from the main (upstream) repository:
|
||||
|
||||
1. To delete the branch in Github, run:
|
||||
|
||||
git push origin :my-fix-branch
|
||||
|
||||
2. To check out the master branch, run:
|
||||
|
||||
git checkout master
|
||||
|
||||
3. To delete a local branch, run:
|
||||
|
||||
git branch -D my-fix-branch
|
||||
|
||||
4. To update your master with the latest upstream version, run:
|
||||
|
||||
git pull --ff upstream master
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
@@ -0,0 +1,70 @@
|
||||
@ngdoc overview
|
||||
@name Downloading
|
||||
@description
|
||||
|
||||
# Including angular scripts from the angular server
|
||||
The quickest way to get started is to point your html `<script>` tag to a
|
||||
<http://code.angularjs.org/> URL. This way, you don't have to download anything or maintain a
|
||||
local copy.
|
||||
|
||||
There are two types of angular script URLs you can point to, one for development and one for
|
||||
production:
|
||||
|
||||
* __angular-<version>.js__ — This is the human-readable, non-minified version, suitable for web
|
||||
development.
|
||||
* __angular-<version>.min.js__ — This is the minified version, which we strongly suggest you use in
|
||||
production.
|
||||
|
||||
To point your code to an angular script on the angular server, use the following template. This
|
||||
example points to (non-minified) version 0.9.12:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<title>My Angular App</title>
|
||||
<script src="http://code.angularjs.org/angular-0.9.12.js" ng:autobind></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
|
||||
# Downloading and hosting angular files locally
|
||||
This option is for those who want to work with angular offline, or those who want to host the
|
||||
angular files on their own servers.
|
||||
|
||||
If you navigate to <http://code.angularjs.org/>, you'll see a directory listing with all of the
|
||||
angular versions since we started releasing versioned build artifacts (quite late in the project
|
||||
lifetime). Each directory contains all artifacts that we released for a particular version.
|
||||
Download the version you want and have fun.
|
||||
|
||||
Each directory under <http://code.angularjs.org/> includes the following set of files:
|
||||
|
||||
* __`angular-<version>.js`__ — This file is non-obfuscated, non-minified, and human-readable by
|
||||
opening it it any editor or browser. In order to get better error messages during development, you
|
||||
should always use this non-minified angular script.
|
||||
|
||||
* __`angular-<version>.min.js`__ — This is a minified and obfuscated version of
|
||||
`angular-<version>.js` created with the Closure compiler. Use this version for production in order
|
||||
to minimize the size of the application that is downloaded by your user's browser.
|
||||
|
||||
* __`angular-<version>.tgz`__ — This is a tarball archive that contains all of the files released
|
||||
for this angular version. Use this file to get everything in a single download.
|
||||
|
||||
* __`angular-ie-compat-<version>.js`__ — This is a special file that contains code and data
|
||||
specifically tailored for getting Internet Explorer to work with angular. If you host your own copy
|
||||
of angular files, make sure that this file is available for download, and that it resides under the
|
||||
same parent path as `angular-<version>.js` or `angular-<version>.min.js`.
|
||||
|
||||
* __`angular-mocks-<version>.js`__ — This file contains an implementation of mocks that makes
|
||||
testing angular apps even easier. Your unit/integration test harness should load this file after
|
||||
`angular-<version>.js` is loaded.
|
||||
|
||||
* __`angular-scenario-<version>.js`__ — This file is a very nifty JavaScript file that allows you
|
||||
to write and execute end-to-end tests for angular applications.
|
||||
|
||||
* __`docs-<version>`__ — this directory contains all the files that compose the
|
||||
<http://docs.angularjs.org/> documentation app. These files are handy to see the older version of
|
||||
our docs, or even more importantly, view the docs offline.
|
||||
@@ -0,0 +1,81 @@
|
||||
@ngdoc overview
|
||||
@name FAQ
|
||||
@description
|
||||
|
||||
#FAQ
|
||||
|
||||
### Why is this project called "angular"? Why is the namespace called "ng"?
|
||||
|
||||
Because HTML has angular brackets and "ng" sounds like "angular".
|
||||
|
||||
### Is <angular/> an HTML5 tag?
|
||||
|
||||
No, <angular/> is not an HTML5 tag. angular is an orthogonal project to HTML5; you can use the two
|
||||
together.
|
||||
|
||||
### Is angular a {library, framework, DOM manipulation library, widget library, native plugin}?
|
||||
|
||||
No, angular is none of these. You don't call its functions, it does not call your functions,
|
||||
it does not provide a way to manipulate DOM, but does provide primitives to create UI projections
|
||||
of your data. There are lots of existing widget libraries which you can integrate with angular.
|
||||
It is 100% JavaScript, 100% client side and compatible with both desktop and mobile browsers.
|
||||
|
||||
### Do I need to worry about security holes in angular?
|
||||
|
||||
Like with any technology, angular is not impervious to attack. angular does, however, provide
|
||||
built-in protection from basic security holes including cross-site scripting and HTML injection
|
||||
attacks. angular does round-trip escaping on all strings for you.
|
||||
|
||||
### Can I download the source, build, and host the angular environment locally?
|
||||
|
||||
Yes. See instructions in {@link downloading}.
|
||||
|
||||
### Is angular a templating system?
|
||||
|
||||
At the highest level, angular does look like a just another templating system. But there is one
|
||||
important reason why angular templating system is different and makes it very good fit for
|
||||
application development: bidirectional data binding. The template is compiled on the browser and
|
||||
the compilation step produces a live view. This means you, the developer, don't need to write
|
||||
code to constantly sync the view with the model and the model with the view as in other
|
||||
templating systems.
|
||||
|
||||
### What browsers does angular work with?
|
||||
|
||||
Webkit-based browsers (Safari, Chrome, iPhone, Android, WebOS, BlackBerry 6), Firefox, IE6 and
|
||||
above. Note that CSS only works on IE7 and above.
|
||||
|
||||
### What's angular's performance like?
|
||||
|
||||
angular takes ~300ms to load, render, and compile. In Chrome it uses about 2-5MB of memory. Your
|
||||
app's performance will vary depending on how many bindings you use.
|
||||
|
||||
### How big is the angular bootstrap JS file that I need to include?
|
||||
|
||||
The size of the library itself is < 50KB compressed and obfuscated.
|
||||
|
||||
### Can I use the open-source Closure Library with angular?
|
||||
|
||||
Yes, you can use widgets from the {@link http://code.google.com/closure/library Closure Library}
|
||||
in angular.
|
||||
|
||||
### Does angular use the jQuery library?
|
||||
|
||||
Yes, angular uses {@link http://jquery.com/ jQuery}, the open source DOM manipulation library.
|
||||
If jQuery is not present in your script path, angular falls back on its own implementation of
|
||||
{@link api/angular.element jQuery lite}. If jQuery is present in the path, angular uses it to
|
||||
manipulate the DOM.
|
||||
|
||||
### What is testability like in angular?
|
||||
|
||||
Very testable. It has an integrated dependency injection framework. See
|
||||
{@link api/angular.service service} for details.
|
||||
|
||||
### How can I learn more about angular?
|
||||
|
||||
Watch the July 28, 2010 talk
|
||||
"{@link http://www.youtube.com/watch?v=elvcgVSynRg| Angular: A Radically Different Way of Building
|
||||
AJAX Apps}".
|
||||
|
||||
### How is angular licensed?
|
||||
|
||||
The MIT License.
|
||||
@@ -0,0 +1,145 @@
|
||||
@ngdoc overview
|
||||
@name Getting Started
|
||||
@description
|
||||
|
||||
# Hello World!
|
||||
|
||||
A great way for you to get started with AngularJS is to create the tradtional
|
||||
"Hello World!" app:
|
||||
|
||||
1. In your favorite text editor, create an HTML file
|
||||
(for example, `helloworld.html`).
|
||||
2. From the __Source__ box below, copy and paste the code into your HTML file.
|
||||
(Double-click on the source to easily select all.)
|
||||
3. Open the file in your web browser.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Hello {{'World'}}!
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
|
||||
The resulting web page should look something like the following:
|
||||
|
||||
<img class="center" src="img/helloworld.png" border="1" />
|
||||
|
||||
Now let's take a closer look at that code, and see what is going on behind
|
||||
the scenes.
|
||||
|
||||
The first line of interest defines the `ng` namespace, which makes
|
||||
AngularJS work across all browsers (especially important for IE):
|
||||
|
||||
<pre>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
</pre>
|
||||
|
||||
The next line downloads the angular script, and instructs angular to process
|
||||
the entire HTML page when it is loaded:
|
||||
|
||||
<pre>
|
||||
<script type="text/javascript" src="http://code.angularjs.org/angular-?.?.?.min.js"
|
||||
ng:autobind></script>
|
||||
</pre>
|
||||
|
||||
(For details on what happens when angular processes an HTML page,
|
||||
see {@link guide/dev_guide.bootstrap Bootstrap}.)
|
||||
|
||||
Finally, this line in the `<body>` of the page is the template that describes
|
||||
how to display our greeting in the UI:
|
||||
|
||||
<pre>
|
||||
Hello {{'World'}}!
|
||||
</pre>
|
||||
|
||||
Note the use of the double curly brace markup (`{{ }}`) to bind the expression to
|
||||
the greeting text. Here the expression is the string literal 'World'.
|
||||
|
||||
Next let's look at a more interesting example, that uses AngularJS to
|
||||
bind a dynamic expression to our greeting text.
|
||||
|
||||
# Hello AngularJS World!
|
||||
|
||||
This example demonstrates angular's two-way data binding:
|
||||
|
||||
1. Edit the HTML file you created in the "Hello World!" example above.
|
||||
2. Replace the contents of `<body>` with the code from the __Source__ box below.
|
||||
3. Refresh your browser window.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Your name: <input type="text" ng:model="yourname" value="World"/>
|
||||
<hr/>
|
||||
Hello {{yourname}}!
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
|
||||
After the refresh, the page should look something like this:
|
||||
|
||||
<img class="left" src="img/helloworld_2way.png" border="1" />
|
||||
|
||||
These are some of the important points to note from this example:
|
||||
|
||||
* The text input {@link api/angular.widget widget} called `yourname` is bound to a model variable
|
||||
called `yourname`.
|
||||
* The double curly braces notation binds the `yourname` model to the greeting text.
|
||||
|
||||
* You did not need to explicitly register an event listener or define an event handler for events!
|
||||
|
||||
Now try typing your name into the input box, and notice the immediate change to
|
||||
the displayed greeting. This demonstrates the concept of angular's
|
||||
{@link guide/dev_guide.templates.databinding bi-directional data binding}. Any changes to the input
|
||||
field are immediately
|
||||
reflected in the model (one direction), and any changes to the model are
|
||||
reflected in the greeting text (the other direction).
|
||||
|
||||
|
||||
# Anatomy Of An Angular App
|
||||
|
||||
This section describes the 3 parts of an angular app, and explains how they map to the
|
||||
Model-View-Controller design pattern:
|
||||
|
||||
## Templates
|
||||
|
||||
Templates, which you write in HTML and CSS, serve as the View. You add elements, attributes, and
|
||||
markup to HTML, which serve as instructions to the angular compiler. The angular compiler is fully
|
||||
extensible, meaning that with angular you can build your own declarative language on top of HTML!
|
||||
|
||||
|
||||
## Application Logic and Behavior
|
||||
|
||||
Application Logic and Behavior, which you define in JavaScript, serve as the Controller. With
|
||||
angular (unlike with standard AJAX applications) you don't need to write additional listeners or
|
||||
DOM manipulators, because they are built-in. This feature makes your application logic very easy to
|
||||
write, test, maintain, and understand.
|
||||
|
||||
|
||||
## Data
|
||||
|
||||
The Model is referenced from properties on {@link guide/dev_guide.scopes angular scope objects}.
|
||||
The data in your model could be Javascript objects, arrays, or primitives, it doesn't matter. What
|
||||
matters is that these are all referenced by the scope object.
|
||||
|
||||
Angular employs scopes to keep your data model and your UI in sync. Whenever something occurs to
|
||||
change the state of the model, angular immediately reflects that change in the UI, and vice versa.
|
||||
|
||||
The following illustration shows the parts of an angular application and how they work together:
|
||||
|
||||
<img class="left" src="img/angular_parts.png" border="0" />
|
||||
|
||||
In addition, angular comes with a set of Services, which have the following properties:
|
||||
|
||||
* The services provided are very useful for building web applications.
|
||||
* You can extend and add application-specific behavior to services.
|
||||
* Services include Dependency-Injection, XHR, caching, URL routing, and browser abstraction.
|
||||
|
||||
|
||||
# Where To Go Next
|
||||
|
||||
* If you like what you've learned so far, you should definitely check out our awesome {@link
|
||||
tutorial/ Tutorial}, which walks you through the process of building real apps with AngularJS.
|
||||
|
||||
* For further explanations and examples of the AngularJS concepts presented on this page, see the
|
||||
{@link guide/index Developer Guide}.
|
||||
|
||||
* For additional hands-on examples of using AngularJS, including more source code that you can
|
||||
copy and paste into your own pages, take a look through the {@link cookbook/ Cookbook}.
|
||||
@@ -0,0 +1,150 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial
|
||||
@description
|
||||
|
||||
A great way to get introduced to Angular 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 src="img/tutorial/catalog_screen.png">
|
||||
|
||||
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:
|
||||
|
||||
* 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!
|
||||
|
||||
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.
|
||||
* Identify resources for learning more about AngularJS.
|
||||
|
||||
The tutorial guides you through the entire process of building a simple application, including
|
||||
writing and running unit and end-to-end tests. Experiments at the end of each step provide
|
||||
suggestions for you learn more about AngularJS and the application you are building.
|
||||
|
||||
You can go through the whole tutorial in a couple of hours or you may want to spend a pleasant day
|
||||
really digging into it. If you're looking for a shorter introduction to AngularJS, check out the
|
||||
{@link misc/started Getting Started} document.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Working with the code
|
||||
|
||||
You can follow this tutorial and hack on the code in either the Mac/Linux or the Windows
|
||||
environment. Options for working with the tutorial are to use the Git versioning system for source
|
||||
code management or to use scripts that copy snapshots of project files into your workspace
|
||||
(`sandbox`) directory. Select one of the tabs below and follow the instructions for setting up your
|
||||
computer for your preferred option.
|
||||
|
||||
<doc:tutorial-instructions show="true">
|
||||
<doc:tutorial-instruction id="git-mac" title="Git on Mac/Linux">
|
||||
<ol>
|
||||
<li><p>Verify that you have <a href="http://java.com/">Java</a> installed by running the
|
||||
following command in a terminal window:</p>
|
||||
<pre><code>java -version</code></pre>
|
||||
<p>You will need Java to run unit tests.</p></li>
|
||||
<li><p>Download Git from the <a href="http://git-scm.com/download">Git</a> site.</p>
|
||||
<p>You can build Git from source or use the pre-compiled package.</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><code>git clone git://github.com/angular/angular-phonecat.git</code></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><code>cd angular-phonecat</code></pre>
|
||||
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
|
||||
directory.</p></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 <a
|
||||
href="https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager">install
|
||||
node.js</a>. Use <code>node</code> to run <code>scripts/web-server.js</code>, a simple bundled
|
||||
http server.</p></li>
|
||||
</ol>
|
||||
</doc:tutorial-instruction>
|
||||
|
||||
<doc:tutorial-instruction id="git-win" title="Git on Windows">
|
||||
<ol>
|
||||
<li><p>You will need Java to run unit tests, so run the following command to verify that you
|
||||
have <a href="http://java.com/">Java</a> installed and that the <code>java</code> executable is on
|
||||
your <code>PATH</code>.</p>
|
||||
<pre><code>java -version</code></pre>
|
||||
<p></p></li>
|
||||
<li><p>Install msysGit from <a href="http://git-scm.com/download">the Git</a> site.</p></li>
|
||||
<li><p>Open msysGit bash and clone the angular-phonecat repository located at <a
|
||||
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
|
||||
<pre><code>git clone git://github.com/angular/angular-phonecat.git</code></pre>
|
||||
<p>This command creates the angular-phonecat directory in your current directory.</p></li>
|
||||
<li><p>Change your current directory to angular-phonecat.</p>
|
||||
<pre><code>cd angular-phonecat</code></pre>
|
||||
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
|
||||
directory.</p>
|
||||
<p>You should run all <code>git</code> commands from msysGit bash.</p>
|
||||
<p>Other commands like <code>test-server.bat</code> or <code>test.bat</code> should be
|
||||
executed from the Windows command line.</li>
|
||||
<li><p>You need an http server running on your system. If you don't already have one
|
||||
installed, you can install <a href="http://nodejs.org/">node.js</a>. Download the <a
|
||||
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them, and then add
|
||||
<code>nodejs\bin</code> into your <code>PATH</code>. Use <code>node</code> to run
|
||||
<code>scripts\web-server.js</code>, a simple, bundled http server.</p></li>
|
||||
</ol>
|
||||
</doc:tutorial-instruction>
|
||||
|
||||
<doc:tutorial-instruction id="ss-mac" title="Snapshots on Mac/Linux">
|
||||
<ol>
|
||||
<li><p>You need Java to run unit tests, so verify that you have <a
|
||||
href="http://java.com/">Java</a> installed by running the following command in a terminal
|
||||
window:</p>
|
||||
<pre><code>java -version</code></pre>
|
||||
<li><p>Download the <a href="http://code.angularjs.org/angular-phonecat/">zip archive</a>
|
||||
containing all of the files and unzip them into the [tutorial-dir] directory</p>.</li>
|
||||
<li><p>Change your current directory to [tutorial-dir]/sandbox, as follows:</p>
|
||||
<pre><code>cd [tutorial-dir]/sandbox</code></pre>
|
||||
<p>The tutorial instructions assume you are running all commands from your
|
||||
<code>sandbox</code> directory.</p></li>
|
||||
<li><p>You need an http server running on your system and Mac and Linux machines typically
|
||||
have Apache pre-installed. If you don't have an http server installed, you can <a
|
||||
href="https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager">install
|
||||
node.js</a> and use it to run <code>scripts/web-server.js</code>, a simple bundled http
|
||||
server.</p></li>
|
||||
</ol>
|
||||
</doc:tutorial-instruction>
|
||||
|
||||
<doc:tutorial-instruction id="ss-win" title="Snapshots on Windows">
|
||||
<ol>
|
||||
<li><p>Verify that you have <a href="http://java.com/">Java</a> installed and that the
|
||||
<code>java</code> executable is on your <code>PATH</code> by running the following command in the
|
||||
Windows command line:</p>
|
||||
<pre><code>java -version</code></pre>
|
||||
<p>You need Java to run unit tests, so download the <a
|
||||
href="http://code.angularjs.org/angular-phonecat/">zip archive</a> that contains all of the files
|
||||
and unzip the files into the [tutorial-dir] directory</p></li>
|
||||
<li><p>Change your current directory to [tutorial-dir]/sandbox, as follows:</p>
|
||||
<pre><code>cd [tutorial-dir]/sandbox</code></pre>
|
||||
<p>The tutorial instructions assume you are running all commands from this directory.</p></li>
|
||||
<li><p>You need an http server running on your system, but if you don't already have one
|
||||
already installed, you can install <a href="http://nodejs.org/">node.js</a>. Download the <a
|
||||
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them, and then add
|
||||
<code>nodejs\bin</code> into your <code>PATH</code>. Use <code>node</code> to run
|
||||
<code>scripts\web-server.js</code>, a simple bundled http server.</p></li>
|
||||
</ol>
|
||||
</doc:tutorial-instruction>
|
||||
</doc:tutorial-instructions>
|
||||
|
||||
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 going with {@link step_00 step 0}.
|
||||
@@ -0,0 +1,216 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 0 - angular-seed
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="0"></ul>
|
||||
|
||||
|
||||
You are now ready to build the Angular phonecat application. In this step, you will become familiar
|
||||
with the most important source code files, learn how to start the development servers bundled with
|
||||
angular-seed, and run the application in the browser.
|
||||
|
||||
|
||||
<doc:tutorial-instructions show="true">
|
||||
<doc:tutorial-instruction id="git-mac" title="Git on Mac/Linux">
|
||||
<ol>
|
||||
<li><p>In angular-phonecat directory, run this command:</p>
|
||||
<pre><code>git checkout -f step-0</code></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>./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">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>
|
||||
</doc:tutorial-instruction>
|
||||
|
||||
|
||||
<doc:tutorial-instruction id="git-win" title="Git on Windows">
|
||||
<ol>
|
||||
<li><p>Open msysGit bash and run this command (in angular-phonecat directory):</p>
|
||||
<pre><code>git checkout -f step-0</code></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">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>
|
||||
</doc:tutorial-instruction>
|
||||
|
||||
|
||||
<doc:tutorial-instruction id="ss-mac" title="Snapshots on Mac/Linux">
|
||||
<ol>
|
||||
<li><p>In the angular-phonecat directory, run this command:</p>
|
||||
<pre><code>./goto_step.sh 0</code></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>./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">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 angular-phonecat
|
||||
<code>sandbox</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>
|
||||
</doc:tutorial-instruction>
|
||||
|
||||
|
||||
<doc:tutorial-instruction id="ss-win" title="Snapshots on Windows">
|
||||
<ol>
|
||||
<li><p>Open windows command line and run this command (in the angular-phonecat directory):</p>
|
||||
<pre><code>goto_step.bat 0</code></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">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 angular-phonecat
|
||||
<code>sandbox</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>
|
||||
</doc:tutorial-instruction>
|
||||
</doc:tutorial-instructions>
|
||||
|
||||
|
||||
You can now see the page in your browser. It's not very exciting, but that's OK.
|
||||
|
||||
The static HTML page that displays "Nothing here yet!" was constructed with the HTML code shown
|
||||
below. The code contains some key Angular elements that we will need going forward.
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org/">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>my angular app</title>
|
||||
<link rel="stylesheet" href="css/app.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Nothing here yet!
|
||||
|
||||
<script src="lib/angular/angular.js" ng:autobind></script>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
## What is the code doing?
|
||||
|
||||
* xmlns declaration
|
||||
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
|
||||
This `xmlns` declaration for the `ng` namespace must be specified in all Angular applications in
|
||||
order to make Angular work with XHTML and IE versions older than 9 (regardless of whether you are
|
||||
using XHTML or HTML).
|
||||
|
||||
* Angular script tag
|
||||
|
||||
<script src="lib/angular/angular.js" ng:autobind>
|
||||
|
||||
This single line of code is all that is needed to bootstrap an angular application.
|
||||
|
||||
The 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
|
||||
looks for the {@link api/angular.directive.ng:autobind ng:autobind} attribute. If Angular finds
|
||||
`ng:autobind`, it creates a root scope for the application and associates it with the `<html>`
|
||||
element of the template:
|
||||
|
||||
<img src="img/tutorial/tutorial_00_final.png">
|
||||
|
||||
As you will see shortly, everything in Angular is evaluated within a scope. We'll learn more
|
||||
about this in the next steps.
|
||||
|
||||
|
||||
## What are all these files in my working directory?
|
||||
|
||||
Most of the files in your working directory come from the {@link
|
||||
https://github.com/angular/angular-seed angular-seed project} 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.
|
||||
|
||||
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`
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
Now let's go to {@link step_01 step 1} and add some content to the web app.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="0"></ul>
|
||||
@@ -0,0 +1,57 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 1 - Static Template
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="1"></ul>
|
||||
|
||||
|
||||
In order to illustrate how angular enhances standard HTML, you will create a purely *static* HTML
|
||||
page and then examine how we can turn this HTML code into a template that angular will use to
|
||||
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.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="1" show="true"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
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 {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-0...step-1 GitHub}:
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<ul>
|
||||
<li>
|
||||
<span>Nexus S</span>
|
||||
<p>
|
||||
Fast just got faster with Nexus S.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<span>Motorola XOOM™ with Wi-Fi</span>
|
||||
<p>
|
||||
The Next, Next Generation tablet.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* Try adding more static HTML to `index.html`. For example:
|
||||
|
||||
<p>Total number of phones: 2</p>
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
This addition to your app uses static HTML to display the list. Now, let's go to {@link step_02
|
||||
step 2} to learn how to use angular to dynamically generate the same list.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="1"></ul>
|
||||
@@ -0,0 +1,202 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 2 - Angular Templates
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="2"></ul>
|
||||
|
||||
|
||||
Now it's time to make the web page dynamic -- with Angular. 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 {@link http://en.wikipedia.org/wiki/Model–View–Controller the Model-View-Controller (MVC)
|
||||
design pattern} 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.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="2"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
The app now contains a list with three phones.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-1...step-2 GitHub}:
|
||||
|
||||
|
||||
## Template for the View
|
||||
|
||||
The __view__ component is constructed by Angular from this template:
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<body ng:controller="PhoneListCtrl">
|
||||
|
||||
<ul>
|
||||
<li ng:repeat="phone in phones">
|
||||
{{phone.name}}
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script src="lib/angular/angular.js" ng:autobind></script>
|
||||
<script src="js/controllers.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
We replaced the hard-coded phone list with the {@link api/angular.widget.@ng:repeat ng:repeat
|
||||
widget} and two {@link guide/dev_guide.expressions Angular expressions} enclosed in curly braces:
|
||||
`{{phone.name}}` and `{{phone.snippet}}`:
|
||||
|
||||
* 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>`
|
||||
tag as the template.
|
||||
|
||||
<img src="img/tutorial/tutorial_02_final.png">
|
||||
|
||||
* The curly braces around `phone.name` and `phone.snippet` are examples of {@link
|
||||
guide/dev_guide.compiler.markup Angular markup}. The curly markup is shorthand for the Angular
|
||||
directive {@link api/angular.directive.ng:bind ng:bind}. An `ng:bind` directive indicates a
|
||||
template binding point to Angular. Binding points are locations in a template where Angular creates
|
||||
data-binding between the view and the model.
|
||||
|
||||
In Angular, the view is a projection of the model through the HTML template. This means that
|
||||
whenever the model changes, Angular refreshes the appropriate binding points, which updates the
|
||||
view.
|
||||
|
||||
|
||||
## Model and Controller
|
||||
|
||||
The data __model__ (a simple array of phones in object literal notation) is instantiated within
|
||||
the __controller__ function(`PhoneListCtrl`):
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
function PhoneListCtrl() {
|
||||
this.phones = [{"name": "Nexus S",
|
||||
"snippet": "Fast just got faster with Nexus S."},
|
||||
{"name": "Motorola XOOM™ with Wi-Fi",
|
||||
"snippet": "The Next, Next Generation tablet."},
|
||||
{"name": "MOTOROLA XOOM™",
|
||||
"snippet": "The Next, Next Generation tablet."}];
|
||||
}
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
|
||||
Although the controller is not yet doing very much controlling, it is playing a crucial role. By
|
||||
providing context for our data model, the controller allows us to establish data-binding between
|
||||
the model and the view. We connected the dots between the presentation, data, and logic components
|
||||
as follows:
|
||||
|
||||
* The name of our controller function(in the JavaScript file `controllers.js`) matches the {@link
|
||||
api/angular.directive.ng:controller ng:controller} directive in the `<body>` tag (`PhoneListCtrl`).
|
||||
* The data is instantiated within the *scope* of our controller function; our template binding
|
||||
points are located within the block bounded by the `<body ng:controller="PhoneListCtrl">` tag.
|
||||
|
||||
The concept of a scope in Angular is crucial; a scope can be seen as the glue which allows the
|
||||
template, model and controller to work together. Angular uses scopes, along with the information
|
||||
contained in the template, data model, and controller, to keep models and views separate, but in
|
||||
sync. Any changes made to the model are reflected in the view; any changes that occur in the view
|
||||
are reflected in the model.
|
||||
|
||||
To learn more about Angular scopes, see the {@link api/angular.scope angular scope documentation}.
|
||||
|
||||
|
||||
## Tests
|
||||
|
||||
The "Angular way" makes it easy to test code as it is being developed. Take a look at the following
|
||||
unit test for your newly created controller:
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
<pre>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function() {
|
||||
|
||||
it('should create "phones" model with 3 phones', function() {
|
||||
var ctrl = new PhoneListCtrl();
|
||||
expect(ctrl.phones.length).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
The test verifies that we have three records in the phones array and the example demonstrates how
|
||||
easy it is to create a unit test for code in Angular. Since testing is such a critical part of
|
||||
software development, we make it easy to create tests in Angular so that developers are encouraged
|
||||
to write them.
|
||||
|
||||
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 {@link
|
||||
http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link
|
||||
https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
|
||||
|
||||
The angular-seed project is pre-configured to run all unit tests using {@link
|
||||
http://code.google.com/p/js-test-driver/ JsTestDriver}. 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-server.sh` to start the test web server.
|
||||
|
||||
2. Open a new browser tab or window and navigate to {@link http://localhost:9876}.
|
||||
|
||||
3. Choose "Capture this browser in strict mode".
|
||||
|
||||
At this point, you can leave this tab open and forget about it. JsTestDriver will use it to
|
||||
execute the tests and report the results in the terminal.
|
||||
|
||||
4. Execute the test by running `./scripts/test.sh`
|
||||
|
||||
You should see the following or similar output:
|
||||
|
||||
Chrome: Runner reset.
|
||||
.
|
||||
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (2.00 ms)
|
||||
Chrome 11.0.696.57 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms)
|
||||
|
||||
Yay! The test passed! Or not...
|
||||
|
||||
Note: If you see errors after you run the test, close the browser tab and go back to the terminal
|
||||
and kill the script, then repeat the procedure above.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Add another binding to `index.html`. For example:
|
||||
|
||||
<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:
|
||||
|
||||
this.hello = "Hello, World!"
|
||||
|
||||
Refresh your browser to make sure it says, "Hello, World!"
|
||||
|
||||
* Create a repeater that constructs a simple table:
|
||||
|
||||
<table>
|
||||
<tr><th>row number</th></tr>
|
||||
<tr ng:repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
|
||||
</table>
|
||||
|
||||
Now, make the list 1-based by incrementing `i` by one in the binding:
|
||||
|
||||
<table>
|
||||
<tr><th>row number</th></tr>
|
||||
<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)`, and rerun the
|
||||
`./scripts/test.sh` script.
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
You now have a dynamic app that features separate model, view, and controller components, and you
|
||||
are testing as you go. Now, let's go to {@link step_03 step 3} to learn how to add full text search
|
||||
to the app.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="2"></ul>
|
||||
@@ -0,0 +1,181 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 3 - Filtering Repeaters
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="3"></ul>
|
||||
|
||||
|
||||
We did a lot of work in laying a foundation for the app in the last step, so now we'll do something
|
||||
simple; we will add full text search (yes, it will be simple!). We will also write an end-to-end
|
||||
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.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="3"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
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
|
||||
{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
We made no changes to the controller.
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
Fulltext Search: <input ng:model="query"/>
|
||||
|
||||
<ul class="phones">
|
||||
<li ng:repeat="phone in phones.$filter(query)">
|
||||
{{phone.name}}
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
|
||||
We added a standard HTML `<input>` tag and used angular's {@link api/angular.Array.filter $filter}
|
||||
function to process the input for the `ng:repeater`.
|
||||
|
||||
This lets a user enter search criteria and immediately see the effects of their search on the phone
|
||||
list. This new code demonstrates the following:
|
||||
|
||||
* Data-binding. This is one of the core features in Angular. When the page loads, Angular binds the
|
||||
name of the input box to a variable of the same name in the data model and keeps the two in sync.
|
||||
|
||||
In this code, the data that a user types into the input box (named __`query`__) is immediately
|
||||
available as a filter input in the list repeater (`phone in phones.$filter(`__`query`__`)`). When
|
||||
changes to the data model cause the repeater's input to change, the repeater efficiently updates
|
||||
the DOM to reflect the current state of the model.
|
||||
|
||||
<img src="img/tutorial/tutorial_03_final.png">
|
||||
|
||||
* Use of `$filter`. The {@link api/angular.Array.filter $filter} method uses the `query` value to
|
||||
create a new array that contains only those records that match the `query`.
|
||||
|
||||
`ng:repeat` automatically updates the view in response to the changing number of phones returned
|
||||
by the `$filter`. The process is completely transparent to the developer.
|
||||
|
||||
## Test
|
||||
|
||||
In Step 2, we learned how to write and run unit tests. Unit tests are perfect for testing
|
||||
controllers and other components of our application written in JavaScript, but they can't easily
|
||||
test DOM manipulation or the wiring of our application. For these, an end-to-end test is a much
|
||||
better choice.
|
||||
|
||||
The search feature was fully implemented via templates and data-binding, so we'll write our first
|
||||
end-to-end test, to verify that the feature works.
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
describe('PhoneCat App', function() {
|
||||
|
||||
describe('Phone list view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../../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);
|
||||
|
||||
input('query').enter('motorola');
|
||||
expect(repeater('.phones li').count()).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
Even though the syntax of this test looks very much like our controller unit test written with
|
||||
Jasmine, the end-to-end test uses APIs of {@link guide/dev_guide.e2e-testing Angular's end-to-end
|
||||
test runner}.
|
||||
|
||||
To run the end-to-end test, open one of the following in a new browser tab:
|
||||
|
||||
* node.js users: {@link http://localhost:8000/test/e2e/runner.html}
|
||||
* users with other http servers:
|
||||
`http://localhost:[port-number]/[context-path]/test/e2e/runner.html`
|
||||
* casual reader: {@link http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html}
|
||||
|
||||
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.
|
||||
|
||||
# Experiments
|
||||
|
||||
* 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.
|
||||
|
||||
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 body element:
|
||||
|
||||
<body ng:controller="PhoneListCtrl">
|
||||
|
||||
If you want to bind to the query model from the `<title>` element, you must __move__ the
|
||||
`ng:controller` declaration to the HTML element because it is the common parent of both the body
|
||||
and title elements:
|
||||
|
||||
<html ng:controller="PhoneListCtrl">
|
||||
|
||||
Be sure to *remove* the `ng:controller` declaration from the body element.
|
||||
|
||||
While using double curlies works fine in 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 api/angular.directive.ng:bind ng:bind} or {@link
|
||||
api/angular.directive.ng:bind-template ng:bind-template} 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`:
|
||||
|
||||
<pre>
|
||||
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');
|
||||
});
|
||||
</pre>
|
||||
|
||||
Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test
|
||||
pass, edit the `index.html` template to add a `div` or `p` element with `id` `"status"` and content
|
||||
with the `query` binding.
|
||||
|
||||
* Add a `pause()` statement into an end-to-end test and rerun it. You'll see the runner pause; this
|
||||
gives you the opportunity to explore the state of your application while it is displayed in the
|
||||
browser. The app is live! You can change the search query to prove it. Notice how useful this is
|
||||
for troubleshooting end-to-end tests.
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
We have now added full text search and included a test to verify that search works! Now let's go on
|
||||
to {@link step_04 step 4} to learn how to add sorting capability to the phone app.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="3"></ul>
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 4 - Two-way Data Binding
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="4"></ul>
|
||||
|
||||
|
||||
In this step, you will add a feature to let your users control the order of the items in the phone
|
||||
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.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="4"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
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
|
||||
{@link https://github.com/angular/angular-phonecat/compare/step-3...step-4 GitHub}:
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<ul class="controls">
|
||||
<li>
|
||||
Search: <input type="text" ng:model="query"/>
|
||||
</li>
|
||||
<li>
|
||||
Sort by:
|
||||
<select ng:model="orderProp">
|
||||
<option value="name">Alphabetical</option>
|
||||
<option value="age">Newest</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="phones">
|
||||
<li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
|
||||
{{phone.name}}
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
|
||||
We made the following changes to the `index.html` template:
|
||||
|
||||
* First, we added a `<select>` html element named `orderProp`, so that our users can pick from the
|
||||
two provided sorting options.
|
||||
|
||||
<img src="img/tutorial/tutorial_04-06_final.png">
|
||||
|
||||
* We then chained the `$filter` method with {@link api/angular.Array.orderBy `$orderBy`} method to
|
||||
further process the input into the repeater. `$orderBy` is a utility method similar to {@link
|
||||
api/angular.Array.filter `$filter`}, but instead of filtering an array, it reorders it.
|
||||
|
||||
Angular creates a two way data-binding between the select element and the `orderProp` model.
|
||||
`orderProp` is then used as the input for the `$orderBy` method.
|
||||
|
||||
As we discussed in the section about data-binding and the repeater in step 3, whenever the model
|
||||
changes (for example because a user changes the order with the select drop down menu), Angular's
|
||||
data-binding will cause the view to automatically update. No bloated DOM manipulation code is
|
||||
necessary!
|
||||
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
__`app/js/controller.js`:__
|
||||
<pre>
|
||||
/* App Controllers */
|
||||
|
||||
function PhoneListCtrl() {
|
||||
this.phones = [{"name": "Nexus S",
|
||||
"snippet": "Fast just got faster with Nexus S.",
|
||||
"age": 0},
|
||||
{"name": "Motorola XOOM™ with Wi-Fi",
|
||||
"snippet": "The Next, Next Generation tablet.",
|
||||
"age": 1},
|
||||
{"name": "MOTOROLA XOOM™",
|
||||
"snippet": "The Next, Next Generation tablet.",
|
||||
"age": 2}];
|
||||
|
||||
this.orderProp = 'age';
|
||||
}
|
||||
</pre>
|
||||
|
||||
* We modified the `phones` model - the array of phones - and added an `age` property to each phone
|
||||
record. This property is used to order phones by age.
|
||||
|
||||
* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had
|
||||
not set the default value here, angular would have used the value of the first `<option>` element
|
||||
(`'name'`) when it initialized the data model.
|
||||
|
||||
This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the
|
||||
browser, "Newest" is selected in the drop down menu. This is because we set `orderProp` to `'age'`
|
||||
in the controller. So the binding works in the direction from our model to the UI. Now if you
|
||||
select "Alphabetically" in the drop down menu, the model will be updated as well and the phones
|
||||
will be reordered. That is the data-binding doing its job in the opposite direction — from the UI
|
||||
to the model.
|
||||
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
The changes we made should be verified with both a unit test and an end-to-end test. Let's look at
|
||||
the unit test first.
|
||||
|
||||
__`test/unit/controllerSpec.js`:__
|
||||
<pre>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function() {
|
||||
var scope, $browser, ctrl;
|
||||
|
||||
beforeEach(function() {
|
||||
ctrl = new PhoneListCtrl();
|
||||
});
|
||||
|
||||
|
||||
it('should create "phones" model with 3 phones', function() {
|
||||
expect(ctrl.phones.length).toBe(3);
|
||||
});
|
||||
|
||||
|
||||
it('should set the default value of orderProp model', function() {
|
||||
expect(ctrl.orderProp).toBe('age');
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
The unit test now verifies that the default ordering property is set.
|
||||
|
||||
We used Jasmine's API to extract the controller construction into a `beforeEach` block, which is
|
||||
shared by all tests in the parent `describe` block.
|
||||
|
||||
To run the unit tests, once again execute the `./scripts/test.sh` script and you should see the
|
||||
following output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
..
|
||||
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
|
||||
Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
|
||||
|
||||
|
||||
Let's turn our attention to the end-to-end test.
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
...
|
||||
it('should be possible to control phone order via the drop down select box',
|
||||
function() {
|
||||
|
||||
// narrow the dataset to make the test assertions shorter
|
||||
input('query').enter('tablet');
|
||||
|
||||
expect(repeater('.phones li', 'Phone List').column('a')).
|
||||
toEqual(["Motorola XOOM\u2122 with Wi-Fi",
|
||||
"MOTOROLA XOOM\u2122"]);
|
||||
|
||||
select('orderProp').option('alphabetical');
|
||||
|
||||
expect(repeater('.phones li', 'Phone List').column('a')).
|
||||
toEqual(["MOTOROLA XOOM\u2122",
|
||||
"Motorola XOOM\u2122 with Wi-Fi"]);
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
|
||||
The end-to-end test verifies that the ordering mechanism of the select box is working correctly.
|
||||
|
||||
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
|
||||
can see them running on {@link
|
||||
http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html
|
||||
Angular's server}.
|
||||
|
||||
# Experiments
|
||||
|
||||
* In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and
|
||||
you'll see that the ordering as well as the current selection in the dropdown menu will default to
|
||||
"Alphabetical".
|
||||
|
||||
* Add an `{{orderProp}}` binding into the `index.html` template to display its current value as
|
||||
text.
|
||||
|
||||
# Summary
|
||||
|
||||
Now that you have added list sorting and tested the app, go to {@link step_05 step 5} to learn
|
||||
about Angular services and how Angular uses dependency injection.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="4"></ul>
|
||||
@@ -0,0 +1,216 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 5 - XHRs & Dependency Injection
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="5"></ul>
|
||||
|
||||
|
||||
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 api/angular.service services} called {@link
|
||||
api/angular.service.$xhr $xhr}. We will use angular's {@link guide/dev_guide.di dependency
|
||||
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="5"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
You should now see a list of 20 phones.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-4...step-5
|
||||
GitHub}:
|
||||
|
||||
## Data
|
||||
|
||||
The `app/phones/phone.json` file in your project is a dataset that contains a larger list of phones
|
||||
stored in the JSON format.
|
||||
|
||||
Following is a sample of the file:
|
||||
<pre>
|
||||
[
|
||||
{
|
||||
"age": 13,
|
||||
"id": "motorola-defy-with-motoblur",
|
||||
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
|
||||
"snippet": "Are you ready for everything life throws your way?"
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
</pre>
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
We'll use angular's {@link api/angular.service.$xhr $xhr} service in our controller to make an HTTP
|
||||
request to your web server to fetch the data in the `app/phones/phones.json` file. `$xhr` is just
|
||||
one of several built-in {@link api/angular.service 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/dev_guide.di DI subsystem}. Dependency injection
|
||||
helps to make your web apps both well-structured (e.g., separate components for presentation, data,
|
||||
and control) and loosely coupled (dependencies between components are not resolved by the
|
||||
components themselves, but by the DI subsystem).
|
||||
|
||||
__`app/js/controllers.js:`__
|
||||
<pre>
|
||||
function PhoneListCtrl($xhr) {
|
||||
var self = this;
|
||||
|
||||
$xhr('GET', 'phones/phones.json', function(code, response) {
|
||||
self.phones = response;
|
||||
});
|
||||
|
||||
self.orderProp = 'age';
|
||||
}
|
||||
|
||||
//PhoneListCtrl.$inject = ['$xhr'];
|
||||
</pre>
|
||||
|
||||
`$xhr` makes an HTTP GET request to our web server, asking for `phone/phones.json` (the url is
|
||||
relative to our `index.html` file). The server responds by providing the data in the json file.
|
||||
(The response might just as well have been dynamically generated by a backend server. To the
|
||||
browser and our app they both look the same. For the sake of simplicity we used a json file in this
|
||||
tutorial.)
|
||||
|
||||
The `$xhr` service takes a callback as the last argument. This callback is used to process the
|
||||
response. We assign the response to the scope controlled by the 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 services you need as arguments to
|
||||
the controller's constructor function, as follows:
|
||||
|
||||
function PhoneListCtrl($xhr) {...}
|
||||
|
||||
Angular's dependency injector provides services to your controller when the controller is being
|
||||
constructed. The dependency injector also takes care of creating any transitive dependencies the
|
||||
service may have (services often depend upon other services).
|
||||
|
||||
<img src="img/tutorial/xhr_service_final.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 '$'
|
||||
prefix in front of the name. Don't use a '$' prefix when naming your services and models, in order
|
||||
to avoid any possible naming collisions.
|
||||
|
||||
### 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 {@link http://en.wikipedia.org/wiki/Minification_(programming)
|
||||
minify} the JavaScript code for `PhoneListCtrl` controller, all of its function arguments would be
|
||||
minified as well, and the dependency injector would not being able to identify services correctly.
|
||||
|
||||
To overcome issues caused by minification, just assign an array with service identifier strings
|
||||
into the `$inject` property of the controller function, just like the last line in the snippet
|
||||
(commented out) suggests:
|
||||
|
||||
PhoneListCtrl.$inject = ['$xhr'];
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
__`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 `$xhr` 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:
|
||||
|
||||
<pre>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function() {
|
||||
var scope, $browser, ctrl;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = angular.scope();
|
||||
$browser = scope.$service('$browser');
|
||||
|
||||
$browser.xhr.expectGET('phones/phones.json')
|
||||
.respond([{name: 'Nexus S'},
|
||||
{name: 'Motorola DROID'}]);
|
||||
ctrl = scope.$new(PhoneListCtrl);
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
We created the controller in the test environment, as follows:
|
||||
|
||||
* We created a root scope object by calling `angular.scope()`
|
||||
|
||||
* We called `scope.$new(PhoneListCtrl)` to get angular to create the child scope associated with
|
||||
the `PhoneListCtrl` controller
|
||||
|
||||
Because our code now uses the `$xhr` service to fetch the phone list data in our controller, before
|
||||
we create the `PhoneListCtrl` child scope, we need to tell the testing harness to expect an
|
||||
incoming request from the controller. To do this we:
|
||||
|
||||
* Use the {@link api/angular.scope.$service `$service`} method to retrieve the `$browser` service,
|
||||
a service that angular uses to represent various browser APIs. In tests, angular automatically uses
|
||||
a mock version of this service that allows you to write tests without having to deal with these
|
||||
native APIs and the global state associated with them.
|
||||
|
||||
* Use the `$browser.xhr.expectGET` method to train the `$browser` object to expect an incoming HTTP
|
||||
request and tell it what to respond with. Note that the responses are not returned before we call
|
||||
the `$browser.xhr.flush` method.
|
||||
|
||||
Now, we will make assertions to verify that the `phones` model doesn't exist on the scope, before
|
||||
the response is received:
|
||||
|
||||
<pre>
|
||||
it('should create "phones" model with 2 phones fetched from xhr', function() {
|
||||
expect(ctrl.phones).toBeUndefined();
|
||||
$browser.xhr.flush();
|
||||
|
||||
expect(ctrl.phones).toEqual([{name: 'Nexus S'},
|
||||
{name: 'Motorola DROID'}]);
|
||||
});
|
||||
</pre>
|
||||
|
||||
* We flush the xhr queue in the browser by calling `$browser.xhr.flush()`. This causes the callback
|
||||
we passed into the `$xhr` service to be executed with the trained response.
|
||||
|
||||
* We make the assertions, verifying that the phone model now exists on the scope.
|
||||
|
||||
Finally, we verify that the default value of `orderProp` is set correctly:
|
||||
|
||||
<pre>
|
||||
it('should set the default value of orderProp model', function() {
|
||||
expect(ctrl.orderProp).toBe('age');
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
|
||||
output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
..
|
||||
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
|
||||
Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* At the bottom of `index.html`, add a `{{phones}}` binding to see the list of phones displayed in
|
||||
json format.
|
||||
|
||||
* In the `PhoneListCtrl` controller, pre-process the xhr response by limiting the number of phones
|
||||
to the first 5 in the list. Use the following code in the xhr callback:
|
||||
|
||||
self.phones = response.splice(0, 5);
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
Now that you have learned how easy it is to use angular services (thanks to angular's
|
||||
implementation of dependency injection), go to {@link step_06 step 6}, where you will add some
|
||||
thumbnail images of phones and some links.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="5"></ul>
|
||||
@@ -0,0 +1,105 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 6 - Templating Links & Images
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="6"></ul>
|
||||
|
||||
|
||||
In this step, you will add thumbnail images for the phones in the phone list, and links that, for
|
||||
now, will go nowhere. In subsequent steps you will use the links to display additional information
|
||||
about the phones in the catalog.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="6"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
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 {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-5...step-6
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Data
|
||||
|
||||
Note that the `phones.json` file contains unique ids and image urls for each of the phones. The
|
||||
urls point to the `app/img/phones/` directory.
|
||||
|
||||
__`app/phones/phones.json`__ (sample snippet):
|
||||
<pre>
|
||||
[
|
||||
{
|
||||
...
|
||||
"id": "motorola-defy-with-motoblur",
|
||||
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
|
||||
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
</pre>
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<ul class="phones">
|
||||
<li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
|
||||
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
|
||||
To dynamically generate links that will in the future lead to phone detail pages, we used the
|
||||
now-familiar {@link guide/dev_guide.compiler.markup double-curly brace markup} in the `href`
|
||||
attribute values. In step 2, we added the `{{phone.name}}` binding as the element content. In this
|
||||
step the `{{phone.id}}` binding is used in the element attribute.
|
||||
|
||||
We also added phone images next to each record using an image tag with the {@link
|
||||
api/angular.directive.ng:src ng:src} directive. That directive prevents the browser from treating
|
||||
the angular `{{ expression }}` markup literally, which it would have done if we had only specified
|
||||
an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`). Using
|
||||
`ng:src` prevents the browser from making an http request to an invalid location.
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
__`test/e2e/scenarios.js`__:
|
||||
<pre>
|
||||
...
|
||||
it('should render phone specific links', function() {
|
||||
input('query').enter('nexus');
|
||||
element('.phones li a').click();
|
||||
expect(browser().location().hash()).toBe('/phones/nexus-s');
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
|
||||
We added a new end-to-end test to verify that the app is generating correct links to the phone
|
||||
views that we will implement in the upcoming steps.
|
||||
|
||||
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
|
||||
can see them running on {@link
|
||||
http://angular.github.com/angular-phonecat/step-6/test/e2e/runner.html
|
||||
angular's server}.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Replace the `ng:src` directive with a plain old `<src>` attribute. Using tools such as Firebug,
|
||||
or Chrome's Web Inspector, or inspecting the webserver access logs, confirm that the app is indeed
|
||||
making an extraneous request to `/app/%7B%7Bphone.imageUrl%7D%7D` (or
|
||||
`/app/index.html/{{phone.imageUrl}}`).
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
Now that you have added phone images and links, go to {@link step_07 step 7} to learn about angular
|
||||
layout templates and how angular makes it easy to create applications that have multiple views.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="6"></ul>
|
||||
@@ -0,0 +1,209 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 7 - Routing & Multiple Views
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="7"></ul>
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="7"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
Note that 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 most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-6...step-7
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Multiple Views, Routing and Layout Template
|
||||
|
||||
Our app is slowly growing and becoming more complex. Before step 7, the app provided our users with
|
||||
a single view (the list of all phones), and all of the template code was located in the
|
||||
`index.html` file. The next step in building the app is to add a view that will show detailed
|
||||
information about each of the devices in our list.
|
||||
|
||||
To add the detailed view, we could expand the `index.html` file to contain template code for both
|
||||
views, but that would get messy very quickly. Instead, we are going to turn the `index.html`
|
||||
template into what we call a "layout template". This is a template that is common for all views in
|
||||
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 api/angular.service.$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 {@link
|
||||
http://en.wikipedia.org/wiki/Deep_linking deep linking}, which lets us utilize the browser's
|
||||
history (back and forward navigation) and bookmarks.
|
||||
|
||||
|
||||
## Controllers
|
||||
|
||||
__`app/js/controller.js`:__
|
||||
<pre>
|
||||
function PhoneCatCtrl($route) {
|
||||
var self = this;
|
||||
|
||||
$route.when('/phones',
|
||||
{template: 'partials/phone-list.html', controller: PhoneListCtrl});
|
||||
$route.when('/phones/:phoneId',
|
||||
{template: 'partials/phone-detail.html', controller: PhoneDetailCtrl});
|
||||
$route.otherwise({redirectTo: '/phones'});
|
||||
|
||||
$route.onChange(function() {
|
||||
self.params = $route.current.params;
|
||||
});
|
||||
|
||||
$route.parent(this);
|
||||
}
|
||||
|
||||
//PhoneCatCtrl.$inject = ['$route'];
|
||||
...
|
||||
</pre>
|
||||
|
||||
We created a new controller called `PhoneCatCtrl`. We declared its dependency on the `$route`
|
||||
service and used this service to declare that our application consists of two different views:
|
||||
|
||||
* 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.
|
||||
|
||||
The statement `$route.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when
|
||||
the browser address doesn't match either of our routes.
|
||||
|
||||
Thanks to the `$route.parent(this);` statement and `ng:controller="PhoneCatCtrl"` declaration in
|
||||
the `index.html` template, the `PhoneCatCtrl` controller has a special role in our app. It is the
|
||||
"root" controller and the parent controller for the other two sub-controllers (`PhoneListCtrl` and
|
||||
`PhoneDetailCtrl`). The sub-controllers inherit the model properties and behavior from the root
|
||||
controller.
|
||||
|
||||
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 `$route.current.params` map.
|
||||
|
||||
The `params` alias created in the {@link api/angular.service.$route `$route.onChange`} callback
|
||||
allows us to use the `phoneId` property of this map in the `phone-details.html` template.
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
The `$route` service is usually used in conjunction with the {@link api/angular.widget.ng:view
|
||||
ng:view} widget. The role of the `ng:view` widget is to include the view template for the current
|
||||
route into the layout template, which makes it a perfect fit for our `index.html` template.
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<body ng:controller="PhoneCatCtrl">
|
||||
|
||||
<ng:view></ng:view>
|
||||
|
||||
<script src="lib/angular/angular.js" ng:autobind></script>
|
||||
<script src="js/controllers.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
Note that we removed most of the code in the `index.html` template and replaced it with a single
|
||||
line containing the `ng:view` tag. The code that we removed was placed into the `phone-list.html`
|
||||
template:
|
||||
|
||||
__`app/partials/phone-list.html`:__
|
||||
<pre>
|
||||
<ul class="predicates">
|
||||
<li>
|
||||
Search: <input type="text" ng:model="query"/>
|
||||
</li>
|
||||
<li>
|
||||
Sort by:
|
||||
<select ng:model="orderProp">
|
||||
<option value="name">Alphabetical</option>
|
||||
<option value="age">Newest</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="phones">
|
||||
<li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
|
||||
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
|
||||
<img src="img/tutorial/tutorial_07_final.png">
|
||||
|
||||
We also added a placeholder template for the phone details view:
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
TBD: detail view for {{params.phoneId}}
|
||||
</pre>
|
||||
|
||||
Note how we are using `params` model defined in the `PhoneCatCtrl` controller.
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
To automatically verify that everything is wired properly, we wrote end-to-end tests that navigate
|
||||
to various URLs and verify that the correct view was rendered.
|
||||
|
||||
<pre>
|
||||
...
|
||||
it('should redirect index.html to index.html#/phones', function() {
|
||||
browser().navigateTo('../../app/index.html');
|
||||
expect(browser().location().hash()).toBe('/phones');
|
||||
});
|
||||
...
|
||||
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../../app/index.html#/phones/nexus-s');
|
||||
});
|
||||
|
||||
|
||||
it('should display placeholder page with phoneId', function() {
|
||||
expect(binding('params.phoneId')).toBe('nexus-s');
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
|
||||
can see them running on {@link
|
||||
http://angular.github.com/angular-phonecat/step-7/test/e2e/runner.html
|
||||
angular's server}.
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* Try to add an `{{orderProp}}` binding to `index.html`, and you'll see that nothing happens even
|
||||
when you are in the phone list view. This is because the `orderProp` model is visible only in the
|
||||
scope managed by `PhoneListCtrl`, which is associated with the `<ng:view>` element. If you add the
|
||||
same binding into the `phone-list.html` template, the binding will work as expected.
|
||||
|
||||
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
|
||||
`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use
|
||||
`this.hero = "Captain Proton"`. Then add the `<p>hero = {{hero}}</p>` to all three of our templates
|
||||
(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
|
||||
inheritance and model property shadowing do some wonders.
|
||||
|
||||
# Summary
|
||||
|
||||
With the routing set up and the phone list view implemented, we're ready to go to {@link step_08
|
||||
step 8} to implement the phone details view.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="7"></ul>
|
||||
@@ -0,0 +1,186 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 8 - More Templating
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="8"></ul>
|
||||
|
||||
|
||||
In this step, you will implement the phone details view, which is displayed when a user clicks on a
|
||||
phone in the phone list.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="8"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
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 api/angular.service.$xhr $xhr} to fetch our
|
||||
data, and we'll flesh out the `phone-details.html` view template.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-7...step-8
|
||||
GitHub}:
|
||||
|
||||
## Data
|
||||
|
||||
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
|
||||
phone:
|
||||
|
||||
__`app/phones/nexus-s.json`:__ (sample snippet)
|
||||
<pre>
|
||||
{
|
||||
"additionalFeatures": "Contour Display, Near Field Communications (NFC),...",
|
||||
"android": {
|
||||
"os": "Android 2.3",
|
||||
"ui": "Android"
|
||||
},
|
||||
...
|
||||
"images": [
|
||||
"img/phones/nexus-s.0.jpg",
|
||||
"img/phones/nexus-s.1.jpg",
|
||||
"img/phones/nexus-s.2.jpg",
|
||||
"img/phones/nexus-s.3.jpg"
|
||||
],
|
||||
"storage": {
|
||||
"flash": "16384MB",
|
||||
"ram": "512MB"
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
|
||||
Each of these files describes various properties of the phone using the same data structure. We'll
|
||||
show this data in the phone detail view.
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
We'll expand the `PhoneDetailCtrl` by using the `$xhr` service to fetch the json files. This works
|
||||
the same way as the phone list controller.
|
||||
|
||||
__`app/js/controller.js`:__
|
||||
<pre>
|
||||
function PhoneDetailCtrl($xhr) {
|
||||
var self = this;
|
||||
|
||||
$xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) {
|
||||
self.phone = response;
|
||||
});
|
||||
}
|
||||
|
||||
//PhoneDetailCtrl.$inject = ['$xhr'];
|
||||
</pre>
|
||||
|
||||
To construct the URL for the HTTP request, we use `params.phoneId` extracted from the current route
|
||||
in the `PhoneCatCtrl` controller.
|
||||
|
||||
|
||||
## 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 `ng:repeater`s to project phone data from
|
||||
our model into the view.
|
||||
|
||||
|
||||
__`app/partials/phone-details.html`:__
|
||||
<pre>
|
||||
<img ng:src="{{phone.images[0]}}" class="phone"/>
|
||||
|
||||
<h1>{{phone.name}}</h1>
|
||||
|
||||
<p>{{phone.description}}</p>
|
||||
|
||||
<ul class="phone-thumbs">
|
||||
<li ng:repeat="img in phone.images">
|
||||
<img ng:src="{{img}}"/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="specs">
|
||||
<li>
|
||||
<span>Availability and Networks</span>
|
||||
<dl>
|
||||
<dt>Availability</dt>
|
||||
<dd ng:repeat="availability in phone.availability">{{availability}}</dd>
|
||||
</dl>
|
||||
</li>
|
||||
...
|
||||
</li>
|
||||
<span>Additional Features</span>
|
||||
<dd>{{phone.additionalFeatures}}</dd>
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
|
||||
<img src="img/tutorial/tutorial_08-09_final.png">
|
||||
|
||||
## Test
|
||||
|
||||
We wrote a new unit test that is similar to the one we wrote for the `PhoneListCtrl` controller in
|
||||
step 5.
|
||||
|
||||
__`test/unit/controllerSpec.js`:__
|
||||
<pre>
|
||||
...
|
||||
it('should fetch phone detail', function() {
|
||||
scope.params = {phoneId:'xyz'};
|
||||
$browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
|
||||
ctrl = scope.$new(PhoneDetailCtrl);
|
||||
|
||||
expect(ctrl.phone).toBeUndefined();
|
||||
$browser.xhr.flush();
|
||||
|
||||
expect(ctrl.phone).toEqual({name:'phone xyz'});
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
|
||||
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
|
||||
output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
...
|
||||
Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (5.00 ms)
|
||||
Chrome 11.0.696.57 Mac OS: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms)
|
||||
|
||||
|
||||
We also added a new end-to-end test that navigates to the Nexus S detail page and verifies that the
|
||||
heading on the page is "Nexus S".
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
...
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../../app/index.html#/phones/nexus-s');
|
||||
});
|
||||
|
||||
|
||||
it('should display nexus-s page', function() {
|
||||
expect(binding('phone.name')).toBe('Nexus S');
|
||||
});
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
|
||||
|
||||
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
|
||||
can see them running on {@link
|
||||
http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html
|
||||
angular's server}.
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
Now that the phone details view is in place, proceed to {@link step_09 step 9} to learn how to
|
||||
write your own custom display filter.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="8"></ul>
|
||||
@@ -0,0 +1,121 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 9 - Filters
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="9"></ul>
|
||||
|
||||
|
||||
In this step you will learn how to create your own custom display filter.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="9"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
Navigate to one of the detail pages.
|
||||
|
||||
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 {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-8...step-9
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Custom Filter
|
||||
|
||||
In order to create a new filter, simply register your custom filter function with the {@link
|
||||
api/angular.filter `angular.filter`} API.
|
||||
|
||||
__`app/js/filters.js`:__
|
||||
<pre>
|
||||
angular.filter('checkmark', function(input) {
|
||||
return input ? '\u2713' : '\u2718';
|
||||
});
|
||||
</pre>
|
||||
|
||||
The name of our filter is "checkmark". The `input` evaluates to either `true` or `false`, and we
|
||||
return one of two unicode characters we have chosen to represent true or false (`\u2713` and
|
||||
`\u2718`).
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
Since the filter code lives in the `app/js/filters.js` file, we need to include this file in our
|
||||
layout template.
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<script src="js/controllers.js"></script>
|
||||
<script src="js/filters.js"></script>
|
||||
...
|
||||
</pre>
|
||||
|
||||
The syntax for using filters in angular templates is as follows:
|
||||
|
||||
{{ expression | filter }}
|
||||
|
||||
Let's employ the filter in the phone details template:
|
||||
|
||||
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
...
|
||||
<dl>
|
||||
<dt>Infrared</dt>
|
||||
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
|
||||
<dt>GPS</dt>
|
||||
<dd>{{phone.connectivity.gps | checkmark}}</dd>
|
||||
</dl>
|
||||
...
|
||||
</pre>
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
Filters, like any other component, should be tested and these tests are very easy to write.
|
||||
|
||||
__`test/unit/filtersSpec.js`:__
|
||||
<pre>
|
||||
describe('checkmark filter', function() {
|
||||
|
||||
it('should convert boolean values to unicode checkmark or cross', function() {
|
||||
expect(angular.filter.checkmark(true)).toBe('\u2713');
|
||||
expect(angular.filter.checkmark(false)).toBe('\u2718');
|
||||
});
|
||||
})
|
||||
</pre>
|
||||
|
||||
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
|
||||
output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
....
|
||||
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
|
||||
Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* Let's experiment with some of the {@link api/angular.filter built-in angular filters} and add the
|
||||
following bindings to `index.html`:
|
||||
* `{{ "lower cap string" | uppercase }}`
|
||||
* `{{ {foo: "bar", baz: 23} | json }}`
|
||||
* `{{ 1304375948024 | date }}`
|
||||
* `{{ 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:
|
||||
|
||||
<input ng:model="userInput"> Uppercased: {{ userInput | uppercase }}
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
Now that you have learned how to write and test a custom filter, go to {@link step_10 step 10} to
|
||||
learn how we can use angular to enhance the phone details page further.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="9"></ul>
|
||||
@@ -0,0 +1,140 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 10 - Event Handlers
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="10"></ul>
|
||||
|
||||
|
||||
In this step, you will add a clickable phone image swapper to the phone details page.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="10"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
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 {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-9...step-10
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
...
|
||||
function PhoneDetailCtrl($xhr) {
|
||||
var self = this;
|
||||
|
||||
$xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) {
|
||||
self.phone = response;
|
||||
self.mainImageUrl = response.images[0];
|
||||
});
|
||||
|
||||
self.setImage = function(imageUrl) {
|
||||
self.mainImageUrl = imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
//PhoneDetailCtrl.$inject = ['$xhr'];
|
||||
</pre>
|
||||
|
||||
In the `PhoneDetailCtrl` controller, we created the `mainImageUrl` model property and set its
|
||||
default value to the first phone image url.
|
||||
|
||||
We also created a `setImage` controller method to change the value of `mainImageUrl`.
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
<img ng:src="{{mainImageUrl}}" class="phone"/>
|
||||
|
||||
...
|
||||
|
||||
<ul class="phone-thumbs">
|
||||
<li ng:repeat="img in phone.images">
|
||||
<img ng:src="{{img}}" ng:click="setImage(img)">
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
|
||||
We bound the `ng:src` attribute of the large image to the `mainImageUrl` property.
|
||||
|
||||
We also registered an {@link api/angular.directive.ng:click `ng:click`} handler with thumbnail
|
||||
images. When a user clicks on one of the thumbnail images, the handler will use the `setImage`
|
||||
controller method to change the value of the `mainImageUrl` property to the url of the thumbnail
|
||||
image.
|
||||
|
||||
<img src="img/tutorial/tutorial_10-11_final.png">
|
||||
|
||||
## Test
|
||||
|
||||
To verify this new feature, we added two end-to-end tests. One verifies that the main image is set
|
||||
to the first phone image by default. The second test clicks on several thumbnail images and
|
||||
verifies that the main image changed appropriately.
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
...
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../../app/index.html#/phones/nexus-s');
|
||||
});
|
||||
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
|
||||
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('.phone-thumbs li:nth-child(1) img').click();
|
||||
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
|
||||
can see them running on {@link
|
||||
http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html
|
||||
angular's server}.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Let's add a new controller method to `PhoneCatCtrl`:
|
||||
|
||||
this.hello = function(name) {
|
||||
alert('Hello ' + (name || 'world') + '!');
|
||||
}
|
||||
|
||||
and add:
|
||||
|
||||
<button ng:click="hello('Elmo')">Hello</button>
|
||||
|
||||
to the `index.html` template.
|
||||
|
||||
The controller methods are inherited between controllers/scopes, so you can use the same snippet
|
||||
in the `phone-list.html` template as well.
|
||||
|
||||
* Move the `hello` method from `PhoneCatCtrl` to `PhoneListCtrl` and you'll see that the button
|
||||
declared in `index.html` will stop working, while the one declared in the `phone-list.html`
|
||||
template remains operational.
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
With the phone image swapper in place, we're ready for {@link step_11 step 11} (the last step!) to
|
||||
learn an even better way to fetch data.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="10"></ul>
|
||||
@@ -0,0 +1,208 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 11 - REST and Custom Services
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="11"></ul>
|
||||
|
||||
|
||||
In this step, you will improve the way our app fetches data.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="11"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
The last improvement we will make to our app is to define a custom service that represents a {@link
|
||||
http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client. Using this client we
|
||||
can make xhr requests for data in an easier way, without having to deal with the lower-level {@link
|
||||
api/angular.service.$xhr $xhr} API, HTTP methods and URLs.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-10...step-11
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
The custom service is defined in `app/js/services.js` so we need to include this file in our layout
|
||||
template:
|
||||
|
||||
__`app/index.html`.__
|
||||
<pre>
|
||||
...
|
||||
<script src="js/services.js"></script>
|
||||
...
|
||||
</pre>
|
||||
|
||||
## Service
|
||||
|
||||
__`app/js/services.js`.__
|
||||
<pre>
|
||||
angular.service('Phone', function($resource) {
|
||||
return $resource('phones/:phoneId.json', {}, {
|
||||
query: {method: 'GET', params: {phoneId: 'phones'}, isArray: true}
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
We used the {@link api/angular.service} API to register a custom service. We passed in the name of
|
||||
the service - 'Phone' - and a 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.
|
||||
|
||||
The {@link api/angular.service.$resource `$resource`} service makes it easy to create a {@link
|
||||
http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client with just a few lines
|
||||
of code. This client can then be used in our application, instead of the lower-level {@link
|
||||
api/angular.service.$xhr $xhr} service.
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
We simplified our sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`) by factoring out the
|
||||
lower-level {@link api/angular.service.$xhr $xhr} service, replacing it with a new service called
|
||||
`Phone`. Angular's {@link api/angular.service.$resource `$resource`} service is easier to use than
|
||||
{@link api/angular.service.$xhr $xhr} for interacting with data sources exposed as RESTful
|
||||
resources. It is also easier now to understand what the code in our controllers is doing.
|
||||
|
||||
__`app/js/controllers.js`.__
|
||||
<pre>
|
||||
...
|
||||
|
||||
function PhoneListCtrl(Phone) {
|
||||
this.orderProp = 'age';
|
||||
this.phones = Phone.query();
|
||||
}
|
||||
//PhoneListCtrl.$inject = ['Phone'];
|
||||
|
||||
|
||||
function PhoneDetailCtrl(Phone) {
|
||||
var self = this;
|
||||
|
||||
self.phone = Phone.get({phoneId: self.params.phoneId}, function(phone) {
|
||||
self.mainImageUrl = phone.images[0];
|
||||
});
|
||||
|
||||
...
|
||||
}
|
||||
//PhoneDetailCtrl.$inject = ['Phone'];
|
||||
</pre>
|
||||
|
||||
Notice how in `PhoneListCtrl` we replaced:
|
||||
|
||||
$xhr('GET', 'phones/phones.json', function(code, response) {
|
||||
self.phones = response;
|
||||
});
|
||||
|
||||
with:
|
||||
|
||||
this.phones = Phone.query();
|
||||
|
||||
This is a simple statement that we want to query for all phones.
|
||||
|
||||
An important thing to notice in the code above is that we don't pass any callback functions when
|
||||
invoking methods of our Phone service. Although it looks as if the result were returned
|
||||
synchronously, that is not the case at all. What is returned synchronously is a "future" — an
|
||||
object, which will be filled with data when the xhr response returns. Because of the data-binding
|
||||
in angular, we can use this future and bind it to our template. Then, when the data arrives, the
|
||||
view will automatically update.
|
||||
|
||||
Sometimes, relying on the future object and data-binding alone is not sufficient to do everything
|
||||
we require, so in these cases, we can add a callback to process the server response. The
|
||||
`PhoneDetailCtrl` controller illustrates this by setting the `mainImageUrl` in a callback.
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
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.
|
||||
|
||||
The {@link api/angular.service.$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` {@link
|
||||
http://pivotal.github.com/jasmine/jsdoc/symbols/jasmine.Matchers.html Jasmine matcher}. When the
|
||||
`toEqualData` matcher compares two objects, it takes only object properties into account and
|
||||
ignores methods.
|
||||
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
<pre>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.addMatchers({
|
||||
toEqualData: function(expected) {
|
||||
return angular.equals(this.actual, expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('PhoneListCtrl', function() {
|
||||
var scope, $browser, ctrl;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = angular.scope();
|
||||
$browser = scope.$service('$browser');
|
||||
|
||||
$browser.xhr.expectGET('phones/phones.json')
|
||||
.respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
|
||||
ctrl = scope.$new(PhoneListCtrl);
|
||||
});
|
||||
|
||||
it('should create "phones" model with 2 phones fetched from xhr', function() {
|
||||
expect(ctrl.phones).toEqual([]);
|
||||
$browser.xhr.flush();
|
||||
|
||||
expect(ctrl.phones).toEqualData([{name: 'Nexus S'},
|
||||
{name: 'Motorola DROID'}]);
|
||||
});
|
||||
|
||||
it('should set the default value of orderProp model', function() {
|
||||
expect(ctrl.orderProp).toBe('age');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('PhoneDetailCtrl', function() {
|
||||
var scope, $browser, ctrl;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = angular.scope();
|
||||
$browser = scope.$service('$browser');
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
scope = angular.scope();
|
||||
$browser = scope.$service('$browser');
|
||||
});
|
||||
|
||||
it('should fetch phone detail', function() {
|
||||
scope.params = {phoneId:'xyz'};
|
||||
$browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
|
||||
ctrl = scope.$new(PhoneDetailCtrl);
|
||||
|
||||
expect(ctrl.phone).toEqualData({});
|
||||
$browser.xhr.flush();
|
||||
|
||||
expect(ctrl.phone).toEqualData({name:'phone xyz'});
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
|
||||
output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
....
|
||||
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
|
||||
Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
There you have it! We have created a web app in a relatively short amount of time. In the {@link
|
||||
the_end closing notes} we'll cover were to go from here.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="11"></ul>
|
||||
@@ -0,0 +1,21 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: The End
|
||||
@description
|
||||
|
||||
Our application is now complete. Feel free to experiment with the code further, and jump back to
|
||||
previous steps using the `git checkout` or `goto_step.sh` commands.
|
||||
|
||||
For more details and examples of the angular concepts we touched on in this tutorial, see the
|
||||
{@link guide/ Developer Guide}.
|
||||
|
||||
For several more examples of code, see the {@link cookbook/ Cookbook}.
|
||||
|
||||
When you are ready to start developing a project using angular, we recommend that you bootstrap
|
||||
your development with the {@link https://github.com/angular/angular-seed angular seed} project.
|
||||
|
||||
We hope this tutorial was useful to you and that you learned enough about angular to make you want
|
||||
to learn more. We especially hope you are inspired to go out and develop angular web apps of your
|
||||
own, and that you might be interested in {@link misc/contribute contributing} to angular.
|
||||
|
||||
If you have questions or feedback or just want to say "hi", please post a message at {@link
|
||||
https://groups.google.com/forum/#!forum/angular}.
|
||||
@@ -0,0 +1,2 @@
|
||||
controller: {{name}}<br />
|
||||
Book Id: {{params.bookId}}<br />
|
||||
@@ -0,0 +1,3 @@
|
||||
controller: {{name}}<br />
|
||||
Book Id: {{prams.bookId}}<br />
|
||||
Chapter Id: {{params.chapterId}}
|
||||
@@ -0,0 +1 @@
|
||||
Content of template1.html
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user