Compare commits
969 Commits
2.0.0-rc.1
...
2.0.0-rc.5
Author | SHA1 | Date | |
---|---|---|---|
ebcd14f8e9 | |||
b65f66feff | |||
dd68ae3ef1 | |||
1b04d70626 | |||
01bca41168 | |||
94e1ab33ce | |||
0b08dd8674 | |||
b2b47177cd | |||
f08257ff4a | |||
d1f4222c83 | |||
d21331e902 | |||
37f138e83d | |||
5dab0bad3c | |||
85e70a4cde | |||
6b564ecda5 | |||
d4cceff0ef | |||
a415613457 | |||
1a41bd1ca4 | |||
5a99393355 | |||
afcb3c0035 | |||
d2d36c61f3 | |||
0bd97ecda2 | |||
46bbcefb36 | |||
74b57dfa7d | |||
8c9c0986e9 | |||
4028fcaa51 | |||
7a8ef1eae5 | |||
e811a5d97f | |||
df44e3e425 | |||
cdb1a237e5 | |||
fcafdff10b | |||
c586656d43 | |||
3a307c2794 | |||
4f17dbc721 | |||
99989f5d3f | |||
6baf3baedd | |||
0d1f3c3b07 | |||
83e2d3d1cb | |||
b4613ab2d2 | |||
0ca05eee45 | |||
26c9e1dc70 | |||
797cb5ae7b | |||
63b82cd730 | |||
2b704f0586 | |||
ce5ba80792 | |||
8b18ef4ba2 | |||
cd18de7a21 | |||
fd19671c07 | |||
3fcd6fd93f | |||
c8d53d71a3 | |||
790362e243 | |||
2eda7a5293 | |||
9925aa89dc | |||
6195a45ae2 | |||
422d380b3e | |||
550ab31bd0 | |||
5fceb21549 | |||
29caa37943 | |||
8efbcc996a | |||
91c64d2b8d | |||
82e7ecd611 | |||
3d53b33391 | |||
34624b2db2 | |||
1ab5eb0844 | |||
630028350a | |||
7e4fd7d7da | |||
af2e80e068 | |||
8e6091de6c | |||
ecdaded25f | |||
c161ed415d | |||
ff3b71f7b3 | |||
16cc9b46aa | |||
3ce11ed58c | |||
7db75fa361 | |||
d6d4568830 | |||
a55d796c4b | |||
73f02c7861 | |||
8c8754e573 | |||
c977a906b3 | |||
e0eea6c2f4 | |||
3e377f520e | |||
8dc82a0080 | |||
4624a35845 | |||
8d4499959a | |||
2dfc9c653b | |||
106db0aba8 | |||
28c4852cd6 | |||
13c8211065 | |||
e73d0511cf | |||
e18626b7a2 | |||
4df7b1cfbc | |||
f9573ece41 | |||
93ade740e2 | |||
a46437c57d | |||
633c7d1ebe | |||
251953218c | |||
0d6cc17252 | |||
c8989c900f | |||
6134320f16 | |||
7f647822bd | |||
e34a04d2ad | |||
44093905e2 | |||
3e2900f74b | |||
11fd2eccec | |||
0eee1d5de3 | |||
1b77604ee2 | |||
cc5cfe87c3 | |||
48f230a951 | |||
2be50bdbb0 | |||
f7258ea52a | |||
28e8b2faab | |||
3c3e9ddb10 | |||
5162fb6d52 | |||
2fdb39e60a | |||
43c71ae103 | |||
f0bd528d77 | |||
50a024b42f | |||
b48f7bcb8d | |||
763ca60f5b | |||
0eca7abdd8 | |||
d0a95e35af | |||
acc6c8d0b7 | |||
3dbc66c1ac | |||
4ad6bcce54 | |||
0988cc82b0 | |||
20b03bad11 | |||
81d27daf0d | |||
bb8b82b3f5 | |||
915a6666f8 | |||
72da547d6a | |||
7c76a75452 | |||
a32c4ad2f0 | |||
fb3608aa5d | |||
0a46f37444 | |||
9b39e499ac | |||
a67cc8229d | |||
b58e9ea775 | |||
422effdd18 | |||
3b690b68a6 | |||
43349dd373 | |||
e44e8668ea | |||
69e72c0786 | |||
553344739c | |||
367f0fd142 | |||
58d9e7fc5a | |||
9d9e9c6ff1 | |||
ba88db5141 | |||
62e7c0f464 | |||
fc83bbbe98 | |||
482c019199 | |||
b449467940 | |||
0aba42ae5b | |||
0d1bf8148b | |||
b42411ba1f | |||
5a21f168d6 | |||
00b726f695 | |||
f02da4e91a | |||
d6b65db9a7 | |||
6f4e49ed53 | |||
46b212706b | |||
ca16fc29a6 | |||
9edea0b139 | |||
d15a1d64e1 | |||
c87847974a | |||
6f68330fa5 | |||
2b63330a36 | |||
06e4ca4bb3 | |||
43437c175a | |||
8d90a5a4cf | |||
93a4ca652a | |||
41178367d1 | |||
54f2edbb90 | |||
b652a7fc9f | |||
e34eb4520f | |||
190bcc89c1 | |||
64fc4648b7 | |||
bdb59129d0 | |||
d455942389 | |||
cdb3678fe3 | |||
e73ac1e992 | |||
51f3d22e4f | |||
00aa7a76b6 | |||
27b87ef535 | |||
44709e0dca | |||
31a7709ece | |||
a441b5b8fe | |||
76b8a49bfb | |||
db54a84d14 | |||
eb6ff65af7 | |||
23ee29b6a2 | |||
73a69895d8 | |||
2799e7a3ca | |||
b43f95435b | |||
450f61d384 | |||
f3dd91e1d7 | |||
979946c062 | |||
51e661eb74 | |||
921a17960c | |||
7a4f6621ed | |||
83bc5c97ef | |||
3f08efa35d | |||
0914dc35e8 | |||
b6746cce9c | |||
8cd97c2054 | |||
32d8cde9c6 | |||
1803ed2512 | |||
f08060b0b0 | |||
b77a4a40a4 | |||
e1109d52e1 | |||
0668ba50e8 | |||
44ff005ce3 | |||
aa88438b54 | |||
85be729c70 | |||
a5dc5705a3 | |||
9e3d13f61f | |||
961c9d48ae | |||
9229bbbc80 | |||
34feecf60e | |||
4c762a6be3 | |||
0426325ef7 | |||
0b54e3cf0a | |||
5cf58971f1 | |||
6518ff88b2 | |||
42b0c1d8a2 | |||
4a965052f9 | |||
5725c5925c | |||
a46291b67c | |||
4ac76ca281 | |||
e7a8e2757b | |||
1266460386 | |||
0ccb6e0dfc | |||
3050ae155c | |||
402fd934d0 | |||
6c86e8d80a | |||
60e6f91a53 | |||
e676fded21 | |||
25e070dd65 | |||
da8eb9f8b8 | |||
806a25413c | |||
5af1e891cd | |||
79eda30f0f | |||
6d02d2f107 | |||
ded518d47f | |||
27436270fd | |||
b4ea0b1601 | |||
7b31178546 | |||
2ff83324af | |||
4ef86891a3 | |||
eb5763c23f | |||
d1a3e3aff1 | |||
93d0a01d3d | |||
9af2d8b810 | |||
94dc632a6d | |||
29231877e6 | |||
e68252a79b | |||
4ec2a30942 | |||
e6b24437a9 | |||
a05f7b2d76 | |||
61e18434d3 | |||
57473e72ec | |||
c3bdd504d0 | |||
daa9da4047 | |||
245b0910ed | |||
a77db44129 | |||
34b3c534e7 | |||
fa47890032 | |||
d84a43c828 | |||
9a1babb30c | |||
30a332ee36 | |||
426b002897 | |||
2de8364de2 | |||
eacc9e6541 | |||
749dec7dfb | |||
96a9e66616 | |||
46e105f3ab | |||
f7a0e9ecb6 | |||
93025d1bc6 | |||
98d49d4ce3 | |||
1426f680f5 | |||
c7fc51a185 | |||
b7e69bc1a1 | |||
72544ba551 | |||
c43dd5a655 | |||
7f4954bed6 | |||
f1fc1dc669 | |||
cbe85a0893 | |||
7073cf74fe | |||
9d265b6f61 | |||
776a83f9da | |||
f29457f3f0 | |||
a005d1595e | |||
8d746e3f67 | |||
37e6da6dfb | |||
8aa2a0c1b2 | |||
6bfd514caf | |||
ad3f18c0dd | |||
39d04b4a15 | |||
6fbe56dbf2 | |||
8ebb8e44c8 | |||
6fcf962fb5 | |||
2708ce6a17 | |||
30bec78da3 | |||
9a04fcd061 | |||
ae62f082fd | |||
9cc3b2ca9e | |||
3fe1cb0253 | |||
0ed7773223 | |||
3f55aa609f | |||
74b45dfbf8 | |||
5c9f871b21 | |||
77dc6ef411 | |||
5eca6e4e40 | |||
0c65d5cf2b | |||
f65ebec3ed | |||
81bf3f66ca | |||
3cbded6694 | |||
137fff9632 | |||
695c08b9dd | |||
119794249b | |||
afb72164e4 | |||
9fee5630fd | |||
01de58d650 | |||
dabf214f17 | |||
fb2539e1d5 | |||
ad9f02a73e | |||
2d73583253 | |||
73f017bad9 | |||
055282f156 | |||
fe7de53b89 | |||
17e4cfc748 | |||
1608d91728 | |||
a3b90411aa | |||
5781b96490 | |||
f208ee0d57 | |||
8aa388de6c | |||
51d4c9dcbd | |||
e81dea695c | |||
3fec27961e | |||
3784696b9e | |||
8c45aebc18 | |||
810c722413 | |||
e2116c53f3 | |||
296a447e3c | |||
0961bd1eff | |||
9340e1b065 | |||
ae4fa56ee9 | |||
2d9d7f1310 | |||
5ee84fe0f6 | |||
1620426393 | |||
bf598d6b8b | |||
24eb8389d2 | |||
fcfddbf79c | |||
dc64e90ab9 | |||
e12b1277df | |||
797914e948 | |||
e0b0a594bb | |||
ed0ade6f34 | |||
5cc7b41f39 | |||
f2f1ec0117 | |||
e913d9954d | |||
d20488752b | |||
855f3afb28 | |||
3f44377f2f | |||
90295e3252 | |||
a620f95891 | |||
db66509e66 | |||
6605eb30e9 | |||
3644eef860 | |||
fb2509675d | |||
eef9512ce6 | |||
9f00a1b902 | |||
c369bc747d | |||
c03e1f2f59 | |||
17dcbf66b9 | |||
40b907a657 | |||
a33195dcf3 | |||
c693c03f1d | |||
de127109f9 | |||
83208983b3 | |||
327d04c9c6 | |||
54edce2bab | |||
1a145ac500 | |||
9f978cf49d | |||
41b781107b | |||
dcf75126bf | |||
1143b0389a | |||
97a2119596 | |||
fbd2dd9ca2 | |||
f463e09b9f | |||
42a5b6cbda | |||
0ad1215a92 | |||
7733c97df3 | |||
8a9e9c7bd3 | |||
3d8eb8cbca | |||
894747c34c | |||
8d5a312585 | |||
8eb81b3741 | |||
22d8f73bc9 | |||
249a6bdd98 | |||
3ad81b1beb | |||
5ab0534164 | |||
5150344213 | |||
98cef76931 | |||
6c5b653593 | |||
9ed8f2d26e | |||
33a2f86b28 | |||
fed1672a43 | |||
54dbed4f48 | |||
df759b8d4b | |||
6edf0474cc | |||
826f89f862 | |||
c43aec2182 | |||
ae75e3640a | |||
a5f2cc73f6 | |||
e1e5c40ef7 | |||
6420f75320 | |||
5face35ae5 | |||
398060d5ff | |||
638fd744aa | |||
098b461b69 | |||
9decc3d823 | |||
a5f2e205ef | |||
8899b83927 | |||
f6a410a4a8 | |||
3d5bb23184 | |||
ef37d2ae0b | |||
2eb234bc63 | |||
758ee95880 | |||
40e1112a8e | |||
397f5e2390 | |||
1a212259af | |||
f114dd300b | |||
5954a26bce | |||
bdbbe5aa20 | |||
15911367a2 | |||
8dd3f59c81 | |||
c9d28492b7 | |||
d1f93072a8 | |||
92d8bf9619 | |||
bd2281e32d | |||
0c50bc6449 | |||
f164715678 | |||
2aa615b4ae | |||
42c89b1b9b | |||
f6b75f56ad | |||
280540e4a2 | |||
fea216db12 | |||
b260eb06f6 | |||
1c937a10f9 | |||
ca23b4c55f | |||
7e12208ca6 | |||
2773281338 | |||
f8e8d22e4e | |||
cf4a9236b9 | |||
4450e7b246 | |||
cdbf67ee05 | |||
9a67f38728 | |||
25560ed048 | |||
2aa19fd078 | |||
cca9a58ded | |||
d6a25325c7 | |||
a717da2d3e | |||
b3e801ed9e | |||
3683fb6886 | |||
3bd0ce291e | |||
523fc5536c | |||
f5efccfb44 | |||
b6ec22de6b | |||
15f27b5455 | |||
127401598b | |||
503b07f698 | |||
f0a6329005 | |||
2982892acc | |||
9de56481f1 | |||
777eb2f159 | |||
05eebe0fed | |||
fdfbbd5bac | |||
1f3f8ef6c8 | |||
820eeb49d1 | |||
2d4be1c9eb | |||
2fef30f619 | |||
10113b63b5 | |||
545caab433 | |||
3f90659cc1 | |||
131914ac94 | |||
29a7c4538c | |||
f195bb608c | |||
66caabca0c | |||
25c6a3715d | |||
97cf0e40d5 | |||
6988a550ea | |||
8a1cdc2dd5 | |||
dadd5ddded | |||
56f8c95ee9 | |||
ed50e17e5b | |||
33b518ad21 | |||
b0e7c14545 | |||
5742d4720a | |||
9b356d9b86 | |||
793ac3f6b4 | |||
9b094e42a3 | |||
6ce7a5a1ea | |||
88920bfee1 | |||
2717bcc3af | |||
5d386dc426 | |||
f34af4f249 | |||
f04b6978fb | |||
ab958598d7 | |||
1914847e72 | |||
d95f0fd83d | |||
243612e36d | |||
c5cca8e098 | |||
99f7404d8b | |||
9ff6b0828f | |||
6f052d1daf | |||
63c194b71f | |||
46911117f1 | |||
2de1030413 | |||
1f6ade894e | |||
8407cfeac7 | |||
91d64a2855 | |||
40a06af79b | |||
8aef86f4a0 | |||
5bdc6ecec8 | |||
c179b5033b | |||
86f47273bc | |||
2e1bd46bb1 | |||
a9e773b47b | |||
10d38cbb72 | |||
a5371bfb8a | |||
4b2740f270 | |||
5b371736b2 | |||
c9b4bcf689 | |||
013f9a2bbc | |||
5bf1c93ead | |||
4f6ec01932 | |||
0f79e504c9 | |||
1a4e911b8b | |||
1f98519380 | |||
aad7010952 | |||
1be9ea681b | |||
f259a2204b | |||
4b1db0e61c | |||
aee764d14d | |||
47585498af | |||
37c5320e33 | |||
01111a1122 | |||
0b2bb1b6f5 | |||
f57df3cf8a | |||
c9c81e1fbc | |||
e38e04c1c2 | |||
99587ea4ed | |||
58b18d7fe7 | |||
04a50f5832 | |||
e157a065b0 | |||
41ef4b3d4a | |||
262650ab39 | |||
8c076d5a73 | |||
c5c456120c | |||
fdf6bc18dd | |||
297f0fd2c3 | |||
86405345b7 | |||
12c49042ab | |||
ba46ca683b | |||
ca42b49fa2 | |||
1b28cf71f5 | |||
6fd52dfb38 | |||
af2f5c3d7d | |||
65be81baf8 | |||
7a3689f175 | |||
8675b8dc48 | |||
279e816ea7 | |||
2d60ff14ae | |||
dee1b774f2 | |||
c0f2a22a08 | |||
5c8d3154d7 | |||
40f8a45b95 | |||
76a418760e | |||
49bf3f5b3a | |||
773c34900f | |||
791153c93c | |||
721f53f0d6 | |||
7498050421 | |||
5e3ccbcea9 | |||
8879aa1df4 | |||
44e0ad4987 | |||
c449f325ba | |||
5fe60759f9 | |||
6c389ed32f | |||
45549cda61 | |||
a13052fc73 | |||
4e7bb03e81 | |||
935c39a7e2 | |||
e0c1c13004 | |||
c08ca22dba | |||
a3f6e19881 | |||
37b617dccf | |||
c60ef45bc8 | |||
2b1ac63e3a | |||
6686bc62f6 | |||
1eaa193c51 | |||
b620f4f456 | |||
e484c62a8d | |||
9ba400d7d5 | |||
9cbd8f7afc | |||
8e6e90e703 | |||
26676c4833 | |||
39e0b4903c | |||
54c577cfe0 | |||
27024915e4 | |||
80f3e7591e | |||
933f45ef31 | |||
4fc37aeabd | |||
2fd1e88199 | |||
17f317d31e | |||
5941c92a31 | |||
d44d0852e5 | |||
80deac5cde | |||
8a54c1a115 | |||
0dbff55bc6 | |||
22916bb5d1 | |||
fe01e2efb7 | |||
7afee97d1b | |||
6fc267f22c | |||
ac84468f1c | |||
387a90e546 | |||
6eeb9495d8 | |||
566b4ef481 | |||
a191e9697c | |||
5504ca1e38 | |||
5c0cfdee48 | |||
bc888bf3a1 | |||
1745366530 | |||
1fb0db4aeb | |||
61960c51a3 | |||
14a3ade662 | |||
de97687422 | |||
a0251305ea | |||
2b8d12ddf0 | |||
1f6fd3c8fc | |||
55860e1621 | |||
4d51158b1a | |||
7d9c1e1225 | |||
d53edfec47 | |||
a6e5ddc5af | |||
b866f32832 | |||
97833d48c1 | |||
9c0031f7a5 | |||
164a091c71 | |||
4ed6cf7519 | |||
5267115481 | |||
43148d8233 | |||
537e99b4ea | |||
e1fcab777c | |||
f39c9c9e75 | |||
bbed364e7b | |||
3aca5ff9e2 | |||
9146bb0816 | |||
0c9e8dbf60 | |||
b34a04d53a | |||
0658eb4429 | |||
e178ee4ba0 | |||
9f506cd330 | |||
729dc3b764 | |||
7ce0fc7d47 | |||
b60eecfc47 | |||
346304762e | |||
e213939f28 | |||
4c39eace52 | |||
86fbd50c3d | |||
87d824e1b4 | |||
50acb96130 | |||
29c2dcff61 | |||
cea103a7ff | |||
d18694a1c3 | |||
b746c64229 | |||
d38aa5e25f | |||
efdc2d5118 | |||
515a8e0765 | |||
45de65bd45 | |||
9d6b98794e | |||
7aa1790874 | |||
5cd490eba2 | |||
ac1156739d | |||
7cefec77ef | |||
36d25f2a07 | |||
c3d2459a4e | |||
8847580fd7 | |||
cf3548a02f | |||
c197e2bb42 | |||
d1c989b8a5 | |||
57c9a07fff | |||
a19c4e8f9a | |||
53083c0b52 | |||
994d9212c1 | |||
52ddc96c9f | |||
057abefe50 | |||
f0e24b1a1e | |||
a1e3004e62 | |||
e504d4eb05 | |||
a6ad61d83e | |||
27a47e7841 | |||
b00b9fe564 | |||
fa0718ba9a | |||
155b88213c | |||
35ea02fb81 | |||
ddd2ac4f55 | |||
6f281ab3c4 | |||
fe8a7b0e82 | |||
76e6214b9b | |||
2d8f776e38 | |||
cf2d3cf920 | |||
b160ada5d1 | |||
1090601e8b | |||
01dd7dde24 | |||
13c39a52c6 | |||
3b80ab51ba | |||
1a386a58c8 | |||
04220be8fd | |||
48bf349c3c | |||
21fc1bb655 | |||
d38d375fa6 | |||
602836800b | |||
2953ea10a7 | |||
a738d0d54d | |||
d781e69948 | |||
c9b71fb5e2 | |||
dd6cb233b5 | |||
a3cf58b67a | |||
70d944a59c | |||
a5a422f8e7 | |||
e93b3d2360 | |||
7bc2d9a93a | |||
1c929031a2 | |||
f2809d1ed8 | |||
f4f6b8721a | |||
7e352a27f7 | |||
0c6b16c208 | |||
10475b859d | |||
4d793c4eb8 | |||
3ae29c08ac | |||
3331321f64 | |||
c6064a30a1 | |||
040b101842 | |||
a78a43c816 | |||
ec198b0dc6 | |||
b5d14c26d2 | |||
2019050db2 | |||
5f999225ba | |||
307d105d2c | |||
0b6967fd74 | |||
ef0c32512c | |||
9096481744 | |||
5936624d11 | |||
83723671af | |||
4d825dd9fd | |||
8e38291156 | |||
f93512bf27 | |||
c3fafa0651 | |||
9036f78b74 | |||
263122ea5f | |||
324f0147f6 | |||
be48ba1b06 | |||
b2a7fd05cb | |||
84f859d7b2 | |||
cbc8d0adf8 | |||
420e83a396 | |||
89f61087c7 | |||
1dcb663917 | |||
b7b56785d1 | |||
3a62023260 | |||
fa2ce8100b | |||
172a5663ef | |||
f4b972815b | |||
0cb93a436d | |||
b2e804c961 | |||
85ce184197 | |||
29c77df4be | |||
c39e0463a3 | |||
c27bc1956b | |||
72707d80ab | |||
f18356307b | |||
00475f25c8 | |||
bab6023eee | |||
5e12a95789 | |||
e5904f4089 | |||
cf1122cf9e | |||
6da79673c2 | |||
352ee53202 | |||
a20639558b | |||
b9347eb01c | |||
4dbd8ed6b8 | |||
cb980d3e43 | |||
f154e2c6cf | |||
d0a64f9c86 | |||
16ef21d086 | |||
39ecd01b86 | |||
5e0f8cf3f0 | |||
6c6b316bd9 | |||
b49dac7be5 | |||
4c26397937 | |||
e26e4f922e | |||
2ab1085dfb | |||
acc3a2de83 | |||
12abdd8782 | |||
2bcdec5aaf | |||
16dfe3c63f | |||
b4a467e387 | |||
53628e19ac | |||
62dd3ceb64 | |||
d5066a9a0f | |||
32b37432b0 | |||
5f3d02bc7c | |||
97e94dd6e0 | |||
846c031ec9 | |||
92340350d2 | |||
d4827caa08 | |||
6ce13b68fa | |||
e82b700ad0 | |||
60a2ba87d4 | |||
83c19a1fbc | |||
1513e201bb | |||
a38c9a1ef7 | |||
d5f5ce82ca | |||
6dc88f5b61 | |||
7a2ce7ff21 | |||
1ac38bd69a | |||
982fad0c45 | |||
390cefac72 | |||
798bface7f | |||
276fec6e50 | |||
95af14b97c | |||
0f0a8ade7c | |||
e0c83f669e | |||
016f0d8e9e | |||
3e5716ec16 | |||
75e6dfb9ab | |||
9634e8d14a | |||
f95a604b59 | |||
3ff20cd7e3 | |||
17f73cb7bc | |||
fba0e2ff12 | |||
b62415c962 | |||
adc135e6c8 | |||
ceac045a7f | |||
4d6da7b1a7 | |||
01b9de7a15 | |||
0a872ffd38 | |||
6f3a6a55a0 | |||
0795dd307b | |||
49fb7ef421 | |||
cbeeff2bd6 | |||
ce013a3dd9 | |||
1f7449ccf4 | |||
c43636f2bb | |||
9c2fe660a3 | |||
abc266fa35 | |||
7d853dd9ad | |||
3ac2821a1b | |||
666dc75c15 | |||
e9332c66d2 | |||
830aecd1a7 | |||
ac6959c819 | |||
2bf21e1747 | |||
9105ab9596 | |||
165357bfa3 | |||
33c7f74cb9 | |||
cb84cbf545 | |||
b2e0946696 | |||
67c80fbb5e | |||
c574e6808f | |||
9175a049d3 | |||
0035575c82 | |||
7bfe8aa553 | |||
1bff47f97d | |||
e8e61de28d | |||
54f8308999 | |||
6c99746f0b | |||
4086b49046 | |||
0fad9c2786 | |||
d75f928fca | |||
68f9aaf214 | |||
efe4633b15 | |||
19e65382f7 | |||
5a9eda73b4 | |||
c6f2b3e96b | |||
55921be1af | |||
9b1b5b393d | |||
3857c8226e | |||
595bcdd1ac | |||
27c25bd0e8 | |||
aec95015f8 | |||
b2db6401cc | |||
ebe14720eb | |||
7112d008d0 | |||
90af4763d8 | |||
390046d7b3 | |||
587c119c75 | |||
3019140e7e | |||
7a80f0d1e1 | |||
0894318f50 | |||
f9fc524a74 | |||
9019c6f937 | |||
166b73f4f3 | |||
cb94111f18 | |||
a88b887a05 | |||
6e62217b78 | |||
a01a54c180 | |||
db8290632f | |||
7bb5167239 | |||
6cdc53c497 | |||
15ae710d22 | |||
dd50124254 | |||
ff36b0384a | |||
50c9bed630 | |||
8b1b427195 | |||
9a05ca95f6 | |||
05266241af | |||
4ddf5536b4 | |||
f389b5a961 | |||
bac1a6eab3 | |||
ff400726ca | |||
267d864976 | |||
97a1084c99 | |||
61b339678d | |||
d537a26297 | |||
d414734aac | |||
817ddfa847 | |||
c1154b30c7 | |||
0d71345b93 | |||
f235454dd6 | |||
6a80578d05 | |||
d33cd43db1 | |||
9e3df8eefe | |||
cf73ad7c8f | |||
3e68b7eb1f | |||
9fbafba993 | |||
7a524e3deb | |||
7b6c4d5acc | |||
99c0d503d7 | |||
f86edae9f3 | |||
df1b1f6957 | |||
9099160038 | |||
119abe7bb9 | |||
67ed2e2c0a | |||
6d36a7a45f | |||
e2b1e1577d | |||
b30ddfbfc5 | |||
abfb522f83 | |||
b8136cc26e | |||
d00b26d941 | |||
12637a761c | |||
1a0aea67a0 | |||
0f1465b899 | |||
c0cfd3c6ed | |||
a81923b793 | |||
7150ace7c7 | |||
bdce154282 | |||
5a84048f72 | |||
188bda813e | |||
29700aa188 | |||
3229bf1665 | |||
52595f52f9 | |||
edec158dd8 | |||
0297398f5e | |||
9485f5a813 | |||
8f8c017882 | |||
eba6e7946d | |||
3cfe281790 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -3,3 +3,6 @@
|
||||
|
||||
# JS files must always use LF for tools to work
|
||||
*.js eol=lf
|
||||
|
||||
# Must keep Windows line ending to be parsed correctly
|
||||
scripts/windows/packages.txt eol=crlf
|
||||
|
35
.github/ISSUE_TEMPLATE.md
vendored
35
.github/ISSUE_TEMPLATE.md
vendored
@ -1,11 +1,9 @@
|
||||
**IMPORTANT**: This repository's issues are reserved for feature requests and bug reports. Do not submit support requests here, see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question.
|
||||
|
||||
|
||||
**Steps to reproduce and a minimal demo of the problem**
|
||||
|
||||
_Use https://plnkr.co or similar -- try this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5_
|
||||
|
||||
_What steps should we try in your demo to see the problem?_
|
||||
**I'm submitting a ...** (check one with "x")
|
||||
```
|
||||
[ ] bug report
|
||||
[ ] feature request
|
||||
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
|
||||
```
|
||||
|
||||
**Current behavior**
|
||||
|
||||
@ -13,4 +11,23 @@ _What steps should we try in your demo to see the problem?_
|
||||
**Expected/desired behavior**
|
||||
|
||||
|
||||
**Other information**
|
||||
**Reproduction of the problem**
|
||||
If the current behavior is a bug or you can illustrate your feature request better with an example, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
|
||||
|
||||
|
||||
|
||||
**What is the expected behavior?**
|
||||
|
||||
|
||||
|
||||
**What is the motivation / use case for changing the behavior?**
|
||||
|
||||
|
||||
|
||||
**Please tell us about your environment:**
|
||||
|
||||
* **Angular version:** 2.0.0-rc.X
|
||||
|
||||
* **Browser:** [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
|
||||
|
||||
* **Language:** [all | TypeScript X.X | ES6/7 | ES5]
|
||||
|
32
.github/PULL_REQUEST_TEMPLATE.md
vendored
32
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,24 +1,36 @@
|
||||
* **Please check if the PR fulfills these requirements**
|
||||
**Please check if the PR fulfills these requirements**
|
||||
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-format
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
|
||||
|
||||
* **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
|
||||
**What kind of change does this PR introduce?** (check one with "x")
|
||||
```
|
||||
[ ] Bugfix
|
||||
[ ] Feature
|
||||
[ ] Code style update (formatting, local variables)
|
||||
[ ] Refactoring (no functional changes, no api changes)
|
||||
[ ] Build related changes
|
||||
[ ] CI related changes
|
||||
[ ] Other... Please describe:
|
||||
```
|
||||
|
||||
**What is the current behavior?** (You can also link to an open issue here)
|
||||
|
||||
|
||||
|
||||
* **What is the current behavior?** (You can also link to an open issue here)
|
||||
**What is the new behavior?**
|
||||
|
||||
|
||||
|
||||
* **What is the new behavior (if this is a feature change)?**
|
||||
**Does this PR introduce a breaking change?** (check one with "x")
|
||||
```
|
||||
[ ] Yes
|
||||
[ ] No
|
||||
```
|
||||
|
||||
If this PR contains a breaking change, please describe the impact and migration path for existing applications: ...
|
||||
|
||||
|
||||
|
||||
* **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?)
|
||||
|
||||
|
||||
|
||||
* **Other information**:
|
||||
**Other information**:
|
||||
|
||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -21,16 +21,6 @@ tmp
|
||||
*.js.deps
|
||||
*.js.map
|
||||
|
||||
# Files created by the template compiler
|
||||
**/*.ngfactory.ts
|
||||
**/*.css.ts
|
||||
**/*.css.shim.ts
|
||||
|
||||
# Or type definitions we mirror from github
|
||||
# (NB: these lines are removed in publish-build-artifacts.sh)
|
||||
**/typings/**/*.d.ts
|
||||
**/typings/tsd.cached.json
|
||||
|
||||
# Include when developing application packages.
|
||||
pubspec.lock
|
||||
.c9
|
||||
|
14
.travis.yml
14
.travis.yml
@ -34,18 +34,22 @@ env:
|
||||
# - LOGS_DIR=/tmp/angular-build/logs
|
||||
# - ARCH=linux-x64
|
||||
|
||||
# GITHUB_TOKEN_ANGULAR
|
||||
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
|
||||
- secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
|
||||
matrix:
|
||||
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
|
||||
- CI_MODE=js
|
||||
- CI_MODE=lint
|
||||
- CI_MODE=e2e
|
||||
- CI_MODE=saucelabs_required
|
||||
- CI_MODE=browserstack_required
|
||||
- CI_MODE=saucelabs_optional
|
||||
- CI_MODE=browserstack_optional
|
||||
|
||||
#matrix:
|
||||
# allow_failures:
|
||||
# - env: "MODE=saucelabs_optional"
|
||||
# - env: "MODE=browserstack_optional"
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "CI_MODE=saucelabs_optional"
|
||||
- env: "CI_MODE=browserstack_optional"
|
||||
|
||||
|
||||
install:
|
||||
|
1037
CHANGELOG.md
1037
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -95,7 +95,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
* In GitHub, send a pull request to `angular:master`.
|
||||
* If we suggest changes then:
|
||||
* Make the required updates.
|
||||
* Re-run the Angular 2 test suites for JS and Dart to ensure tests are still passing.
|
||||
* Re-run the Angular 2 test suites to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```shell
|
||||
@ -166,6 +166,19 @@ The **header** is mandatory and the **scope** of the header is optional.
|
||||
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
|
||||
to read on GitHub as well as in various git tools.
|
||||
|
||||
Footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
|
||||
|
||||
Samples: (even more [samples](https://github.com/angular/angular/commits/master))
|
||||
|
||||
```
|
||||
docs(changelog): update change log to beta.5
|
||||
```
|
||||
```
|
||||
fix(release): need to depend on latest rxjs and zone.js
|
||||
|
||||
The version in our package.json gets copied to the one we publish, and users need the latest of these.
|
||||
```
|
||||
|
||||
### Revert
|
||||
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
|
||||
|
||||
@ -225,8 +238,8 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
|
||||
[github]: https://github.com/angular/angular
|
||||
[gitter]: https://gitter.im/angular/angular
|
||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||
[js-style-guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
|
||||
[jsfiddle]: http://jsfiddle.net/
|
||||
[js-style-guide]: https://google.github.io/styleguide/javascriptguide.xml
|
||||
[jsfiddle]: http://jsfiddle.net
|
||||
[plunker]: http://plnkr.co/edit
|
||||
[runnable]: http://runnable.com/
|
||||
[runnable]: http://runnable.com
|
||||
[stackoverflow]: http://stackoverflow.com/questions/tagged/angular
|
||||
|
308
DEVELOPER.md
308
DEVELOPER.md
@ -5,15 +5,9 @@ JS and Dart versions. It also explains the basic mechanics of using `git`, `node
|
||||
|
||||
* [Prerequisite Software](#prerequisite-software)
|
||||
* [Getting the Sources](#getting-the-sources)
|
||||
* [Environment Variable Setup](#environment-variable-setup)
|
||||
* [Installing NPM Modules and Dart Packages](#installing-npm-modules-and-dart-packages)
|
||||
* [Build commands](#build-commands)
|
||||
* [Installing NPM Modules](#installing-npm-modules)
|
||||
* [Building](#building)
|
||||
* [Running Tests Locally](#running-tests-locally)
|
||||
* [Code Style](#code-style)
|
||||
* [Project Information](#project-information)
|
||||
* [CI using Travis](#ci-using-travis)
|
||||
* [Transforming Dart code](#transforming-dart-code)
|
||||
* [Debugging](#debugging)
|
||||
|
||||
See the [contribution guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md)
|
||||
if you'd like to contribute to Angular.
|
||||
@ -32,17 +26,8 @@ following products on your development machine:
|
||||
(version `>=3.5.3 <4.0`), which comes with Node. Depending on your system, you can install Node either from
|
||||
source or as a pre-packaged bundle.
|
||||
|
||||
* *Optional*: [Dart](https://www.dartlang.org) (version ` >=1.13.2 <2.0.0`), specifically the Dart-SDK and
|
||||
Dartium (a version of [Chromium](http://www.chromium.org) with native support for Dart through
|
||||
the Dart VM). One of the **simplest** ways to get both is to install the **Dart Editor bundle**,
|
||||
which includes the editor, SDK and Dartium. See the [Dart tools](https://www.dartlang.org/tools)
|
||||
download [page for instructions](https://www.dartlang.org/tools/download.html).
|
||||
You can also download both **stable** and **dev** channel versions from the [download
|
||||
archive](https://www.dartlang.org/tools/download-archive). In that case, on Windows, Dart must be added
|
||||
to the `Path` (e.g. `path-to-dart-sdk-folder\bin`) and a new `DARTIUM_BIN` environment variable must be
|
||||
created, pointing to the executable (e.g. `path-to-dartium-folder\chrome.exe).`
|
||||
|
||||
|
||||
* [Java Development Kit](http://www.oracle.com/technetwork/es/java/javase/downloads/index.html) which is used
|
||||
to execute the selenium standalone server for e2e testing.
|
||||
|
||||
## Getting the Sources
|
||||
|
||||
@ -65,42 +50,7 @@ cd angular
|
||||
# Add the main Angular repository as an upstream remote to your repository:
|
||||
git remote add upstream https://github.com/angular/angular.git
|
||||
```
|
||||
|
||||
## Environment Variable Setup
|
||||
|
||||
Define the environment variables listed below. These are mainly needed for the testing. The
|
||||
notation shown here is for [`bash`](http://www.gnu.org/software/bash); adapt as appropriate for
|
||||
your favorite shell.
|
||||
|
||||
Examples given below of possible values for initializing the environment variables assume **Mac OS
|
||||
X** and that you have installed the Dart Editor in the directory named by
|
||||
`DART_EDITOR_DIR=/Applications/dart`. This is only for illustrative purposes.
|
||||
|
||||
```shell
|
||||
# DARTIUM_BIN: path to a Dartium browser executable; used by Karma to run Dart tests
|
||||
export DARTIUM_BIN="$DART_EDITOR_DIR/chromium/Chromium.app/Contents/MacOS/Chromium"
|
||||
```
|
||||
|
||||
Add the Dart SDK `bin` directory to your path and/or define `DART_SDK` (this is also detailed
|
||||
[here](https://www.dartlang.org/tools/pub/installing.html)):
|
||||
|
||||
```shell
|
||||
# DART_SDK: path to a Dart SDK directory
|
||||
export DART_SDK="$DART_EDITOR_DIR/dart-sdk"
|
||||
|
||||
# Update PATH to include the Dart SDK bin directory
|
||||
PATH+=":$DART_SDK/bin"
|
||||
```
|
||||
|
||||
And specify where the pub’s dependencies are downloaded. By default, this directory is located under .pub_cache
|
||||
in your home directory (on Mac and Linux), or in AppData\Roaming\Pub\Cache (on Windows).
|
||||
|
||||
```shell
|
||||
# PUB_CACHE: location of pub dependencies
|
||||
export PUB_CACHE="/Users/<user>/.pub-cache"
|
||||
```
|
||||
|
||||
## Installing NPM Modules and Dart Packages
|
||||
## Installing NPM Modules
|
||||
|
||||
Next, install the JavaScript modules and Dart packages needed to build and test Angular:
|
||||
|
||||
@ -124,239 +74,67 @@ use in these instructions.
|
||||
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
||||
|
||||
## Build commands
|
||||
|
||||
To build Angular and prepare tests, run:
|
||||
## Windows only
|
||||
|
||||
In order to create the right symlinks, run **as administrator**:
|
||||
```shell
|
||||
$(npm bin)/gulp build
|
||||
./scripts/windows/create-symlinks.sh
|
||||
```
|
||||
|
||||
Notes:
|
||||
* Results are put in the `dist` folder.
|
||||
* This will also run `pub get` for the subfolders in `modules` and run `dartanalyzer` for
|
||||
every file that matches `<module>/src/<module>.dart`, e.g. `di/src/di.dart`.
|
||||
Before submitting a PR, do not forget to remove them:
|
||||
```shell
|
||||
./scripts/windows/remove-symlinks.sh
|
||||
```
|
||||
|
||||
You can selectively build either the JS or Dart versions as follows:
|
||||
## Building
|
||||
|
||||
* `$(npm bin)/gulp build.js`
|
||||
* `$(npm bin)/gulp build.dart`
|
||||
|
||||
To clean out the `dist` folder, run:
|
||||
To build Angular run:
|
||||
|
||||
```shell
|
||||
$(npm bin)/gulp clean
|
||||
./build.sh
|
||||
```
|
||||
|
||||
* Results are put in the dist folder.
|
||||
|
||||
## Running Tests Locally
|
||||
|
||||
### Full test suite
|
||||
|
||||
* `npm test`: full test suite for both JS and Dart versions of Angular. These are the same tests
|
||||
that run on Travis.
|
||||
|
||||
You can selectively run either the JS or Dart versions as follows:
|
||||
|
||||
* `$(npm bin)/gulp test.all.js`
|
||||
* `$(npm bin)/gulp test.all.dart`
|
||||
|
||||
### Unit tests
|
||||
|
||||
You can run just the unit tests as follows:
|
||||
|
||||
* `$(npm bin)/gulp test.unit.js`: JS tests in a browser; runs in **watch mode** (i.e.
|
||||
watches the test files for changes and re-runs tests when files are updated).
|
||||
* `$(npm bin)/gulp test.unit.cjs`: JS tests in NodeJS; runs in **watch mode**.
|
||||
* `$(npm bin)/gulp test.unit.dart`: Dart tests in Dartium; runs in **watch mode**.
|
||||
|
||||
If you prefer running tests in "single-run" mode rather than watch mode use:
|
||||
|
||||
* `$(npm bin)/gulp test.unit.js/ci`
|
||||
* `$(npm bin)/gulp test.unit.cjs/ci`
|
||||
* `$(npm bin)/gulp test.unit.dart/ci`
|
||||
|
||||
The task updates the dist folder with transpiled code whenever a source or test file changes, and
|
||||
Karma is run against the new output.
|
||||
|
||||
**Note**: If you want to only run a single test you can alter the test you wish to run by changing
|
||||
`it` to `iit` or `describe` to `ddescribe`. This will only run that individual test and make it
|
||||
much easier to debug. `xit` and `xdescribe` can also be useful to exclude a test and a group of
|
||||
tests respectively.
|
||||
|
||||
**Note**: **watch mode** needs symlinks to work, so if you're using windows, ensure you have the
|
||||
rights to built them in your operating system.
|
||||
|
||||
### Unit tests with Sauce Labs or Browser Stack
|
||||
|
||||
First, in a terminal, create a tunnel with [Sauce Connect](https://docs.saucelabs.com/reference/sauce-connect/) or [Browser Stack Local](https://www.browserstack.com/local-testing#command-line), and valid credentials.
|
||||
|
||||
Then, in another terminal:
|
||||
- Define the credentials as environment variables, e.g.:
|
||||
```
|
||||
export SAUCE_USERNAME='my_user'; export SAUCE_ACCESS_KEY='my_key';
|
||||
export BROWSER_STACK_USERNAME='my_user'; export BROWSER_STACK_ACCESS_KEY='my_key';
|
||||
```
|
||||
- Then run `gulp test.unit.js.(sauce|browserstack) --browsers=option1,option2,..,optionN`
|
||||
The options are any mix of browsers and aliases which are defined in the [browser-providers.conf.js](https://github.com/angular/angular/blob/master/browser-providers.conf.js) file.
|
||||
They are case insensitive, and the `SL_` or `BS_` prefix must not be added for browsers.
|
||||
|
||||
Some examples of commands:
|
||||
```
|
||||
gulp test.unit.js.sauce --browsers=Safari8,ie11 //run in Sauce Labs with Safari 8 and IE11
|
||||
gulp test.unit.js.browserstack --browsers=Safari,IE //run in Browser Stack with Safari 7, Safari 8, Safari 9, IE 9, IE 10 and IE 11
|
||||
gulp test.unit.js.sauce --browsers=IOS,safari8,android5.1 //run in Sauce Labs with iOS 7, iOS 8, iOs 9, Safari 8 and Android 5.1
|
||||
```
|
||||
|
||||
### E2E tests
|
||||
|
||||
1. `$(npm bin)/gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder).
|
||||
2. `$(npm bin)/gulp serve.js.prod serve.dart` (runs a local webserver).
|
||||
3. `$(npm bin)/protractor protractor-js.conf.js`: JS e2e tests.
|
||||
4. `$(npm bin)/protractor protractor-dart2js.conf.js`: dart2js e2e tests.
|
||||
|
||||
Angular specific command line options when running protractor:
|
||||
- `$(npm bin)/protractor protractor-{js|dart2js}-conf.js --ng-help`
|
||||
|
||||
### Performance tests
|
||||
|
||||
1. `$(npm bin)/gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder)
|
||||
2. `$(npm bin)/gulp serve.js.prod serve.dart` (runs a local webserver)
|
||||
3. `$(npm bin)/protractor protractor-js.conf.js --benchmark`: JS performance tests
|
||||
4. `$(npm bin)/protractor protractor-dart2js.conf.js --benchmark`: dart2js performance tests
|
||||
|
||||
Angular specific command line options when running protractor (e.g. force gc, ...):
|
||||
`$(npm bin)/protractor protractor-{js|dart2js}-conf.js --ng-help`
|
||||
|
||||
## Code Style
|
||||
|
||||
### Formatting with <a name="clang-format">clang-format</a>
|
||||
|
||||
We use [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to automatically enforce code
|
||||
style for our TypeScript code. This allows us to focus our code reviews more on the content, and
|
||||
less on style nit-picking. It also lets us encode our style guide in the `.clang-format` file in the
|
||||
repository, allowing many tools and editors to share our settings.
|
||||
|
||||
To check the formatting of your code, run
|
||||
|
||||
gulp check-format
|
||||
|
||||
Note that the continuous build on Travis runs `gulp enforce-format`. Unlike the `check-format` task,
|
||||
this will actually fail the build if files aren't formatted according to the style guide.
|
||||
|
||||
Your life will be easier if you include the formatter in your standard workflow. Otherwise, you'll
|
||||
likely forget to check the formatting, and waste time waiting for a build on Travis that fails due
|
||||
to some whitespace difference.
|
||||
|
||||
* Use `$(npm bin)/clang-format -i [file name]` to format a file (or multiple).
|
||||
* Use `gulp enforce-format` to check if your code is `clang-format` clean. This also gives
|
||||
you a command line to format your code.
|
||||
* `clang-format` also includes a git hook, run `git clang-format` to format all files you
|
||||
touched.
|
||||
* You can run this as a **git pre-commit hook** to automatically format your delta regions when you
|
||||
commit a change. In the angular repo, run
|
||||
|
||||
```
|
||||
$ echo -e '#!/bin/sh\nexec git clang-format' > .git/hooks/pre-commit
|
||||
$ chmod u+x !$
|
||||
```
|
||||
|
||||
* **WebStorm** can run clang-format on the current file.
|
||||
1. Under Preferences, open Tools > External Tools.
|
||||
1. Plus icon to Create Tool
|
||||
1. Fill in the form:
|
||||
- Name: clang-format
|
||||
- Description: Format
|
||||
- Synchronize files after execution: checked
|
||||
- Open console: not checked
|
||||
- Show in: Editor menu
|
||||
- Program: `$ProjectFileDir$/node_modules/.bin/clang-format`
|
||||
- Parameters: `-i -style=file $FilePath$`
|
||||
- Working directory: `$ProjectFileDir$`
|
||||
* `clang-format` integrations are also available for many popular editors (`vim`, `emacs`,
|
||||
`Sublime Text`, etc.).
|
||||
|
||||
### Linting
|
||||
|
||||
We use [tslint](https://github.com/palantir/tslint) for linting. See linting rules in [gulpfile](gulpfile.js). To lint, run
|
||||
To run tests:
|
||||
|
||||
```shell
|
||||
$ gulp lint
|
||||
$ ./test.sh node # Run all angular tests on node
|
||||
|
||||
$ ./test.sh browser # Run all angular tests in browser
|
||||
$ ./test.sh browserNoRouter # Optionally run all angular tests without router in browser
|
||||
|
||||
$ ./test.sh tools # Run angular tooling (not framework) tests
|
||||
```
|
||||
|
||||
## Generating the API documentation
|
||||
You should execute the 3 test suites before submitting a PR to github.
|
||||
|
||||
The following gulp task will generate the API docs in the `dist/angular.io/partials/api/angular2`:
|
||||
All the tests are executed on our Continuous Integration infrastructure and a PR could only be merged once the tests pass.
|
||||
|
||||
```shell
|
||||
$(npm bin)/gulp docs/angular.io
|
||||
- CircleCI fails if your code is not formatted properly,
|
||||
- Travis CI fails if any of the test suite describe above fails.
|
||||
|
||||
## Update the public API tests
|
||||
|
||||
If you happen to modify the public API of Angular, API golden files must be updated using:
|
||||
|
||||
``` shell
|
||||
$ gulp public-api:update
|
||||
```
|
||||
|
||||
You can serve the generated documentation to check how it would render on [angular.io](https://angular.io/):
|
||||
- check out the [angular.io repo](https://github.com/angular/angular.io) locally,
|
||||
- install dependencies as described in the [angular.io README](https://github.com/angular/angular.io/blob/master/README.md),
|
||||
- copy the generated documentation from your local angular repo at `angular/dist/angular.io/partials/api/angular2` to your local angular.io repo at `angular.io/public/docs/js/latest/api`,
|
||||
- run `harp compile` at the root of the angular.io repo to check the generated documentation for errors,
|
||||
- run `harp server` and open a browser at `http://localhost:9000/docs/js/latest/api/` to check the rendered documentation.
|
||||
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
|
||||
|
||||
## Project Information
|
||||
## Formatting your source code
|
||||
|
||||
### Folder structure
|
||||
Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code
|
||||
is not properly formatted, the CI will fail and the PR can not be merged.
|
||||
|
||||
* `modules/*`: modules that will be loaded in the browser
|
||||
* `tools/*`: tools that are needed to build Angular
|
||||
* `dist/*`: build files are placed here.
|
||||
You can automatically format your code by running:
|
||||
|
||||
### File suffixes
|
||||
``` shell
|
||||
$ gulp format
|
||||
```
|
||||
|
||||
* `*.ts`: TypeScript files that get transpiled to Dart and EcmaScript 5/6
|
||||
* `*.dart`: Dart files that don't get transpiled
|
||||
|
||||
## CI using Travis
|
||||
|
||||
For instructions on setting up Continuous Integration using Travis, see the instructions given
|
||||
[here](https://github.com/angular/angular.dart/blob/master/travis.md).
|
||||
|
||||
## Transforming Dart code
|
||||
|
||||
See the [wiki](//github.com/angular/angular/wiki/Angular-2-Dart-Transformer).
|
||||
|
||||
## Debugging
|
||||
|
||||
### Debug the transpiler
|
||||
|
||||
If you need to debug the transpiler:
|
||||
|
||||
- add a `debugger;` statement in the transpiler code,
|
||||
- from the root folder, execute `node debug $(npm bin)/gulp build` to enter the node
|
||||
debugger
|
||||
- press "c" to execute the program until you reach the `debugger;` statement,
|
||||
- you can then type "repl" to enter the REPL and inspect variables in the context.
|
||||
|
||||
See the [Node.js manual](http://nodejs.org/api/debugger.html) for more information.
|
||||
|
||||
Notes:
|
||||
- You can also execute `node $(npm bin)/karma start karma-dart.conf.js` depending on which
|
||||
code you want to debug (the former will process the "modules" folder while the later processes
|
||||
the transpiler specs).
|
||||
- You can also add `debugger;` statements in the specs (JavaScript). The execution will halt when
|
||||
the developer tools are opened in the browser running Karma.
|
||||
|
||||
### Debug the tests
|
||||
|
||||
If you need to debug the tests:
|
||||
|
||||
- add a `debugger;` statement to the test you want to debug (or the source code),
|
||||
- execute karma `$(npm bin)/gulp test.js`,
|
||||
- press the top right "DEBUG" button,
|
||||
- open the DevTools and press F5,
|
||||
- the execution halts at the `debugger;` statement
|
||||
|
||||
**Note (WebStorm users)**:
|
||||
|
||||
1. Create a Karma run config from WebStorm.
|
||||
2. Then in the "Run" menu, press "Debug 'karma-js.conf.js'", and WebStorm will stop in the generated
|
||||
code on the `debugger;` statement.
|
||||
3. You can then step into the code and add watches.
|
||||
|
||||
The `debugger;` statement is needed because WebStorm will stop in a transpiled file. Breakpoints in
|
||||
the original source files are not supported at the moment.
|
||||
|
14
README.md
14
README.md
@ -1,8 +1,9 @@
|
||||
[](https://travis-ci.org/angular/angular)
|
||||
[](https://circleci.com/gh/angular/angular/tree/master)
|
||||
[](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](http://issuestats.com/github/angular/angular)
|
||||
[](http://issuestats.com/github/angular/angular)
|
||||
[](http://badge.fury.io/js/angular2)
|
||||
[](http://issuestats.com/github/angular/angular)
|
||||
[](http://issuestats.com/github/angular/angular)
|
||||
[](https://badge.fury.io/js/%40angular%2Fcore)
|
||||
[](https://npmjs.org/package/angular2)
|
||||
|
||||
[](https://saucelabs.com/u/angular2-ci)
|
||||
@ -11,9 +12,11 @@ Angular
|
||||
=========
|
||||
|
||||
Angular is a development platform for building mobile and desktop web applications. This is the
|
||||
repository for [Angular 2][ng2], both the JavaScript (JS) and [Dart][dart] versions.
|
||||
repository for [Angular 2][ng2] Typescript/JavaScript (JS).
|
||||
|
||||
Angular2 for [Dart][dart] can be found at [dart-lang/angular2][ng2dart].
|
||||
|
||||
Angular 2 is currently in **Beta**.
|
||||
Angular 2 is currently in **Release Candidate**.
|
||||
|
||||
## Quickstart
|
||||
|
||||
@ -33,3 +36,4 @@ guidelines for [contributing][contributing] and then check out one of our issues
|
||||
[ng2]: http://angular.io
|
||||
[ngDart]: http://angulardart.org
|
||||
[ngJS]: http://angularjs.org
|
||||
[ng2dart]: https://github.com/dart-lang/angular2
|
||||
|
376
TOOLS_DART.md
376
TOOLS_DART.md
@ -1,376 +0,0 @@
|
||||
# Developer Tools for Dart
|
||||
|
||||
Use these tools and techniques to increase your app's performance
|
||||
and reliability.
|
||||
|
||||
* [Angular debugging tools](#angular-debugging-tools)
|
||||
* [Code size](#code-size)
|
||||
* [Performance](#performance)
|
||||
|
||||
|
||||
## Angular debugging tools
|
||||
|
||||
Starting with alpha.38, Angular provides a set of debugging tools
|
||||
that are accessible from any browser's developer console.
|
||||
In Chrome, you can get to the dev console by pressing
|
||||
Ctrl + Shift + J (on Mac: Cmd + Opt + J).
|
||||
|
||||
### Enabling the debugging tools
|
||||
|
||||
By default the debugging tools are disabled.
|
||||
Enable the debugging tools as follows:
|
||||
|
||||
```dart
|
||||
import 'package:angular2/platform/browser.dart';
|
||||
|
||||
main() async {
|
||||
var appRef = await bootstrap(Application);
|
||||
enableDebugTools(appRef);
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Change function name to enableDebuggingTools? -->
|
||||
|
||||
|
||||
### Using the debugging tools
|
||||
|
||||
In the browser, open the dev console. The top-level object is called `ng` and
|
||||
contains more specific tools inside it.
|
||||
|
||||
For example, to run the change detection profiler on your app:
|
||||
|
||||
```javascript
|
||||
// In the dev console:
|
||||
ng.profiler.timeChangeDetection();
|
||||
```
|
||||
|
||||
The [Change detection profiler](#change-detection-profiler) section
|
||||
has more details.
|
||||
<!-- Point to API docs when they're published, if they're useful.
|
||||
They should be under
|
||||
http://www.dartdocs.org/documentation/angular2/latest
|
||||
and/or
|
||||
https://angular.io/docs/js/latest/api/. -->
|
||||
|
||||
|
||||
## Code size
|
||||
|
||||
Code must be downloaded, parsed, and executed. Too much code can lead to
|
||||
slow application start-up time, especially on slow networks and low-end devices.
|
||||
The tools and techniques in this section can help you to identify
|
||||
unnecessarily large code and to reduce code size.
|
||||
|
||||
### Finding contributors to code size
|
||||
|
||||
Options for investigating code size include the `--dump-info` dart2js option,
|
||||
ng2soyc, `reflector.trackUsage()`, and code coverage information
|
||||
from the Dart VM.
|
||||
|
||||
#### Use --dump-info
|
||||
|
||||
The `--dump-info` option of `dart2js` outputs information about what happened
|
||||
during compilation. You can specify `--dump-info` in `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
transformers:
|
||||
...
|
||||
- $dart2js:
|
||||
commandLineOptions:
|
||||
- --dump-info
|
||||
```
|
||||
|
||||
The [Dump Info Visualizer](https://github.com/dart-lang/dump-info-visualizer)
|
||||
can help you analyze the output.
|
||||
For more information, see the
|
||||
[dart2js_info API reference](http://dart-lang.github.io/dart2js_info/doc/api/).
|
||||
|
||||
#### Use ng2soyc.dart
|
||||
|
||||
[ng2soyc](https://github.com/angular/ng2soyc.dart) is a utility for analyzing
|
||||
code size contributors in Angular 2 applications. It groups code size by
|
||||
library and, assuming your library names follow
|
||||
[standard naming conventions](https://www.dartlang.org/articles/style-guide/#do-prefix-library-names-with-the-package-name-and-a-dot-separated-path)
|
||||
(package.library.sublibrary...), gives the code size breakdown at
|
||||
each level. To reduce noise in the output of very large apps, ng2soyc provides
|
||||
an option to hide libraries that are too small, so you can focus on the biggest
|
||||
contributors.
|
||||
|
||||
#### Find unused reflection data
|
||||
|
||||
Your app might have types that are annotated with `@Component` or `@Injectable`
|
||||
but never used.
|
||||
To find these unused types, use `reflector.trackUsage()` and then,
|
||||
after exercising your app, `reflector.listUnusedKeys()`.
|
||||
For example:
|
||||
|
||||
```
|
||||
import 'package:angular2/src/core/reflection/reflection.dart';
|
||||
...
|
||||
main() async {
|
||||
reflector.trackUsage();
|
||||
await bootstrap(AppComponent);
|
||||
print('Unused keys: ${reflector.listUnusedKeys()}');
|
||||
}
|
||||
```
|
||||
|
||||
When you run that code (in Dartium or another browser),
|
||||
you'll see a list of types that Angular _can_ inject but hasn't needed to.
|
||||
Consider removing those types or their `@Component`/`@Injectable` annotation
|
||||
to decrease your app's code size.
|
||||
|
||||
Three conditions must be true for `listUnusedKeys()` to return helpful data:
|
||||
|
||||
1. The angular2 transformer must run on the app.
|
||||
2. If you're running a JavaScript version of the app,
|
||||
the app must not be minified, so that the names are readable.
|
||||
3. You must exercise your app in as many ways as possible
|
||||
before calling `listUnusedKeys()`.
|
||||
Otherwise, you might get false positives:
|
||||
keys that haven't been used only because you didn't exercise
|
||||
the relevant feature of the app.
|
||||
|
||||
To run the angular2 transformer, first specify it in `pubspec.yaml`:
|
||||
|
||||
```
|
||||
name: hello_world
|
||||
...
|
||||
transformers:
|
||||
- angular2:
|
||||
entry_points: web/main.dart
|
||||
```
|
||||
|
||||
Then use pub to run the transformer. If you use `pub serve`,
|
||||
it provides both Dart and unminified (by default) JavaScript versions.
|
||||
If you want to serve actual files, then use `pub build` in debug mode
|
||||
to generate Dart and unminified JavaScript files:
|
||||
`pub build --mode=debug`.
|
||||
|
||||
The `reflector.trackUsage()` method makes Angular track the reflection
|
||||
information used by the app. Reflection information (`ReflectionInfo`) is a data
|
||||
structure that stores information that Angular uses for locating DI factories
|
||||
and for generating change detectors and other code related to a
|
||||
given type.
|
||||
|
||||
#### Use code coverage to find dead code
|
||||
|
||||
When running in Dartium (or in the Dart VM, in general) you can request code
|
||||
coverage information from the VM. You can either use
|
||||
[observatory](https://www.dartlang.org/tools/observatory/) or download
|
||||
the coverage file and use your own tools to inspect it. Lines of code that are
|
||||
not covered are top candidates for dead code.
|
||||
|
||||
Keep in mind, however, that uncovered code is not sufficient evidence of dead
|
||||
code, only necessary evidence. It is perfectly possible that you simply didn't
|
||||
exercise your application in a way that triggers the execution of uncovered
|
||||
code. A common example is error handling code. Just because your testing never
|
||||
encountered an error does not mean the error won't happen in production. You
|
||||
therefore don't have to rush and remove all the `catch` blocks.
|
||||
|
||||
### Reducing code size
|
||||
|
||||
To reduce code size, you can disable reflection,
|
||||
enable minification, and manually remove dead code.
|
||||
You can also try less safe options such as
|
||||
telling dart2js to trust type annotations.
|
||||
|
||||
|
||||
#### Disable reflection
|
||||
|
||||
`dart:mirrors` allows discovering program metadata at runtime. However, this
|
||||
means that `dart2js` needs to retain that metadata and thus increase the size
|
||||
of resulting JS output. In practice, however, it is possible to extract most
|
||||
metadata necessary for your metaprogramming tasks statically using a
|
||||
transformer and `package:analyzer`, and act on it before compiling to JS.
|
||||
|
||||
#### Enable minification
|
||||
|
||||
Minification shortens all your `longMethodNames` into 2- or 3-letter long
|
||||
symbols. `dart2js` ensures that this kind of renaming is done safely, without
|
||||
breaking the functionality of your programs. You can enable it in `pubspec.yaml`
|
||||
under `$dart2js` transformer:
|
||||
|
||||
```yaml
|
||||
transformers:
|
||||
...
|
||||
- $dart2js:
|
||||
minify: true
|
||||
```
|
||||
|
||||
#### Manually remove dead code
|
||||
|
||||
`dart2js` comes with dead code elimination out-of-the-box. However, it may not
|
||||
always be able to tell if a piece of code could be used. Consider the following
|
||||
example:
|
||||
|
||||
```dart
|
||||
/// This function decides which serialization format to use
|
||||
void setupSerializers() {
|
||||
if (server.doYouSupportProtocolBuffers()) {
|
||||
useProtobufSerializers();
|
||||
} else {
|
||||
useJsonSerializers();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this example the application asks the server what kind of serialization
|
||||
format it uses and dynamically chooses one or the other. `dart2js` can't
|
||||
tell whether the server responds with yes or no, so it must retain both
|
||||
kinds of serializers. However, if you know that your server supports
|
||||
protocol buffers, you can remove that `if` block entirely and default to
|
||||
protocol buffers.
|
||||
|
||||
Code coverage (see above) is a good way to find dead code in your app.
|
||||
|
||||
#### Unsafe options
|
||||
|
||||
Dart also provides more aggressive optimization options. However, you have to
|
||||
be careful when using them and as of today the benefits aren't that clear. If
|
||||
your type annotations are inaccurate you may end up with non-Darty runtime
|
||||
behavior, including the classic "undefined is not a function" tautology, as
|
||||
well as the "keep on truckin'" behavior, e.g. `null + 1 == 1` and
|
||||
`{} + [] == 0`.
|
||||
|
||||
`--trust-type-annotations` tells `dart2js` to trust that your type annotations
|
||||
are correct. So if you have a function `foo(Bar bar)` the compiler can omit the
|
||||
check that `bar` is truly `Bar` when calling methods on it.
|
||||
|
||||
`--trust-primitives` tells `dart2js` that primitive types, such as numbers and
|
||||
booleans are never `null` when performing arithmetic, and that your program
|
||||
does not run into range error when operating on lists, letting the compiler
|
||||
remove some of the error checking code.
|
||||
|
||||
Specify these options in `pubspec.yaml`.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
transformers:
|
||||
...
|
||||
- $dart2js:
|
||||
commandLineOptions:
|
||||
- --trust-type-annotations
|
||||
- --trust-primitives
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Change detection profiler
|
||||
|
||||
If your application is janky (it misses frames) or is slow according to other
|
||||
metrics, you need to find out why. This tool helps by measuring the average
|
||||
speed of _change detection_, a phase in Angular's
|
||||
lifecycle that detects changes in values that are bound to the UI.
|
||||
Janky UI updates can result from slowness either in _computing_ the changes or
|
||||
in _applying_ those changes to the UI.
|
||||
|
||||
For your app to be performant, the process of _computing_ changes must be very
|
||||
fast—preferably **under 3 milliseconds**.
|
||||
Fast change computation leaves room for
|
||||
the application logic, UI updates, and browser rendering pipeline
|
||||
to fit within a 16 ms frame (assuming a target frame rate of 60 FPS).
|
||||
|
||||
The change detection profiler repeatedly performs change detection
|
||||
without invoking any user actions, such as clicking buttons or entering
|
||||
text in input fields. It then computes the average amount of time
|
||||
(in milliseconds) to perform a single cycle of change detection and
|
||||
prints that to the console. This number depends on the current state of the UI. You are likely to see different numbers
|
||||
as you go from one screen in your application to another.
|
||||
|
||||
#### Running the profiler
|
||||
|
||||
Before running the profiler, enable the debugging tools
|
||||
and put the app into the state you want to measure:
|
||||
|
||||
1. If you haven't already done so,
|
||||
[enable the debugging tools](#enabling-the-debugging-tools).
|
||||
2. Navigate the app to a screen whose performance you want to profile.
|
||||
3. Make sure the screen is in a state that you want to measure.
|
||||
For example, you might want to profile the screen several times,
|
||||
with different amounts and kinds of data.
|
||||
|
||||
To run the profiler, enter the following in the dev console:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection();
|
||||
```
|
||||
|
||||
The results are visible in the console.
|
||||
|
||||
|
||||
#### Recording CPU profiles
|
||||
|
||||
To record a profile, pass `{record: true}` to `timeChangeDetection()`:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection({record: true});
|
||||
```
|
||||
|
||||
Then open the **Profiles** tab. The recorded profile has the title
|
||||
**Change Detection**. In Chrome, if you record the profile repeatedly, all the
|
||||
profiles are nested under Change Detection.
|
||||
|
||||
|
||||
#### Interpreting the numbers
|
||||
|
||||
In a properly designed application, repeated attempts to detect changes without
|
||||
any user actions result in no changes to the UI. It is
|
||||
also desirable to have the cost of a user action be proportional to the amount
|
||||
of UI changes required. For example, popping up a menu with 5 items should be
|
||||
vastly faster than rendering a table of 500 rows and 10 columns. Therefore,
|
||||
change detection with no UI updates should be as fast as possible.
|
||||
|
||||
#### Investigating slow change detection
|
||||
|
||||
So you found a screen in your application on which the profiler reports a very
|
||||
high number (i.e. >3ms). This is where a recorded CPU profile can help. Enable
|
||||
recording while profiling:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection({record: true});
|
||||
```
|
||||
|
||||
Then look for hot spots using
|
||||
[Chrome CPU profiler](https://developer.chrome.com/devtools/docs/cpu-profiling).
|
||||
|
||||
#### Reducing change detection cost
|
||||
|
||||
There are many reasons for slow change detection. To gain intuition about
|
||||
possible causes it helps to understand how change detection works. Such a
|
||||
discussion is outside the scope of this document,
|
||||
but here are some key concepts.
|
||||
|
||||
<!-- TODO: link to change detection docs -->
|
||||
|
||||
By default, Angular uses a _dirty checking_ mechanism to find model changes.
|
||||
This mechanism involves evaluating every bound expression that's active on the
|
||||
UI. These usually include text interpolation via `{{expression}}` and property
|
||||
bindings via `[prop]="expression"`. If any of the evaluated expressions are
|
||||
costly to compute, they might contribute to slow change detection. A good way to
|
||||
speed things up is to use plain class fields in your expressions and avoid any
|
||||
kind of computation. For example:
|
||||
|
||||
```dart
|
||||
@View(
|
||||
template: '<button [enabled]="isEnabled">{{title}}</button>'
|
||||
)
|
||||
class FancyButton {
|
||||
// GOOD: no computation, just returns the value
|
||||
bool isEnabled;
|
||||
|
||||
// BAD: computes the final value upon request
|
||||
String _title;
|
||||
String get title => _title.trim().toUpperCase();
|
||||
}
|
||||
```
|
||||
|
||||
Most cases like these can be solved by precomputing the value and storing the
|
||||
final value in a field.
|
||||
|
||||
Angular also supports a second type of change detection: the _push_ model. In
|
||||
this model, Angular does not poll your component for changes. Instead, the
|
||||
component tells Angular when it changes, and only then does Angular perform
|
||||
the update. This model is suitable in situations when your data model uses
|
||||
observable or immutable objects.
|
||||
|
||||
<!-- TODO: link to discussion of push model -->
|
@ -14,7 +14,7 @@ Ctrl + Shift + j.
|
||||
By default the debug tools are disabled. You can enable debug tools as follows:
|
||||
|
||||
```typescript
|
||||
import {enableDebugTools} from 'angular2/platform/browser';
|
||||
import {enableDebugTools} from '@angular/platform-browser';
|
||||
|
||||
bootstrap(Application).then((appRef) => {
|
||||
enableDebugTools(appRef);
|
||||
|
@ -4,15 +4,16 @@
|
||||
var CIconfiguration = {
|
||||
'Chrome': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'Firefox': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
// FirefoxBeta should be required:true
|
||||
// https://github.com/angular/angular/issues/7560
|
||||
'FirefoxBeta': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: false}},
|
||||
// FirefoxBeta and ChromeBeta should be target:'BS' or target:'SL', and required:true
|
||||
// Currently deactivated due to https://github.com/angular/angular/issues/7560
|
||||
'ChromeBeta': { unitTest: {target: null, required: true}, e2e: {target: null, required: false}},
|
||||
'FirefoxBeta': { unitTest: {target: null, required: false}, e2e: {target: null, required: false}},
|
||||
'ChromeDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
|
||||
'FirefoxDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
|
||||
'IE9': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
'IE10': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'IE11': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'Edge': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'Edge': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'Android4.1': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
'Android4.2': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
'Android4.3': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
@ -23,8 +24,7 @@ var CIconfiguration = {
|
||||
'Safari9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS7': { unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
|
||||
'iOS8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
// TODO(mlaval): iOS9 deactivated as not reliable, reactivate after https://github.com/angular/angular/issues/5408
|
||||
'iOS9': { unitTest: {target: null, required: false}, e2e: {target: null, required: true}},
|
||||
'iOS9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'WindowsPhone': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
|
||||
};
|
||||
|
||||
@ -38,7 +38,7 @@ var customLaunchers = {
|
||||
'SL_CHROME': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome',
|
||||
version: '50'
|
||||
version: '52'
|
||||
},
|
||||
'SL_CHROMEBETA': {
|
||||
base: 'SauceLabs',
|
||||
@ -53,7 +53,7 @@ var customLaunchers = {
|
||||
'SL_FIREFOX': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'firefox',
|
||||
version: '45'
|
||||
version: '46'
|
||||
},
|
||||
'SL_FIREFOXBETA': {
|
||||
base: 'SauceLabs',
|
||||
@ -69,13 +69,13 @@ var customLaunchers = {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
version: '7.0'
|
||||
},
|
||||
'SL_SAFARI8': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.10',
|
||||
version: '8'
|
||||
version: '8.0'
|
||||
},
|
||||
'SL_SAFARI9': {
|
||||
base: 'SauceLabs',
|
||||
@ -99,7 +99,7 @@ var customLaunchers = {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
platform: 'OS X 10.10',
|
||||
version: '9.1'
|
||||
version: '9.3'
|
||||
},
|
||||
'SL_IE9': {
|
||||
base: 'SauceLabs',
|
||||
@ -121,9 +121,9 @@ var customLaunchers = {
|
||||
},
|
||||
'SL_EDGE': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'microsoftedge',
|
||||
browserName: 'MicrosoftEdge',
|
||||
platform: 'Windows 10',
|
||||
version: '20.10240'
|
||||
version: '13.10586'
|
||||
},
|
||||
'SL_ANDROID4.1': {
|
||||
base: 'SauceLabs',
|
||||
@ -202,7 +202,7 @@ var customLaunchers = {
|
||||
base: 'BrowserStack',
|
||||
device: 'iPhone 6S',
|
||||
os: 'ios',
|
||||
os_version: '9.0'
|
||||
os_version: '9.1'
|
||||
},
|
||||
'BS_IE9': {
|
||||
base: 'BrowserStack',
|
||||
|
117
build.sh
117
build.sh
@ -4,13 +4,18 @@ set -e -o pipefail
|
||||
|
||||
cd `dirname $0`
|
||||
|
||||
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
|
||||
|
||||
|
||||
TSCONFIG=./modules/tsconfig.json
|
||||
echo "====== (all)COMPILING: \$(npm bin)/ng2tc -p ${TSCONFIG} ====="
|
||||
rm -rf ./dist/all/
|
||||
mkdir ./dist/all/
|
||||
mkdir -p ./dist/all/
|
||||
|
||||
# prepare all files for e2e tests
|
||||
TSCONFIG=./tools/tsconfig.json
|
||||
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
|
||||
$(npm bin)/tsc -p ${TSCONFIG}
|
||||
cp ./tools/@angular/tsc-wrapped/package.json ./dist/tools/@angular/tsc-wrapped
|
||||
|
||||
echo "====== Copying files needed for e2e tests ====="
|
||||
cp -r ./modules/playground ./dist/all/
|
||||
cp -r ./modules/playground/favicon.ico ./dist/
|
||||
#rsync -aP ./modules/playground/* ./dist/all/playground/
|
||||
@ -22,13 +27,16 @@ ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js .
|
||||
ln -s ../../../../node_modules/systemjs/dist/system.src.js .
|
||||
ln -s ../../../../node_modules/base64-js/lib/b64.js .
|
||||
ln -s ../../../../node_modules/reflect-metadata/Reflect.js .
|
||||
ln -s ../../../../node_modules/rxjs/bundles/Rx.js .
|
||||
ln -s ../../../../node_modules/rxjs .
|
||||
ln -s ../../../../node_modules/angular/angular.js .
|
||||
cd -
|
||||
|
||||
# compile ts code
|
||||
$(npm bin)/ng2tc -p ${TSCONFIG}
|
||||
|
||||
TSCONFIG=./modules/tsconfig.json
|
||||
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
|
||||
# compile ts code
|
||||
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
|
||||
$TSC -p modules/tsconfig.json
|
||||
|
||||
rm -rf ./dist/packages-dist
|
||||
|
||||
@ -36,26 +44,28 @@ for PACKAGE in \
|
||||
core \
|
||||
compiler \
|
||||
common \
|
||||
forms \
|
||||
platform-browser \
|
||||
platform-browser-dynamic \
|
||||
platform-server \
|
||||
http \
|
||||
router \
|
||||
router-deprecated \
|
||||
upgrade
|
||||
upgrade \
|
||||
compiler-cli
|
||||
do
|
||||
SRCDIR=./modules/@angular/${PACKAGE}
|
||||
DESTDIR=./dist/packages-dist/${PACKAGE}
|
||||
UMDES6PATH=${DESTDIR}/esm/${PACKAGE}.umd.js
|
||||
UMDES5PATH=${DESTDIR}/${PACKAGE}.umd.js
|
||||
|
||||
UMD_ES6_PATH=${DESTDIR}/esm/${PACKAGE}.umd.js
|
||||
UMD_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.js
|
||||
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
|
||||
|
||||
if [[ ${PACKAGE} == "router-deprecated" ]]; then
|
||||
echo "====== COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json ====="
|
||||
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json
|
||||
else
|
||||
echo "====== COMPILING: \$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es5.json ====="
|
||||
$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es5.json
|
||||
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-es5.json ====="
|
||||
$TSC -p ${SRCDIR}/tsconfig-es5.json
|
||||
fi
|
||||
|
||||
cp ${SRCDIR}/package.json ${DESTDIR}/
|
||||
@ -63,50 +73,49 @@ do
|
||||
|
||||
echo "====== TSC 1.8 d.ts compat for ${DESTDIR} ====="
|
||||
# safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8
|
||||
if [[ ${TRAVIS} ]]; then
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -e 's/\(^ *(static |private )*\)*readonly */\1/g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -e 's/\(^ *(static |private )*\)*readonly */\1/g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
|
||||
else
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -e 's/\(^ *(static |private )*\)*readonly */\1/g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -e 's/\(^ *(static |private )*\)*readonly */\1/g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
|
||||
fi
|
||||
|
||||
if [[ ${PACKAGE} != compiler-cli ]]; then
|
||||
|
||||
if [[ ${PACKAGE} == "router-deprecated" ]]; then
|
||||
echo "====== (esm)COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json ====="
|
||||
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json
|
||||
else
|
||||
echo "====== (esm)COMPILING: \$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es2015.json ====="
|
||||
$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es2015.json
|
||||
if [[ ${PACKAGE} == "router-deprecated" ]]; then
|
||||
echo "====== (esm)COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json ====="
|
||||
$(npm bin)/tsc --emitDecoratorMetadata -p ${SRCDIR}/tsconfig-es2015.json
|
||||
else
|
||||
echo "====== (esm)COMPILING: $TSC -p ${SRCDIR}/tsconfig-es2015.json ====="
|
||||
$TSC -p ${SRCDIR}/tsconfig-es2015.json
|
||||
fi
|
||||
|
||||
echo "====== BUNDLING: ${SRCDIR} ====="
|
||||
mkdir ${DESTDIR}/bundles
|
||||
|
||||
(
|
||||
cd ${SRCDIR}
|
||||
echo "..." # here just to have grep match something and not exit with 1
|
||||
../../../node_modules/.bin/rollup -c rollup.config.js
|
||||
) 2>&1 | grep -v "as external dependency"
|
||||
|
||||
$(npm bin)/tsc \
|
||||
--out ${UMD_ES5_PATH} \
|
||||
--target es5 \
|
||||
--lib "es6,dom" \
|
||||
--allowJs \
|
||||
${UMD_ES6_PATH}
|
||||
|
||||
rm ${UMD_ES6_PATH}
|
||||
|
||||
cat ./modules/@angular/license-banner.txt > ${UMD_ES5_PATH}.tmp
|
||||
cat ${UMD_ES5_PATH} >> ${UMD_ES5_PATH}.tmp
|
||||
mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH}
|
||||
|
||||
$(npm bin)/uglifyjs -c --screw-ie8 -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
|
||||
fi
|
||||
|
||||
|
||||
echo "====== BUNDLING: ${SRCDIR} ====="
|
||||
(
|
||||
cd ${SRCDIR}
|
||||
echo "..." # here just to have grep match something and not exit with 1
|
||||
../../../node_modules/.bin/rollup -c rollup.config.js
|
||||
) 2>&1 | grep -v "as external dependency"
|
||||
|
||||
# workaround for https://github.com/rollup/rollup/issues/626
|
||||
if [[ ${TRAVIS} ]]; then
|
||||
sed -i "s/ class exports\./ class /g" ${DESTDIR}/esm/${PACKAGE}.umd.js
|
||||
else
|
||||
sed -i '' "s/ class exports\./ class /g" ${DESTDIR}/esm/${PACKAGE}.umd.js
|
||||
fi
|
||||
|
||||
$(npm bin)/tsc \
|
||||
--out ${UMDES5PATH} \
|
||||
--target es5 \
|
||||
--allowJs \
|
||||
${UMDES6PATH} \
|
||||
modules/\@angular/manual_typings/globals.d.ts \
|
||||
modules/\@angular/typings/es6-collections/es6-collections.d.ts \
|
||||
modules/\@angular/typings/es6-promise/es6-promise.d.ts
|
||||
rm ${UMDES6PATH}
|
||||
|
||||
cat ./modules/@angular/license-banner.txt > ${UMDES5PATH}.tmp
|
||||
cat ${UMDES5PATH} >> ${UMDES5PATH}.tmp
|
||||
mv ${UMDES5PATH}.tmp ${UMDES5PATH}
|
||||
|
||||
done
|
||||
|
||||
echo "====== COMPILING: \$(npm bin)/tsc -p benchpress/tsconfig.json ====="
|
||||
$(npm bin)/tsc -p ./modules/benchpress/tsconfig.json
|
||||
|
12
circle.yml
12
circle.yml
@ -5,17 +5,7 @@ machine:
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g npm
|
||||
override:
|
||||
- npm install:
|
||||
environment:
|
||||
# Token for tsd to increase github rate limit
|
||||
# See https://github.com/DefinitelyTyped/tsd#tsdrc
|
||||
# This is not hidden using https://circleci.com/docs/fork-pr-builds#details
|
||||
# because those are not visible for pull requests, and those should also be reliable.
|
||||
# This SSO token belongs to github account angular-github-ratelimit-token which has no access
|
||||
# (password is in Valentine)
|
||||
TSD_GITHUB_TOKEN: ef474500309daea53d5991b3079159a29520a40b
|
||||
|
||||
test:
|
||||
override:
|
||||
- npm run build
|
||||
- gulp lint
|
||||
|
1664
gulpfile.js
1664
gulpfile.js
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,6 @@ module.exports = function(config) {
|
||||
// Sources and specs.
|
||||
// Loaded through the System loader, in `test-main.js`.
|
||||
{pattern: 'dist/all/@angular/**/*.js', included: false, watched: true},
|
||||
{pattern: 'dist/all/angular2/**/*.js', included: false, watched: true},
|
||||
|
||||
'node_modules/es6-shim/es6-shim.js',
|
||||
// include Angular v1 for upgrade module testing
|
||||
@ -33,12 +32,14 @@ module.exports = function(config) {
|
||||
'test-main.js',
|
||||
{pattern: 'dist/all/empty.*', included: false, watched: false},
|
||||
{pattern: 'modules/@angular/platform-browser/test/static_assets/**', included: false, watched: false},
|
||||
{pattern: 'modules/@angular/platform-browser-dynamic/test/browser/static_assets/**', included: false, watched: false}
|
||||
{pattern: 'modules/@angular/platform-browser/test/browser/static_assets/**', included: false, watched: false}
|
||||
],
|
||||
|
||||
exclude: [
|
||||
'dist/all/@angular/**/e2e_test/**',
|
||||
'dist/all/@angular/examples/**',
|
||||
'dist/all/@angular/router/**',
|
||||
'dist/all/@angular/compiler-cli/**',
|
||||
'dist/all/angular1_router.js',
|
||||
'dist/all/@angular/platform-browser/testing/e2e_util.js'
|
||||
],
|
||||
@ -51,7 +52,6 @@ module.exports = function(config) {
|
||||
'karma-sauce-launcher',
|
||||
'karma-chrome-launcher',
|
||||
'karma-sourcemap-loader',
|
||||
'karma-dart',
|
||||
internalAngularReporter
|
||||
],
|
||||
|
||||
@ -67,7 +67,7 @@ module.exports = function(config) {
|
||||
recordVideo: false,
|
||||
recordScreenshots: false,
|
||||
options: {
|
||||
'selenium-version': '2.48.2',
|
||||
'selenium-version': '2.53.0',
|
||||
'command-timeout': 600,
|
||||
'idle-timeout': 600,
|
||||
'max-duration': 5400
|
||||
|
@ -1,6 +0,0 @@
|
||||
Angular2
|
||||
=========
|
||||
|
||||
The sources for this package are in the main [Angular2](https://github.com/angular/angular) repo. Please file issues and pull requests against that repo. This is the repository for the upcoming 2.0 version. If you're looking for the current official version of Angular you should go to [angular/angular.js](https://github.com/angular/angular.js)
|
||||
|
||||
License: Apache MIT 2.0
|
@ -1 +0,0 @@
|
||||
export 'index.dart';
|
@ -1,5 +1,30 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgModule} from '@angular/core';
|
||||
import {COMMON_DIRECTIVES} from './src/common_directives';
|
||||
import {COMMON_PIPES} from './src/pipes';
|
||||
|
||||
export * from './src/pipes';
|
||||
export * from './src/directives';
|
||||
export * from './src/forms';
|
||||
export * from './src/forms-deprecated';
|
||||
export * from './src/common_directives';
|
||||
export * from './src/location';
|
||||
export {NgLocalization} from './src/localization';
|
||||
|
||||
// Note: This does not contain the location providers,
|
||||
// as they need some platform specific implementations to work.
|
||||
/**
|
||||
* The module that includes all the basic Angular directives like {@link NgIf}, ${link NgFor}, ...
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@NgModule(
|
||||
{declarations: [COMMON_DIRECTIVES, COMMON_PIPES], exports: [COMMON_DIRECTIVES, COMMON_PIPES]})
|
||||
export class CommonModule {
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@angular/common",
|
||||
"version": "$$ANGULAR_VERSION$$",
|
||||
"version": "0.0.0-PLACEHOLDER",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"jsnext:main": "esm/index.js",
|
||||
@ -8,6 +8,10 @@
|
||||
"author": "angular",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@angular/core": "$$ANGULAR_VERSION$$"
|
||||
"@angular/core": "0.0.0-PLACEHOLDER"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.git"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,13 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import {FORM_DIRECTIVES} from './forms';
|
||||
|
||||
import {CORE_DIRECTIVES} from './directives';
|
||||
|
||||
/**
|
||||
@ -44,5 +52,7 @@ import {CORE_DIRECTIVES} from './directives';
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental Contains forms which are experimental.
|
||||
*/
|
||||
export const COMMON_DIRECTIVES: Type[][] = /*@ts2dart_const*/[CORE_DIRECTIVES, FORM_DIRECTIVES];
|
||||
export const COMMON_DIRECTIVES: Type[][] = [CORE_DIRECTIVES];
|
||||
|
@ -1,14 +1,21 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* Common directives shipped with Angular.
|
||||
*/
|
||||
export {CORE_DIRECTIVES} from './directives/core_directives';
|
||||
export {NgClass} from './directives/ng_class';
|
||||
export {NgFor} from './directives/ng_for';
|
||||
export {NgIf} from './directives/ng_if';
|
||||
export {NgTemplateOutlet} from './directives/ng_template_outlet';
|
||||
export {NgPlural, NgPluralCase} from './directives/ng_plural';
|
||||
export {NgStyle} from './directives/ng_style';
|
||||
export {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './directives/ng_switch';
|
||||
export {NgPlural, NgPluralCase, NgLocalization} from './directives/ng_plural';
|
||||
export * from './directives/observable_list_diff';
|
||||
export {CORE_DIRECTIVES} from './directives/core_directives';
|
||||
export {NgSwitch, NgSwitchCase, NgSwitchDefault} from './directives/ng_switch';
|
||||
export {NgTemplateOutlet} from './directives/ng_template_outlet';
|
||||
|
@ -1,11 +1,20 @@
|
||||
import {Type} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '../facade/lang';
|
||||
|
||||
import {NgClass} from './ng_class';
|
||||
import {NgFor} from './ng_for';
|
||||
import {NgIf} from './ng_if';
|
||||
import {NgTemplateOutlet} from './ng_template_outlet';
|
||||
import {NgStyle} from './ng_style';
|
||||
import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './ng_switch';
|
||||
import {NgPlural, NgPluralCase} from './ng_plural';
|
||||
import {NgStyle} from './ng_style';
|
||||
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from './ng_switch';
|
||||
import {NgTemplateOutlet} from './ng_template_outlet';
|
||||
|
||||
/**
|
||||
* A collection of Angular core directives that are likely to be used in each and every Angular
|
||||
@ -46,16 +55,18 @@ import {NgPlural, NgPluralCase} from './ng_plural';
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export const CORE_DIRECTIVES: Type[] = /*@ts2dart_const*/[
|
||||
export const CORE_DIRECTIVES: Type[] = [
|
||||
NgClass,
|
||||
NgFor,
|
||||
NgIf,
|
||||
NgTemplateOutlet,
|
||||
NgStyle,
|
||||
NgSwitch,
|
||||
NgSwitchWhen,
|
||||
NgSwitchCase,
|
||||
NgSwitchDefault,
|
||||
NgPlural,
|
||||
NgPluralCase
|
||||
NgPluralCase,
|
||||
];
|
||||
|
@ -1,18 +1,17 @@
|
||||
import {
|
||||
DoCheck,
|
||||
OnDestroy,
|
||||
Directive,
|
||||
ElementRef,
|
||||
IterableDiffers,
|
||||
KeyValueDiffers,
|
||||
Renderer,
|
||||
IterableDiffer,
|
||||
KeyValueDiffer,
|
||||
CollectionChangeRecord,
|
||||
KeyValueChangeRecord
|
||||
} from '@angular/core';
|
||||
import {isPresent, isString, isArray} from '../../src/facade/lang';
|
||||
import {StringMapWrapper, isListLikeIterable} from '../../src/facade/collection';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
|
||||
import {StringMapWrapper, isListLikeIterable} from '../facade/collection';
|
||||
import {isArray, isPresent, isString} from '../facade/lang';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The `NgClass` directive conditionally adds and removes CSS classes on an HTML element based on
|
||||
@ -58,7 +57,7 @@ import {StringMapWrapper, isListLikeIterable} from '../../src/facade/collection'
|
||||
* color: gray;
|
||||
* border: medium solid gray;
|
||||
* }
|
||||
* `]
|
||||
* `],
|
||||
* directives: [NgClass]
|
||||
* })
|
||||
* class ToggleButton {
|
||||
@ -72,17 +71,22 @@ import {StringMapWrapper, isListLikeIterable} from '../../src/facade/collection'
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngClass]', inputs: ['rawClass: ngClass', 'initialClasses: class']})
|
||||
export class NgClass implements DoCheck, OnDestroy {
|
||||
@Directive({selector: '[ngClass]'})
|
||||
export class NgClass implements DoCheck {
|
||||
private _iterableDiffer: IterableDiffer;
|
||||
private _keyValueDiffer: KeyValueDiffer;
|
||||
private _initialClasses: string[] = [];
|
||||
private _rawClass: string[] | Set<string>;
|
||||
private _rawClass: string[]|Set<string>;
|
||||
|
||||
constructor(private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
|
||||
private _ngEl: ElementRef, private _renderer: Renderer) {}
|
||||
constructor(
|
||||
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
|
||||
private _ngEl: ElementRef, private _renderer: Renderer) {}
|
||||
|
||||
|
||||
@Input('class')
|
||||
set initialClasses(v: string) {
|
||||
this._applyInitialClasses(true);
|
||||
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
|
||||
@ -90,14 +94,15 @@ export class NgClass implements DoCheck, OnDestroy {
|
||||
this._applyClasses(this._rawClass, false);
|
||||
}
|
||||
|
||||
set rawClass(v: string | string[] | Set<string>| {[key: string]: any}) {
|
||||
@Input()
|
||||
set ngClass(v: string|string[]|Set<string>|{[key: string]: any}) {
|
||||
this._cleanupClasses(this._rawClass);
|
||||
|
||||
if (isString(v)) {
|
||||
v = (<string>v).split(' ');
|
||||
}
|
||||
|
||||
this._rawClass = <string[] | Set<string>>v;
|
||||
this._rawClass = <string[]|Set<string>>v;
|
||||
this._iterableDiffer = null;
|
||||
this._keyValueDiffer = null;
|
||||
if (isPresent(v)) {
|
||||
@ -124,9 +129,7 @@ export class NgClass implements DoCheck, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this._cleanupClasses(this._rawClass); }
|
||||
|
||||
private _cleanupClasses(rawClassVal: string[] | Set<string>| {[key: string]: any}): void {
|
||||
private _cleanupClasses(rawClassVal: string[]|Set<string>|{[key: string]: any}): void {
|
||||
this._applyClasses(rawClassVal, true);
|
||||
this._applyInitialClasses(false);
|
||||
}
|
||||
@ -154,18 +157,18 @@ export class NgClass implements DoCheck, OnDestroy {
|
||||
this._initialClasses.forEach(className => this._toggleClass(className, !isCleanup));
|
||||
}
|
||||
|
||||
private _applyClasses(rawClassVal: string[] | Set<string>| {[key: string]: any},
|
||||
isCleanup: boolean) {
|
||||
private _applyClasses(
|
||||
rawClassVal: string[]|Set<string>|{[key: string]: any}, isCleanup: boolean) {
|
||||
if (isPresent(rawClassVal)) {
|
||||
if (isArray(rawClassVal)) {
|
||||
(<string[]>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
|
||||
} else if (rawClassVal instanceof Set) {
|
||||
(<Set<string>>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
|
||||
} else {
|
||||
StringMapWrapper.forEach(<{[k: string]: any}>rawClassVal,
|
||||
(expVal: any, className: string) => {
|
||||
if (isPresent(expVal)) this._toggleClass(className, !isCleanup);
|
||||
});
|
||||
StringMapWrapper.forEach(
|
||||
<{[k: string]: any}>rawClassVal, (expVal: any, className: string) => {
|
||||
if (isPresent(expVal)) this._toggleClass(className, !isCleanup);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,15 @@
|
||||
import {
|
||||
DoCheck,
|
||||
Directive,
|
||||
ChangeDetectorRef,
|
||||
IterableDiffer,
|
||||
IterableDiffers,
|
||||
ViewContainerRef,
|
||||
TemplateRef,
|
||||
EmbeddedViewRef,
|
||||
TrackByFn,
|
||||
DefaultIterableDiffer,
|
||||
CollectionChangeRecord
|
||||
} from '@angular/core';
|
||||
import {isPresent, isBlank, getTypeNameForDebugging} from '../../src/facade/lang';
|
||||
import {BaseException} from '../../src/facade/exceptions';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
import {getTypeNameForDebugging, isBlank, isPresent} from '../facade/lang';
|
||||
|
||||
export class NgForRow {
|
||||
constructor(public $implicit: any, public index: number, public count: number) {}
|
||||
@ -76,73 +73,79 @@ export class NgForRow {
|
||||
*
|
||||
* See a [live demo](http://plnkr.co/edit/KVuXxDp0qinGDyo307QW?p=preview) for a more detailed
|
||||
* example.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngFor][ngForOf]', inputs: ['ngForTrackBy', 'ngForOf', 'ngForTemplate']})
|
||||
export class NgFor implements DoCheck {
|
||||
/** @internal */
|
||||
_ngForOf: any;
|
||||
/** @internal */
|
||||
_ngForTrackBy: TrackByFn;
|
||||
@Directive({selector: '[ngFor][ngForOf]'})
|
||||
export class NgFor implements DoCheck, OnChanges {
|
||||
@Input() ngForOf: any;
|
||||
@Input() ngForTrackBy: TrackByFn;
|
||||
|
||||
private _differ: IterableDiffer;
|
||||
|
||||
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<NgForRow>,
|
||||
private _iterableDiffers: IterableDiffers, private _cdr: ChangeDetectorRef) {}
|
||||
|
||||
set ngForOf(value: any) {
|
||||
this._ngForOf = value;
|
||||
if (isBlank(this._differ) && isPresent(value)) {
|
||||
try {
|
||||
this._differ = this._iterableDiffers.find(value).create(this._cdr, this._ngForTrackBy);
|
||||
} catch (e) {
|
||||
throw new BaseException(
|
||||
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
constructor(
|
||||
private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<NgForRow>,
|
||||
private _iterableDiffers: IterableDiffers, private _cdr: ChangeDetectorRef) {}
|
||||
|
||||
@Input()
|
||||
set ngForTemplate(value: TemplateRef<NgForRow>) {
|
||||
if (isPresent(value)) {
|
||||
this._templateRef = value;
|
||||
}
|
||||
}
|
||||
|
||||
set ngForTrackBy(value: TrackByFn) { this._ngForTrackBy = value; }
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if ('ngForOf' in changes) {
|
||||
// React on ngForOf changes only once all inputs have been initialized
|
||||
const value = changes['ngForOf'].currentValue;
|
||||
if (isBlank(this._differ) && isPresent(value)) {
|
||||
try {
|
||||
this._differ = this._iterableDiffers.find(value).create(this._cdr, this.ngForTrackBy);
|
||||
} catch (e) {
|
||||
throw new BaseException(
|
||||
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck() {
|
||||
if (isPresent(this._differ)) {
|
||||
var changes = this._differ.diff(this._ngForOf);
|
||||
const changes = this._differ.diff(this.ngForOf);
|
||||
if (isPresent(changes)) this._applyChanges(changes);
|
||||
}
|
||||
}
|
||||
|
||||
private _applyChanges(changes: DefaultIterableDiffer) {
|
||||
// TODO(rado): check if change detection can produce a change record that is
|
||||
// easier to consume than current.
|
||||
var recordViewTuples: RecordViewTuple[] = [];
|
||||
changes.forEachRemovedItem((removedRecord: CollectionChangeRecord) =>
|
||||
recordViewTuples.push(new RecordViewTuple(removedRecord, null)));
|
||||
const insertTuples: RecordViewTuple[] = [];
|
||||
changes.forEachOperation(
|
||||
(item: CollectionChangeRecord, adjustedPreviousIndex: number, currentIndex: number) => {
|
||||
if (item.previousIndex == null) {
|
||||
let view = this._viewContainer.createEmbeddedView(
|
||||
this._templateRef, new NgForRow(null, null, null), currentIndex);
|
||||
let tuple = new RecordViewTuple(item, view);
|
||||
insertTuples.push(tuple);
|
||||
} else if (currentIndex == null) {
|
||||
this._viewContainer.remove(adjustedPreviousIndex);
|
||||
} else {
|
||||
let view = this._viewContainer.get(adjustedPreviousIndex);
|
||||
this._viewContainer.move(view, currentIndex);
|
||||
let tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForRow>>view);
|
||||
insertTuples.push(tuple);
|
||||
}
|
||||
});
|
||||
|
||||
changes.forEachMovedItem((movedRecord: CollectionChangeRecord) =>
|
||||
recordViewTuples.push(new RecordViewTuple(movedRecord, null)));
|
||||
|
||||
var insertTuples = this._bulkRemove(recordViewTuples);
|
||||
|
||||
changes.forEachAddedItem((addedRecord: CollectionChangeRecord) =>
|
||||
insertTuples.push(new RecordViewTuple(addedRecord, null)));
|
||||
|
||||
this._bulkInsert(insertTuples);
|
||||
|
||||
for (var i = 0; i < insertTuples.length; i++) {
|
||||
for (let i = 0; i < insertTuples.length; i++) {
|
||||
this._perViewChange(insertTuples[i].view, insertTuples[i].record);
|
||||
}
|
||||
|
||||
for (var i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
|
||||
for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
|
||||
var viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i);
|
||||
viewRef.context.index = i;
|
||||
viewRef.context.count = ilen;
|
||||
}
|
||||
|
||||
changes.forEachIdentityChange((record) => {
|
||||
changes.forEachIdentityChange((record: any) => {
|
||||
var viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex);
|
||||
viewRef.context.$implicit = record.item;
|
||||
});
|
||||
@ -151,45 +154,8 @@ export class NgFor implements DoCheck {
|
||||
private _perViewChange(view: EmbeddedViewRef<NgForRow>, record: CollectionChangeRecord) {
|
||||
view.context.$implicit = record.item;
|
||||
}
|
||||
|
||||
private _bulkRemove(tuples: RecordViewTuple[]): RecordViewTuple[] {
|
||||
tuples.sort((a: RecordViewTuple, b: RecordViewTuple) =>
|
||||
a.record.previousIndex - b.record.previousIndex);
|
||||
var movedTuples: RecordViewTuple[] = [];
|
||||
for (var i = tuples.length - 1; i >= 0; i--) {
|
||||
var tuple = tuples[i];
|
||||
// separate moved views from removed views.
|
||||
if (isPresent(tuple.record.currentIndex)) {
|
||||
tuple.view =
|
||||
<EmbeddedViewRef<NgForRow>>this._viewContainer.detach(tuple.record.previousIndex);
|
||||
movedTuples.push(tuple);
|
||||
} else {
|
||||
this._viewContainer.remove(tuple.record.previousIndex);
|
||||
}
|
||||
}
|
||||
return movedTuples;
|
||||
}
|
||||
|
||||
private _bulkInsert(tuples: RecordViewTuple[]): RecordViewTuple[] {
|
||||
tuples.sort((a, b) => a.record.currentIndex - b.record.currentIndex);
|
||||
for (var i = 0; i < tuples.length; i++) {
|
||||
var tuple = tuples[i];
|
||||
if (isPresent(tuple.view)) {
|
||||
this._viewContainer.insert(tuple.view, tuple.record.currentIndex);
|
||||
} else {
|
||||
tuple.view = this._viewContainer.createEmbeddedView(
|
||||
this._templateRef, new NgForRow(null, null, null), tuple.record.currentIndex);
|
||||
}
|
||||
}
|
||||
return tuples;
|
||||
}
|
||||
}
|
||||
|
||||
class RecordViewTuple {
|
||||
view: EmbeddedViewRef<NgForRow>;
|
||||
record: any;
|
||||
constructor(record: any, view: EmbeddedViewRef<NgForRow>) {
|
||||
this.record = record;
|
||||
this.view = view;
|
||||
}
|
||||
constructor(public record: any, public view: EmbeddedViewRef<NgForRow>) {}
|
||||
}
|
||||
|
@ -1,5 +1,16 @@
|
||||
import {Directive, ViewContainerRef, TemplateRef} from '@angular/core';
|
||||
import {isBlank} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {isBlank} from '../facade/lang';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Removes or recreates a portion of the DOM tree based on an {expression}.
|
||||
@ -22,15 +33,18 @@ import {isBlank} from '../../src/facade/lang';
|
||||
* - `<div *ngIf="condition">...</div>`
|
||||
* - `<div template="ngIf condition">...</div>`
|
||||
* - `<template [ngIf]="condition"><div>...</div></template>`
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngIf]', inputs: ['ngIf']})
|
||||
@Directive({selector: '[ngIf]'})
|
||||
export class NgIf {
|
||||
private _prevCondition: boolean = null;
|
||||
|
||||
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<Object>) {
|
||||
}
|
||||
|
||||
set ngIf(newCondition: any /* boolean */) {
|
||||
@Input()
|
||||
set ngIf(newCondition: any) {
|
||||
if (newCondition && (isBlank(this._prevCondition) || !this._prevCondition)) {
|
||||
this._prevCondition = true;
|
||||
this._viewContainer.createEmbeddedView(this._templateRef);
|
||||
|
@ -1,21 +1,18 @@
|
||||
import {
|
||||
Directive,
|
||||
ViewContainerRef,
|
||||
TemplateRef,
|
||||
ContentChildren,
|
||||
QueryList,
|
||||
Attribute,
|
||||
AfterContentInit,
|
||||
Input
|
||||
} from '@angular/core';
|
||||
import {isPresent, NumberWrapper} from '../../src/facade/lang';
|
||||
import {Map} from '../../src/facade/collection';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Attribute, Directive, Host, Input, OnInit, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {NgLocalization, getPluralCategory} from '../localization';
|
||||
|
||||
import {SwitchView} from './ng_switch';
|
||||
|
||||
const _CATEGORY_DEFAULT = 'other';
|
||||
|
||||
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
|
||||
|
||||
/**
|
||||
* `ngPlural` is an i18n directive that displays DOM sub-trees that match the switch expression
|
||||
@ -32,9 +29,6 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
|
||||
* value matches aren't found and the value maps to its category using the `getPluralCategory`
|
||||
* function provided.
|
||||
*
|
||||
* If no matching views are found for a switch expression, inner elements marked
|
||||
* `[ngPluralCase]="other"` will be displayed.
|
||||
*
|
||||
* ```typescript
|
||||
* class MyLocalization extends NgLocalization {
|
||||
* getPluralCategory(value: any) {
|
||||
@ -46,7 +40,7 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'app',
|
||||
* providers: [provide(NgLocalization, {useClass: MyLocalization})]
|
||||
* providers: [{provide: NgLocalization, useClass: MyLocalization}]
|
||||
* })
|
||||
* @View({
|
||||
* template: `
|
||||
@ -71,25 +65,13 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
|
||||
@Directive({selector: '[ngPluralCase]'})
|
||||
export class NgPluralCase {
|
||||
/** @internal */
|
||||
_view: SwitchView;
|
||||
constructor(@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||
viewContainer: ViewContainerRef) {
|
||||
this._view = new SwitchView(viewContainer, template);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Directive({selector: '[ngPlural]'})
|
||||
export class NgPlural implements AfterContentInit {
|
||||
export class NgPlural {
|
||||
private _switchValue: number;
|
||||
private _activeView: SwitchView;
|
||||
private _caseViews = new Map<any, SwitchView>();
|
||||
@ContentChildren(NgPluralCase) cases: QueryList<NgPluralCase> = null;
|
||||
private _caseViews: {[k: string]: SwitchView} = {};
|
||||
|
||||
constructor(private _localization: NgLocalization) {}
|
||||
|
||||
@ -99,21 +81,15 @@ export class NgPlural implements AfterContentInit {
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.cases.forEach((pluralCase: NgPluralCase): void => {
|
||||
this._caseViews.set(this._formatValue(pluralCase), pluralCase._view);
|
||||
});
|
||||
this._updateView();
|
||||
}
|
||||
addCase(value: string, switchView: SwitchView): void { this._caseViews[value] = switchView; }
|
||||
|
||||
/** @internal */
|
||||
_updateView(): void {
|
||||
this._clearViews();
|
||||
|
||||
var view: SwitchView = this._caseViews.get(this._switchValue);
|
||||
if (!isPresent(view)) view = this._getCategoryView(this._switchValue);
|
||||
|
||||
this._activateView(view);
|
||||
var key =
|
||||
getPluralCategory(this._switchValue, Object.keys(this._caseViews), this._localization);
|
||||
this._activateView(this._caseViews[key]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -127,22 +103,16 @@ export class NgPlural implements AfterContentInit {
|
||||
this._activeView = view;
|
||||
this._activeView.create();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getCategoryView(value: number): SwitchView {
|
||||
var category: string = this._localization.getPluralCategory(value);
|
||||
var categoryView: SwitchView = this._caseViews.get(category);
|
||||
return isPresent(categoryView) ? categoryView : this._caseViews.get(_CATEGORY_DEFAULT);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_isValueView(pluralCase: NgPluralCase): boolean { return pluralCase.value[0] === "="; }
|
||||
|
||||
/** @internal */
|
||||
_formatValue(pluralCase: NgPluralCase): any {
|
||||
return this._isValueView(pluralCase) ? this._stripValue(pluralCase.value) : pluralCase.value;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_stripValue(value: string): number { return NumberWrapper.parseInt(value.substring(1), 10); }
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngPluralCase]'})
|
||||
export class NgPluralCase {
|
||||
constructor(
|
||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
||||
ngPlural.addCase(value, new SwitchView(viewContainer, template));
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
import {KeyValueChangeRecord} from '@angular/core';
|
||||
import {
|
||||
DoCheck,
|
||||
KeyValueDiffer,
|
||||
KeyValueDiffers,
|
||||
ElementRef,
|
||||
Directive,
|
||||
Renderer
|
||||
} from '@angular/core';
|
||||
import {isPresent, isBlank} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The `NgStyle` directive changes styles based on a result of expression evaluation.
|
||||
@ -18,7 +21,8 @@ import {isPresent, isBlank} from '../../src/facade/lang';
|
||||
*
|
||||
* ### Syntax
|
||||
*
|
||||
* - `<div [ngStyle]="{'font-style': style}"></div>`
|
||||
* - `<div [ngStyle]="{'font-style': styleExp}"></div>`
|
||||
* - `<div [ngStyle]="{'max-width.px': widthExp}"></div>`
|
||||
* - `<div [ngStyle]="styleExp"></div>` - here the `styleExp` must evaluate to an object
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/YamGS6GkUh9GqWNQhCyM?p=preview)):
|
||||
@ -59,27 +63,30 @@ import {isPresent, isBlank} from '../../src/facade/lang';
|
||||
*
|
||||
* In this example the `font-style`, `font-size` and `font-weight` styles will be updated
|
||||
* based on the `style` property's value changes.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngStyle]', inputs: ['rawStyle: ngStyle']})
|
||||
@Directive({selector: '[ngStyle]'})
|
||||
export class NgStyle implements DoCheck {
|
||||
/** @internal */
|
||||
_rawStyle: {[key: string]: string};
|
||||
_ngStyle: {[key: string]: string};
|
||||
/** @internal */
|
||||
_differ: KeyValueDiffer;
|
||||
|
||||
constructor(private _differs: KeyValueDiffers, private _ngEl: ElementRef,
|
||||
private _renderer: Renderer) {}
|
||||
constructor(
|
||||
private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer) {}
|
||||
|
||||
set rawStyle(v: {[key: string]: string}) {
|
||||
this._rawStyle = v;
|
||||
@Input()
|
||||
set ngStyle(v: {[key: string]: string}) {
|
||||
this._ngStyle = v;
|
||||
if (isBlank(this._differ) && isPresent(v)) {
|
||||
this._differ = this._differs.find(this._rawStyle).create(null);
|
||||
this._differ = this._differs.find(this._ngStyle).create(null);
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck() {
|
||||
if (isPresent(this._differ)) {
|
||||
var changes = this._differ.diff(this._rawStyle);
|
||||
var changes = this._differ.diff(this._ngStyle);
|
||||
if (isPresent(changes)) {
|
||||
this._applyChanges(changes);
|
||||
}
|
||||
@ -87,15 +94,19 @@ export class NgStyle implements DoCheck {
|
||||
}
|
||||
|
||||
private _applyChanges(changes: any): void {
|
||||
changes.forEachRemovedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, null); });
|
||||
changes.forEachAddedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); });
|
||||
changes.forEachChangedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); });
|
||||
changes.forEachRemovedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, null); });
|
||||
}
|
||||
|
||||
private _setStyle(name: string, val: string): void {
|
||||
this._renderer.setElementStyle(this._ngEl.nativeElement, name, val);
|
||||
const nameParts = name.split('.');
|
||||
const nameToSet = nameParts[0];
|
||||
const valToSet = isPresent(val) && nameParts.length === 2 ? `${val}${nameParts[1]}` : val;
|
||||
|
||||
this._renderer.setElementStyle(this._ngEl.nativeElement, nameToSet, valToSet);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,24 @@
|
||||
import {Directive, Host, ViewContainerRef, TemplateRef} from '@angular/core';
|
||||
import {isPresent, isBlank, normalizeBlank} from '../../src/facade/lang';
|
||||
import {ListWrapper, Map} from '../../src/facade/collection';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
const _WHEN_DEFAULT = /*@ts2dart_const*/ new Object();
|
||||
import {Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent, normalizeBlank} from '../facade/lang';
|
||||
|
||||
const _CASE_DEFAULT = new Object();
|
||||
|
||||
// TODO: remove when fully deprecated
|
||||
let _warned: boolean = false;
|
||||
|
||||
export class SwitchView {
|
||||
constructor(private _viewContainerRef: ViewContainerRef,
|
||||
private _templateRef: TemplateRef<Object>) {}
|
||||
constructor(
|
||||
private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef<Object>) {}
|
||||
|
||||
create(): void { this._viewContainerRef.createEmbeddedView(this._templateRef); }
|
||||
|
||||
@ -16,17 +28,17 @@ export class SwitchView {
|
||||
/**
|
||||
* Adds or removes DOM sub-trees when their match expressions match the switch expression.
|
||||
*
|
||||
* Elements within `NgSwitch` but without `NgSwitchWhen` or `NgSwitchDefault` directives will be
|
||||
* Elements within `NgSwitch` but without `NgSwitchCase` or `NgSwitchDefault` directives will be
|
||||
* preserved at the location as specified in the template.
|
||||
*
|
||||
* `NgSwitch` simply inserts nested elements based on which match expression matches the value
|
||||
* obtained from the evaluated switch expression. In other words, you define a container element
|
||||
* (where you place the directive with a switch expression on the
|
||||
* `[ngSwitch]="..."` attribute), define any inner elements inside of the directive and
|
||||
* place a `[ngSwitchWhen]` attribute per element.
|
||||
* place a `[ngSwitchCase]` attribute per element.
|
||||
*
|
||||
* The `ngSwitchWhen` property is used to inform `NgSwitch` which element to display when the
|
||||
* expression is evaluated. If a matching expression is not found via a `ngSwitchWhen` property
|
||||
* The `ngSwitchCase` property is used to inform `NgSwitch` which element to display when the
|
||||
* expression is evaluated. If a matching expression is not found via a `ngSwitchCase` property
|
||||
* then an element with the `ngSwitchDefault` attribute is displayed.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/DQMTII95CbuqWrl3lYAs?p=preview))
|
||||
@ -39,24 +51,24 @@ export class SwitchView {
|
||||
* <button (click)="inc()">Increment</button>
|
||||
*
|
||||
* <div [ngSwitch]="value">
|
||||
* <p *ngSwitchWhen="'init'">increment to start</p>
|
||||
* <p *ngSwitchWhen="0">0, increment again</p>
|
||||
* <p *ngSwitchWhen="1">1, increment again</p>
|
||||
* <p *ngSwitchWhen="2">2, stop incrementing</p>
|
||||
* <p *ngSwitchCase="'init'">increment to start</p>
|
||||
* <p *ngSwitchCase="0">0, increment again</p>
|
||||
* <p *ngSwitchCase="1">1, increment again</p>
|
||||
* <p *ngSwitchCase="2">2, stop incrementing</p>
|
||||
* <p *ngSwitchDefault>> 2, STOP!</p>
|
||||
* </div>
|
||||
*
|
||||
* <!-- alternate syntax -->
|
||||
*
|
||||
* <p [ngSwitch]="value">
|
||||
* <template ngSwitchWhen="init">increment to start</template>
|
||||
* <template [ngSwitchWhen]="0">0, increment again</template>
|
||||
* <template [ngSwitchWhen]="1">1, increment again</template>
|
||||
* <template [ngSwitchWhen]="2">2, stop incrementing</template>
|
||||
* <template ngSwitchCase="init">increment to start</template>
|
||||
* <template [ngSwitchCase]="0">0, increment again</template>
|
||||
* <template [ngSwitchCase]="1">1, increment again</template>
|
||||
* <template [ngSwitchCase]="2">2, stop incrementing</template>
|
||||
* <template ngSwitchDefault>> 2, STOP!</template>
|
||||
* </p>
|
||||
* `,
|
||||
* directives: [NgSwitch, NgSwitchWhen, NgSwitchDefault]
|
||||
* directives: [NgSwitch, NgSwitchCase, NgSwitchDefault]
|
||||
* })
|
||||
* export class App {
|
||||
* value = 'init';
|
||||
@ -68,14 +80,17 @@ export class SwitchView {
|
||||
*
|
||||
* bootstrap(App).catch(err => console.error(err));
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngSwitch]', inputs: ['ngSwitch']})
|
||||
@Directive({selector: '[ngSwitch]'})
|
||||
export class NgSwitch {
|
||||
private _switchValue: any;
|
||||
private _useDefault: boolean = false;
|
||||
private _valueViews = new Map<any, SwitchView[]>();
|
||||
private _activeViews: SwitchView[] = [];
|
||||
|
||||
@Input()
|
||||
set ngSwitch(value: any) {
|
||||
// Empty the currently active ViewContainers
|
||||
this._emptyAllActiveViews();
|
||||
@ -85,7 +100,7 @@ export class NgSwitch {
|
||||
var views = this._valueViews.get(value);
|
||||
if (isBlank(views)) {
|
||||
this._useDefault = true;
|
||||
views = normalizeBlank(this._valueViews.get(_WHEN_DEFAULT));
|
||||
views = normalizeBlank(this._valueViews.get(_CASE_DEFAULT));
|
||||
}
|
||||
this._activateViews(views);
|
||||
|
||||
@ -93,14 +108,14 @@ export class NgSwitch {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_onWhenValueChanged(oldWhen: any, newWhen: any, view: SwitchView): void {
|
||||
this._deregisterView(oldWhen, view);
|
||||
this._registerView(newWhen, view);
|
||||
_onCaseValueChanged(oldCase: any, newCase: any, view: SwitchView): void {
|
||||
this._deregisterView(oldCase, view);
|
||||
this._registerView(newCase, view);
|
||||
|
||||
if (oldWhen === this._switchValue) {
|
||||
if (oldCase === this._switchValue) {
|
||||
view.destroy();
|
||||
ListWrapper.remove(this._activeViews, view);
|
||||
} else if (newWhen === this._switchValue) {
|
||||
} else if (newCase === this._switchValue) {
|
||||
if (this._useDefault) {
|
||||
this._useDefault = false;
|
||||
this._emptyAllActiveViews();
|
||||
@ -112,7 +127,7 @@ export class NgSwitch {
|
||||
// Switch to default when there is no more active ViewContainers
|
||||
if (this._activeViews.length === 0 && !this._useDefault) {
|
||||
this._useDefault = true;
|
||||
this._activateViews(this._valueViews.get(_WHEN_DEFAULT));
|
||||
this._activateViews(this._valueViews.get(_CASE_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,8 +163,8 @@ export class NgSwitch {
|
||||
|
||||
/** @internal */
|
||||
_deregisterView(value: any, view: SwitchView): void {
|
||||
// `_WHEN_DEFAULT` is used a marker for non-registered whens
|
||||
if (value === _WHEN_DEFAULT) return;
|
||||
// `_CASE_DEFAULT` is used a marker for non-registered cases
|
||||
if (value === _CASE_DEFAULT) return;
|
||||
var views = this._valueViews.get(value);
|
||||
if (views.length == 1) {
|
||||
this._valueViews.delete(value);
|
||||
@ -160,30 +175,44 @@ export class NgSwitch {
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the sub-tree when the `ngSwitchWhen` expression evaluates to the same value as the
|
||||
* Insert the sub-tree when the `ngSwitchCase` expression evaluates to the same value as the
|
||||
* enclosing switch expression.
|
||||
*
|
||||
* If multiple match expression match the switch expression value, all of them are displayed.
|
||||
*
|
||||
* See {@link NgSwitch} for more details and example.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngSwitchWhen]', inputs: ['ngSwitchWhen']})
|
||||
export class NgSwitchWhen {
|
||||
// `_WHEN_DEFAULT` is used as a marker for a not yet initialized value
|
||||
@Directive({selector: '[ngSwitchCase],[ngSwitchWhen]'})
|
||||
export class NgSwitchCase {
|
||||
// `_CASE_DEFAULT` is used as a marker for a not yet initialized value
|
||||
/** @internal */
|
||||
_value: any = _WHEN_DEFAULT;
|
||||
_value: any = _CASE_DEFAULT;
|
||||
/** @internal */
|
||||
_view: SwitchView;
|
||||
private _switch: NgSwitch;
|
||||
|
||||
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
|
||||
@Host() ngSwitch: NgSwitch) {
|
||||
constructor(
|
||||
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
|
||||
@Host() ngSwitch: NgSwitch) {
|
||||
this._switch = ngSwitch;
|
||||
this._view = new SwitchView(viewContainer, templateRef);
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngSwitchCase(value: any) {
|
||||
this._switch._onCaseValueChanged(this._value, value, this._view);
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngSwitchWhen(value: any) {
|
||||
this._switch._onWhenValueChanged(this._value, value, this._view);
|
||||
if (!_warned) {
|
||||
_warned = true;
|
||||
console.warn('*ngSwitchWhen is deprecated and will be removed. Use *ngSwitchCase instead');
|
||||
}
|
||||
this._switch._onCaseValueChanged(this._value, value, this._view);
|
||||
this._value = value;
|
||||
}
|
||||
}
|
||||
@ -193,11 +222,14 @@ export class NgSwitchWhen {
|
||||
* value.
|
||||
*
|
||||
* See {@link NgSwitch} for more details and example.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngSwitchDefault]'})
|
||||
export class NgSwitchDefault {
|
||||
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
|
||||
@Host() sswitch: NgSwitch) {
|
||||
sswitch._registerView(_WHEN_DEFAULT, new SwitchView(viewContainer, templateRef));
|
||||
constructor(
|
||||
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
|
||||
@Host() sswitch: NgSwitch) {
|
||||
sswitch._registerView(_CASE_DEFAULT, new SwitchView(viewContainer, templateRef));
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,52 @@
|
||||
import {Directive, Input, ViewContainerRef, ViewRef, TemplateRef} from '@angular/core';
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, EmbeddedViewRef, Input, OnChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Creates and inserts an embedded view based on a prepared `TemplateRef`.
|
||||
* You can attach a context object to the `EmbeddedViewRef` by setting `[ngOutletContext]`.
|
||||
* `[ngOutletContext]` should be an object, the object's keys will be the local template variables
|
||||
* available within the `TemplateRef`.
|
||||
*
|
||||
* Note: using the key `$implicit` in the context object will set it's value as default.
|
||||
*
|
||||
* ### Syntax
|
||||
* - `<template [ngTemplateOutlet]="templateRefExpression"></template>`
|
||||
*
|
||||
* ```
|
||||
* <template [ngTemplateOutlet]="templateRefExpression"
|
||||
* [ngOutletContext]="objectExpression">
|
||||
* </template>
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngTemplateOutlet]'})
|
||||
export class NgTemplateOutlet {
|
||||
private _insertedViewRef: ViewRef;
|
||||
export class NgTemplateOutlet implements OnChanges {
|
||||
private _viewRef: EmbeddedViewRef<any>;
|
||||
private _context: Object;
|
||||
private _templateRef: TemplateRef<any>;
|
||||
|
||||
constructor(private _viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
@Input()
|
||||
set ngTemplateOutlet(templateRef: TemplateRef<Object>) {
|
||||
if (isPresent(this._insertedViewRef)) {
|
||||
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._insertedViewRef));
|
||||
set ngOutletContext(context: Object) { this._context = context; }
|
||||
|
||||
@Input()
|
||||
set ngTemplateOutlet(templateRef: TemplateRef<Object>) { this._templateRef = templateRef; }
|
||||
|
||||
ngOnChanges() {
|
||||
if (this._viewRef) {
|
||||
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef));
|
||||
}
|
||||
|
||||
if (isPresent(templateRef)) {
|
||||
this._insertedViewRef = this._viewContainerRef.createEmbeddedView(templateRef);
|
||||
if (this._templateRef) {
|
||||
this._viewRef = this._viewContainerRef.createEmbeddedView(this._templateRef, this._context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
library angular2.directives.observable_list_iterable_diff;
|
||||
|
||||
import 'package:observe/observe.dart' show ObservableList;
|
||||
import 'package:angular2/core.dart';
|
||||
import 'package:angular2/src/core/change_detection/differs/default_iterable_differ.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class ObservableListDiff extends DefaultIterableDiffer {
|
||||
ChangeDetectorRef _ref;
|
||||
ObservableListDiff(this._ref);
|
||||
|
||||
bool _updated = true;
|
||||
ObservableList _collection;
|
||||
StreamSubscription _subscription;
|
||||
|
||||
onDestroy() {
|
||||
if (this._subscription != null) {
|
||||
this._subscription.cancel();
|
||||
this._subscription = null;
|
||||
this._collection = null;
|
||||
}
|
||||
}
|
||||
|
||||
DefaultIterableDiffer diff(ObservableList collection) {
|
||||
if (collection is! ObservableList) {
|
||||
throw "Cannot change the type of a collection";
|
||||
}
|
||||
|
||||
// A new collection instance is passed in.
|
||||
// - We need to set up a listener.
|
||||
// - We need to diff collection.
|
||||
if (!identical(_collection, collection)) {
|
||||
_collection = collection;
|
||||
|
||||
if (_subscription != null) _subscription.cancel();
|
||||
_subscription = collection.changes.listen((_) {
|
||||
_updated = true;
|
||||
_ref.markForCheck();
|
||||
});
|
||||
_updated = false;
|
||||
return super.diff(collection);
|
||||
|
||||
// An update has been registered since the last change detection check.
|
||||
// - We reset the flag.
|
||||
// - We diff the collection.
|
||||
} else if (_updated) {
|
||||
_updated = false;
|
||||
return super.diff(collection);
|
||||
|
||||
// No updates has been registered.
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableListDiffFactory implements IterableDifferFactory {
|
||||
const ObservableListDiffFactory();
|
||||
bool supports(obj) => obj is ObservableList;
|
||||
IterableDiffer create(ChangeDetectorRef cdRef, [Function trackByFn]) {
|
||||
return new ObservableListDiff(cdRef);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
// TS does not have Observables
|
||||
|
||||
// I need to be here to make TypeScript think this is a module.
|
||||
import {} from '../../src/facade/lang';
|
||||
|
||||
/**
|
||||
* This module exists in Dart, but not in Typescript. This exported symbol
|
||||
* is only here to help Typescript think this is a module.
|
||||
*/
|
||||
export var workaround_empty_observable_list_diff: any;
|
75
modules/@angular/common/src/forms-deprecated.ts
Normal file
75
modules/@angular/common/src/forms-deprecated.ts
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* This module is used for handling user input, by defining and building a {@link ControlGroup} that
|
||||
* consists of
|
||||
* {@link Control} objects, and mapping them onto the DOM. {@link Control} objects can then be used
|
||||
* to read information
|
||||
* from the form DOM elements.
|
||||
*
|
||||
* Forms providers are not included in default providers; you must import these providers
|
||||
* explicitly.
|
||||
*/
|
||||
import {NgModule, Type} from '@angular/core';
|
||||
|
||||
import {FORM_DIRECTIVES} from './forms-deprecated/directives';
|
||||
import {RadioControlRegistry} from './forms-deprecated/directives/radio_control_value_accessor';
|
||||
import {FormBuilder} from './forms-deprecated/form_builder';
|
||||
|
||||
export {FORM_DIRECTIVES, RadioButtonState} from './forms-deprecated/directives';
|
||||
export {AbstractControlDirective} from './forms-deprecated/directives/abstract_control_directive';
|
||||
export {CheckboxControlValueAccessor} from './forms-deprecated/directives/checkbox_value_accessor';
|
||||
export {ControlContainer} from './forms-deprecated/directives/control_container';
|
||||
export {ControlValueAccessor, NG_VALUE_ACCESSOR} from './forms-deprecated/directives/control_value_accessor';
|
||||
export {DefaultValueAccessor} from './forms-deprecated/directives/default_value_accessor';
|
||||
export {Form} from './forms-deprecated/directives/form_interface';
|
||||
export {NgControl} from './forms-deprecated/directives/ng_control';
|
||||
export {NgControlGroup} from './forms-deprecated/directives/ng_control_group';
|
||||
export {NgControlName} from './forms-deprecated/directives/ng_control_name';
|
||||
export {NgControlStatus} from './forms-deprecated/directives/ng_control_status';
|
||||
export {NgForm} from './forms-deprecated/directives/ng_form';
|
||||
export {NgFormControl} from './forms-deprecated/directives/ng_form_control';
|
||||
export {NgFormModel} from './forms-deprecated/directives/ng_form_model';
|
||||
export {NgModel} from './forms-deprecated/directives/ng_model';
|
||||
export {NgSelectOption, SelectControlValueAccessor} from './forms-deprecated/directives/select_control_value_accessor';
|
||||
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator} from './forms-deprecated/directives/validators';
|
||||
export {FormBuilder} from './forms-deprecated/form_builder';
|
||||
export {AbstractControl, Control, ControlArray, ControlGroup} from './forms-deprecated/model';
|
||||
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './forms-deprecated/validators';
|
||||
|
||||
|
||||
/**
|
||||
* Shorthand set of providers used for building Angular forms.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* bootstrap(MyApp, [FORM_PROVIDERS]);
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const FORM_PROVIDERS: Type[] = [FormBuilder, RadioControlRegistry];
|
||||
|
||||
|
||||
/**
|
||||
* The ng module for the deprecated forms API.
|
||||
* @deprecated
|
||||
*/
|
||||
@NgModule({
|
||||
providers: [
|
||||
FORM_PROVIDERS,
|
||||
],
|
||||
declarations: FORM_DIRECTIVES,
|
||||
exports: FORM_DIRECTIVES
|
||||
})
|
||||
export class DeprecatedFormsModule {
|
||||
}
|
@ -1,52 +1,45 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import {NgControlName} from './directives/ng_control_name';
|
||||
import {NgFormControl} from './directives/ng_form_control';
|
||||
import {NgModel} from './directives/ng_model';
|
||||
import {NgControlGroup} from './directives/ng_control_group';
|
||||
import {NgFormModel} from './directives/ng_form_model';
|
||||
import {NgForm} from './directives/ng_form';
|
||||
import {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
|
||||
import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
import {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
import {NgControlGroup} from './directives/ng_control_group';
|
||||
import {NgControlName} from './directives/ng_control_name';
|
||||
import {NgControlStatus} from './directives/ng_control_status';
|
||||
import {NgForm} from './directives/ng_form';
|
||||
import {NgFormControl} from './directives/ng_form_control';
|
||||
import {NgFormModel} from './directives/ng_form_model';
|
||||
import {NgModel} from './directives/ng_model';
|
||||
import {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
import {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
import {NgControlStatus} from './directives/ng_control_status';
|
||||
import {
|
||||
SelectControlValueAccessor,
|
||||
NgSelectOption
|
||||
} from './directives/select_control_value_accessor';
|
||||
import {
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
PatternValidator
|
||||
} from './directives/validators';
|
||||
import {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||
import {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||
import {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
||||
|
||||
export {NgControlName} from './directives/ng_control_name';
|
||||
export {NgFormControl} from './directives/ng_form_control';
|
||||
export {NgModel} from './directives/ng_model';
|
||||
export {NgControlGroup} from './directives/ng_control_group';
|
||||
export {NgFormModel} from './directives/ng_form_model';
|
||||
export {NgForm} from './directives/ng_form';
|
||||
export {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
export {
|
||||
RadioControlValueAccessor,
|
||||
RadioButtonState
|
||||
} from './directives/radio_control_value_accessor';
|
||||
export {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
export {NgControlStatus} from './directives/ng_control_status';
|
||||
export {
|
||||
SelectControlValueAccessor,
|
||||
NgSelectOption
|
||||
} from './directives/select_control_value_accessor';
|
||||
export {
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
PatternValidator
|
||||
} from './directives/validators';
|
||||
export {NgControl} from './directives/ng_control';
|
||||
export {ControlValueAccessor} from './directives/control_value_accessor';
|
||||
export {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
export {NgControl} from './directives/ng_control';
|
||||
export {NgControlGroup} from './directives/ng_control_group';
|
||||
export {NgControlName} from './directives/ng_control_name';
|
||||
export {NgControlStatus} from './directives/ng_control_status';
|
||||
export {NgForm} from './directives/ng_form';
|
||||
export {NgFormControl} from './directives/ng_form_control';
|
||||
export {NgFormModel} from './directives/ng_form_model';
|
||||
export {NgModel} from './directives/ng_model';
|
||||
export {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
export {RadioButtonState, RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||
export {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
@ -63,8 +56,9 @@ export {ControlValueAccessor} from './directives/control_value_accessor';
|
||||
* })
|
||||
* class MyApp {}
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
export const FORM_DIRECTIVES: Type[] = /*@ts2dart_const*/[
|
||||
export const FORM_DIRECTIVES: Type[] = [
|
||||
NgControlName,
|
||||
NgControlGroup,
|
||||
|
||||
@ -74,15 +68,17 @@ export const FORM_DIRECTIVES: Type[] = /*@ts2dart_const*/[
|
||||
NgForm,
|
||||
|
||||
NgSelectOption,
|
||||
NgSelectMultipleOption,
|
||||
DefaultValueAccessor,
|
||||
NumberValueAccessor,
|
||||
CheckboxControlValueAccessor,
|
||||
SelectControlValueAccessor,
|
||||
SelectMultipleControlValueAccessor,
|
||||
RadioControlValueAccessor,
|
||||
NgControlStatus,
|
||||
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
PatternValidator
|
||||
PatternValidator,
|
||||
];
|
@ -1,11 +1,22 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {unimplemented} from '../../facade/exceptions';
|
||||
import {isPresent} from '../../facade/lang';
|
||||
import {AbstractControl} from '../model';
|
||||
import {isPresent} from '../../../src/facade/lang';
|
||||
import {unimplemented} from '../../../src/facade/exceptions';
|
||||
|
||||
|
||||
/**
|
||||
* Base class for control directives.
|
||||
*
|
||||
* Only used internally in the forms module.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class AbstractControlDirective {
|
||||
get control(): AbstractControl { return unimplemented(); }
|
@ -1,7 +1,16 @@
|
||||
import {Directive, Renderer, ElementRef, Self, forwardRef, Provider} from '@angular/core';
|
||||
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export const CHECKBOX_VALUE_ACCESSOR: any = /*@ts2dart_const*/ {
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const CHECKBOX_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => CheckboxControlValueAccessor),
|
||||
multi: true
|
||||
@ -14,6 +23,8 @@ export const CHECKBOX_VALUE_ACCESSOR: any = /*@ts2dart_const*/ {
|
||||
* ```
|
||||
* <input type="checkbox" ngControl="rememberLogin">
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
@ -1,10 +1,21 @@
|
||||
import {Form} from './form_interface';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {Form} from './form_interface';
|
||||
|
||||
|
||||
/**
|
||||
* A directive that contains multiple {@link NgControl}s.
|
||||
*
|
||||
* Only used by the forms module.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class ControlContainer extends AbstractControlDirective {
|
||||
name: string;
|
@ -1,3 +1,11 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
|
||||
/**
|
||||
@ -7,6 +15,8 @@ import {OpaqueToken} from '@angular/core';
|
||||
* DOM element representing an input control.
|
||||
*
|
||||
* Please see {@link DefaultValueAccessor} for more information.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface ControlValueAccessor {
|
||||
/**
|
||||
@ -29,6 +39,6 @@ export interface ControlValueAccessor {
|
||||
* Used to provide a {@link ControlValueAccessor} for form controls.
|
||||
*
|
||||
* See {@link DefaultValueAccessor} for how to implement one.
|
||||
* @experimental
|
||||
*/
|
||||
export const NG_VALUE_ACCESSOR: OpaqueToken =
|
||||
/*@ts2dart_const*/ new OpaqueToken("NgValueAccessor");
|
||||
export const NG_VALUE_ACCESSOR: OpaqueToken = new OpaqueToken('NgValueAccessor');
|
@ -1,13 +1,22 @@
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
import {isBlank} from '../../../src/facade/lang';
|
||||
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export const DEFAULT_VALUE_ACCESSOR: any = /*@ts2dart_const*/
|
||||
/* @ts2dart_Provider */ {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DefaultValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {isBlank} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const DEFAULT_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DefaultValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The default accessor for writing a value and listening to changes that is used by the
|
||||
@ -17,6 +26,8 @@ export const DEFAULT_VALUE_ACCESSOR: any = /*@ts2dart_const*/
|
||||
* ```
|
||||
* <input type="text" ngControl="searchQuery">
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
@ -25,7 +36,7 @@ export const DEFAULT_VALUE_ACCESSOR: any = /*@ts2dart_const*/
|
||||
// https://github.com/angular/angular/issues/3011 is implemented
|
||||
// selector: '[ngControl],[ngModel],[ngFormControl]',
|
||||
host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
|
||||
bindings: [DEFAULT_VALUE_ACCESSOR]
|
||||
providers: [DEFAULT_VALUE_ACCESSOR]
|
||||
})
|
||||
export class DefaultValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_: any) => {};
|
@ -1,11 +1,23 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Control, ControlGroup} from '../model';
|
||||
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
import {Control, ControlGroup} from '../model';
|
||||
|
||||
|
||||
/**
|
||||
* An interface that {@link NgFormModel} and {@link NgForm} implement.
|
||||
*
|
||||
* Only used by the forms module.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface Form {
|
||||
/**
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {unimplemented} from '../../facade/exceptions';
|
||||
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
|
||||
/**
|
||||
* A base class that all control directive extend.
|
||||
* It binds a {@link Control} object to a DOM element.
|
||||
*
|
||||
* Used internally by Angular forms.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class NgControl extends AbstractControlDirective {
|
||||
name: string = null;
|
||||
valueAccessor: ControlValueAccessor = null;
|
||||
|
||||
get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); }
|
||||
get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); }
|
||||
|
||||
abstract viewToModelUpdate(newValue: any): void;
|
||||
}
|
@ -1,27 +1,25 @@
|
||||
import {
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Directive,
|
||||
Optional,
|
||||
Inject,
|
||||
Host,
|
||||
SkipSelf,
|
||||
forwardRef,
|
||||
Provider,
|
||||
Self
|
||||
} from '@angular/core';
|
||||
import {ControlContainer} from './control_container';
|
||||
import {controlPath, composeValidators, composeAsyncValidators} from './shared';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Host, Inject, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core';
|
||||
|
||||
import {ControlGroup} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {Form} from './form_interface';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {composeAsyncValidators, composeValidators, controlPath} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
export const controlGroupProvider: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => NgControlGroup)
|
||||
};
|
||||
export const controlGroupProvider: any = {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => NgControlGroup)
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and binds a control group to a DOM element.
|
||||
@ -33,12 +31,11 @@ export const controlGroupProvider: any =
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>Angular Control & ControlGroup Example</h2>
|
||||
* <form #f="ngForm">
|
||||
* <div ngControlGroup="name" #cg-name="form">
|
||||
* <div ngControlGroup="name" #cgName="ngForm">
|
||||
* <h3>Enter your name:</h3>
|
||||
* <p>First: <input ngControl="first" required></p>
|
||||
* <p>Middle: <input ngControl="middle"></p>
|
||||
@ -67,6 +64,8 @@ export const controlGroupProvider: any =
|
||||
*
|
||||
* This example declares a control group for a user's name. The value and validation state of
|
||||
* this group can be accessed separately from the overall form.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngControlGroup]',
|
||||
@ -79,9 +78,10 @@ export class NgControlGroup extends ControlContainer implements OnInit,
|
||||
/** @internal */
|
||||
_parent: ControlContainer;
|
||||
|
||||
constructor(@Host() @SkipSelf() parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
|
||||
constructor(
|
||||
@Host() @SkipSelf() parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
|
||||
super();
|
||||
this._parent = parent;
|
||||
}
|
@ -1,39 +1,28 @@
|
||||
import {
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
SimpleChange,
|
||||
Query,
|
||||
Directive,
|
||||
forwardRef,
|
||||
Host,
|
||||
SkipSelf,
|
||||
Provider,
|
||||
Inject,
|
||||
Optional,
|
||||
Self
|
||||
} from '@angular/core';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {EventEmitter, ObservableWrapper} from '../../../src/facade/async';
|
||||
import {ControlContainer} from './control_container';
|
||||
import {NgControl} from './ng_control';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {
|
||||
controlPath,
|
||||
composeValidators,
|
||||
composeAsyncValidators,
|
||||
isPropertyUpdated,
|
||||
selectValueAccessor
|
||||
} from './shared';
|
||||
import {Directive, Host, Inject, OnChanges, OnDestroy, Optional, Self, SimpleChanges, SkipSelf, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {Control} from '../model';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
|
||||
export const controlNameBinding: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgControlName)
|
||||
};
|
||||
export const controlNameBinding: any = {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgControlName)
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and binds a control with a specified name to a DOM element.
|
||||
@ -52,7 +41,7 @@ export const controlNameBinding: any =
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: `
|
||||
* <form #f="ngForm" (submit)='onLogIn(f.value)'>
|
||||
* Login <input type='text' ngControl='login' #l="form">
|
||||
* Login <input type='text' ngControl='login' #l="ngForm">
|
||||
* <div *ngIf="!l.valid">Login is invalid</div>
|
||||
*
|
||||
* Password <input type='password' ngControl='password'>
|
||||
@ -89,10 +78,12 @@ export const controlNameBinding: any =
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngControl]',
|
||||
bindings: [controlNameBinding],
|
||||
providers: [controlNameBinding],
|
||||
inputs: ['name: ngControl', 'model: ngModel'],
|
||||
outputs: ['update: ngModelChange'],
|
||||
exportAs: 'ngForm'
|
||||
@ -112,35 +103,37 @@ export class NgControlName extends NgControl implements OnChanges,
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: {[key: string]: SimpleChange}) {
|
||||
if (!this._added) {
|
||||
this.formDirective.addControl(this);
|
||||
this._added = true;
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.viewModel = this.model;
|
||||
this.formDirective.updateModel(this, this.model);
|
||||
}
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!this._added) {
|
||||
this.formDirective.addControl(this);
|
||||
this._added = true;
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.viewModel = this.model;
|
||||
this.formDirective.updateModel(this, this.model);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this.formDirective.removeControl(this); }
|
||||
ngOnDestroy(): void { this.formDirective.removeControl(this); }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
ObservableWrapper.callEmit(this.update, newValue);
|
||||
}
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
this.update.emit(newValue);
|
||||
}
|
||||
|
||||
get path(): string[] { return controlPath(this.name, this._parent); }
|
||||
get path(): string[] { return controlPath(this.name, this._parent); }
|
||||
|
||||
get formDirective(): any { return this._parent.formDirective; }
|
||||
get formDirective(): any { return this._parent.formDirective; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
|
||||
get control(): Control { return this.formDirective.getControl(this); }
|
||||
get control(): Control { return this.formDirective.getControl(this); }
|
||||
}
|
@ -1,10 +1,23 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Self} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../../facade/lang';
|
||||
|
||||
import {NgControl} from './ng_control';
|
||||
import {isPresent} from '../../../src/facade/lang';
|
||||
|
||||
|
||||
/**
|
||||
* Directive automatically applied to Angular forms that sets CSS classes
|
||||
* based on control status (valid/invalid/dirty/etc).
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngControl],[ngModel],[ngFormControl]',
|
@ -1,22 +1,33 @@
|
||||
import {Directive, forwardRef, Provider, Optional, Inject, Self} from '@angular/core';
|
||||
import {
|
||||
PromiseWrapper,
|
||||
ObservableWrapper,
|
||||
EventEmitter,
|
||||
PromiseCompleter
|
||||
} from '../../../src/facade/async';
|
||||
import {ListWrapper} from '../../../src/facade/collection';
|
||||
import {isPresent} from '../../../src/facade/lang';
|
||||
import {NgControl} from './ng_control';
|
||||
import {Form} from './form_interface';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
import {ControlContainer} from './control_container';
|
||||
import {AbstractControl, ControlGroup, Control} from '../model';
|
||||
import {setUpControl, setUpControlGroup, composeValidators, composeAsyncValidators} from './shared';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export const formDirectiveProvider: any =
|
||||
/*@ts2dart_const*/ {provide: ControlContainer, useExisting: forwardRef(() => NgForm)};
|
||||
import {Directive, Inject, Optional, Self, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import {isPresent} from '../../facade/lang';
|
||||
import {AbstractControl, Control, ControlGroup} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {Form} from './form_interface';
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
import {composeAsyncValidators, composeValidators, setUpControl, setUpControlGroup} from './shared';
|
||||
|
||||
export const formDirectiveProvider: any = {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => NgForm)
|
||||
};
|
||||
|
||||
let _formWarningDisplayed: boolean = false;
|
||||
|
||||
const resolvedPromise = Promise.resolve(null);
|
||||
|
||||
/**
|
||||
* If `NgForm` is bound in a component, `<form>` elements in that component will be
|
||||
@ -24,7 +35,7 @@ export const formDirectiveProvider: any =
|
||||
*
|
||||
* ### Typical Use
|
||||
*
|
||||
* Include `FORM_DIRECTIVES` in the `directives` section of a {@link View} annotation
|
||||
* Include `FORM_DIRECTIVES` in the `directives` section of a {@link Component} annotation
|
||||
* to use `NgForm` and its associated controls.
|
||||
*
|
||||
* ### Structure
|
||||
@ -76,10 +87,13 @@ export const formDirectiveProvider: any =
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
|
||||
@Directive({
|
||||
selector: 'form:not([ngNoForm]):not([ngFormModel]),ngForm,[ngForm]',
|
||||
bindings: [formDirectiveProvider],
|
||||
providers: [formDirectiveProvider],
|
||||
host: {
|
||||
'(submit)': 'onSubmit()',
|
||||
},
|
||||
@ -87,16 +101,34 @@ export const formDirectiveProvider: any =
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgForm extends ControlContainer implements Form {
|
||||
private _submitted: boolean = false;
|
||||
|
||||
form: ControlGroup;
|
||||
ngSubmit = new EventEmitter();
|
||||
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
|
||||
constructor(
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
|
||||
super();
|
||||
this.form = new ControlGroup({}, null, composeValidators(validators),
|
||||
composeAsyncValidators(asyncValidators));
|
||||
this._displayWarning();
|
||||
this.form = new ControlGroup(
|
||||
{}, null, composeValidators(validators), composeAsyncValidators(asyncValidators));
|
||||
}
|
||||
|
||||
private _displayWarning() {
|
||||
// TODO(kara): Update this when the new forms module becomes the default
|
||||
if (!_formWarningDisplayed) {
|
||||
_formWarningDisplayed = true;
|
||||
console.warn(`
|
||||
*It looks like you're using the old forms module. This will be opt-in in the next RC, and
|
||||
will eventually be removed in favor of the new forms module. For more information, see:
|
||||
https://docs.google.com/document/d/1RIezQqE4aEhBRmArIAS1mRIZtWFf6JxN_7B4meyWK0Y/preview
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
get submitted(): boolean { return this._submitted; }
|
||||
|
||||
get formDirective(): Form { return this; }
|
||||
|
||||
get control(): ControlGroup { return this.form; }
|
||||
@ -106,11 +138,11 @@ export class NgForm extends ControlContainer implements Form {
|
||||
get controls(): {[key: string]: AbstractControl} { return this.form.controls; }
|
||||
|
||||
addControl(dir: NgControl): void {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
var ctrl = new Control();
|
||||
setUpControl(ctrl, dir);
|
||||
container.addControl(dir.name, ctrl);
|
||||
container.registerControl(dir.name, ctrl);
|
||||
ctrl.updateValueAndValidity({emitEvent: false});
|
||||
});
|
||||
}
|
||||
@ -118,31 +150,29 @@ export class NgForm extends ControlContainer implements Form {
|
||||
getControl(dir: NgControl): Control { return <Control>this.form.find(dir.path); }
|
||||
|
||||
removeControl(dir: NgControl): void {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
container.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addControlGroup(dir: NgControlGroup): void {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
var group = new ControlGroup({});
|
||||
setUpControlGroup(group, dir);
|
||||
container.addControl(dir.name, group);
|
||||
container.registerControl(dir.name, group);
|
||||
group.updateValueAndValidity({emitEvent: false});
|
||||
});
|
||||
}
|
||||
|
||||
removeControlGroup(dir: NgControlGroup): void {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
container.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -152,14 +182,15 @@ export class NgForm extends ControlContainer implements Form {
|
||||
}
|
||||
|
||||
updateModel(dir: NgControl, value: any): void {
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
resolvedPromise.then(() => {
|
||||
var ctrl = <Control>this.form.find(dir.path);
|
||||
ctrl.updateValue(value);
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(): boolean {
|
||||
ObservableWrapper.callEmit(this.ngSubmit, null);
|
||||
this._submitted = true;
|
||||
this.ngSubmit.emit(null);
|
||||
return false;
|
||||
}
|
||||
|
@ -1,36 +1,27 @@
|
||||
import {
|
||||
OnChanges,
|
||||
SimpleChange,
|
||||
Query,
|
||||
Directive,
|
||||
forwardRef,
|
||||
Provider,
|
||||
Inject,
|
||||
Optional,
|
||||
Self
|
||||
} from '@angular/core';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StringMapWrapper} from '../../../src/facade/collection';
|
||||
import {EventEmitter, ObservableWrapper} from '../../../src/facade/async';
|
||||
import {Directive, Inject, OnChanges, Optional, Self, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {NgControl} from './ng_control';
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {StringMapWrapper} from '../../facade/collection';
|
||||
import {Control} from '../model';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {
|
||||
setUpControl,
|
||||
composeValidators,
|
||||
composeAsyncValidators,
|
||||
isPropertyUpdated,
|
||||
selectValueAccessor
|
||||
} from './shared';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
export const formControlBinding: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgFormControl)
|
||||
};
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
export const formControlBinding: any = {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgFormControl)
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds an existing {@link Control} to a DOM element.
|
||||
@ -78,10 +69,12 @@ export const formControlBinding: any =
|
||||
* login:string;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngFormControl]',
|
||||
bindings: [formControlBinding],
|
||||
providers: [formControlBinding],
|
||||
inputs: ['form: ngFormControl', 'model: ngModel'],
|
||||
outputs: ['update: ngModelChange'],
|
||||
exportAs: 'ngForm'
|
||||
@ -98,35 +91,37 @@ export class NgFormControl extends NgControl implements OnChanges {
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: {[key: string]: SimpleChange}): void {
|
||||
if (this._isControlChanged(changes)) {
|
||||
setUpControl(this.form, this);
|
||||
this.form.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.form.updateValue(this.model);
|
||||
this.viewModel = this.model;
|
||||
}
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (this._isControlChanged(changes)) {
|
||||
setUpControl(this.form, this);
|
||||
this.form.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.form.updateValue(this.model);
|
||||
this.viewModel = this.model;
|
||||
}
|
||||
}
|
||||
|
||||
get path(): string[] { return []; }
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
|
||||
get control(): Control { return this.form; }
|
||||
get control(): Control { return this.form; }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
ObservableWrapper.callEmit(this.update, newValue);
|
||||
}
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
this.update.emit(newValue);
|
||||
}
|
||||
|
||||
private _isControlChanged(changes: {[key: string]: any}): boolean {
|
||||
return StringMapWrapper.contains(changes, "form");
|
||||
}
|
||||
private _isControlChanged(changes: {[key: string]: any}): boolean {
|
||||
return StringMapWrapper.contains(changes, 'form');
|
||||
}
|
||||
}
|
@ -1,30 +1,32 @@
|
||||
import {
|
||||
SimpleChange,
|
||||
OnChanges,
|
||||
Directive,
|
||||
forwardRef,
|
||||
Provider,
|
||||
Inject,
|
||||
Optional,
|
||||
Self
|
||||
} from '@angular/core';
|
||||
import {isBlank} from '../../../src/facade/lang';
|
||||
import {ListWrapper, StringMapWrapper} from '../../../src/facade/collection';
|
||||
import {BaseException} from '../../../src/facade/exceptions';
|
||||
import {ObservableWrapper, EventEmitter} from '../../../src/facade/async';
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Inject, OnChanges, Optional, Self, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {ListWrapper, StringMapWrapper} from '../../facade/collection';
|
||||
import {BaseException} from '../../facade/exceptions';
|
||||
import {isBlank} from '../../facade/lang';
|
||||
import {Control, ControlGroup} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {Form} from './form_interface';
|
||||
import {Control, ControlGroup} from '../model';
|
||||
import {setUpControl, setUpControlGroup, composeValidators, composeAsyncValidators} from './shared';
|
||||
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
import {composeAsyncValidators, composeValidators, setUpControl, setUpControlGroup} from './shared';
|
||||
|
||||
export const formDirectiveProvider: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => NgFormModel)
|
||||
};
|
||||
export const formDirectiveProvider: any = {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => NgFormModel)
|
||||
};
|
||||
|
||||
let _formModelWarningDisplayed: boolean = false;
|
||||
|
||||
/**
|
||||
* Binds an existing control group to a DOM element.
|
||||
@ -97,10 +99,13 @@ export const formDirectiveProvider: any =
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
|
||||
@Directive({
|
||||
selector: '[ngFormModel]',
|
||||
bindings: [formDirectiveProvider],
|
||||
providers: [formDirectiveProvider],
|
||||
inputs: ['form: ngFormModel'],
|
||||
host: {'(submit)': 'onSubmit()'},
|
||||
outputs: ['ngSubmit'],
|
||||
@ -108,18 +113,34 @@ export const formDirectiveProvider: any =
|
||||
})
|
||||
export class NgFormModel extends ControlContainer implements Form,
|
||||
OnChanges {
|
||||
private _submitted: boolean = false;
|
||||
|
||||
form: ControlGroup = null;
|
||||
directives: NgControl[] = [];
|
||||
ngSubmit = new EventEmitter();
|
||||
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
|
||||
constructor(
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
|
||||
super();
|
||||
this._displayWarning();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: {[key: string]: SimpleChange}): void {
|
||||
private _displayWarning() {
|
||||
// TODO(kara): Update this when the new forms module becomes the default
|
||||
if (!_formModelWarningDisplayed) {
|
||||
_formModelWarningDisplayed = true;
|
||||
console.warn(`
|
||||
*It looks like you're using the old forms module. This will be opt-in in the next RC, and
|
||||
will eventually be removed in favor of the new forms module. For more information, see:
|
||||
https://docs.google.com/document/d/1RIezQqE4aEhBRmArIAS1mRIZtWFf6JxN_7B4meyWK0Y/preview
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this._checkFormPresent();
|
||||
if (StringMapWrapper.contains(changes, "form")) {
|
||||
if (StringMapWrapper.contains(changes, 'form')) {
|
||||
var sync = composeValidators(this._validators);
|
||||
this.form.validator = Validators.compose([this.form.validator, sync]);
|
||||
|
||||
@ -132,6 +153,8 @@ export class NgFormModel extends ControlContainer implements Form,
|
||||
this._updateDomValue();
|
||||
}
|
||||
|
||||
get submitted(): boolean { return this._submitted; }
|
||||
|
||||
get formDirective(): Form { return this; }
|
||||
|
||||
get control(): ControlGroup { return this.form; }
|
||||
@ -167,7 +190,8 @@ export class NgFormModel extends ControlContainer implements Form,
|
||||
}
|
||||
|
||||
onSubmit(): boolean {
|
||||
ObservableWrapper.callEmit(this.ngSubmit, null);
|
||||
this._submitted = true;
|
||||
this.ngSubmit.emit(null);
|
||||
return false;
|
||||
}
|
||||
|
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Inject, OnChanges, Optional, Self, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {Control} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
export const formControlBinding: any = {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgModel)
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds a domain model to a form control.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* `ngModel` binds an existing domain model to a form control. For a
|
||||
* two-way binding, use `[(ngModel)]` to ensure the model updates in
|
||||
* both directions.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/R3UX5qDaUqFO2VYR0UzH?p=preview))
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: "search-comp",
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: `<input type='text' [(ngModel)]="searchQuery">`
|
||||
* })
|
||||
* class SearchComp {
|
||||
* searchQuery: string;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngModel]:not([ngControl]):not([ngFormControl])',
|
||||
providers: [formControlBinding],
|
||||
inputs: ['model: ngModel'],
|
||||
outputs: ['update: ngModelChange'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgModel extends NgControl implements OnChanges {
|
||||
/** @internal */
|
||||
_control = new Control();
|
||||
/** @internal */
|
||||
_added = false;
|
||||
update = new EventEmitter();
|
||||
model: any;
|
||||
viewModel: any;
|
||||
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!this._added) {
|
||||
setUpControl(this._control, this);
|
||||
this._control.updateValueAndValidity({emitEvent: false});
|
||||
this._added = true;
|
||||
}
|
||||
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this._control.updateValue(this.model);
|
||||
this.viewModel = this.model;
|
||||
}
|
||||
}
|
||||
|
||||
get control(): Control { return this._control; }
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
this.update.emit(newValue);
|
||||
}
|
||||
}
|
@ -1,5 +1,14 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbstractControl} from '../model';
|
||||
import {Validator, ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
|
||||
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
||||
|
||||
export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
@ -11,7 +20,7 @@ export function normalizeValidator(validator: ValidatorFn | Validator): Validato
|
||||
|
||||
export function normalizeAsyncValidator(validator: AsyncValidatorFn | Validator): AsyncValidatorFn {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
return (c: AbstractControl) => Promise.resolve((<Validator>validator).validate(c));
|
||||
return (c: AbstractControl) => (<Validator>validator).validate(c);
|
||||
} else {
|
||||
return <AsyncValidatorFn>validator;
|
||||
}
|
@ -1,8 +1,18 @@
|
||||
import {Directive, ElementRef, Renderer, Self, forwardRef, Provider} from '@angular/core';
|
||||
import {NumberWrapper} from '../../../src/facade/lang';
|
||||
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export const NUMBER_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {NumberWrapper, isBlank} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const NUMBER_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => NumberValueAccessor),
|
||||
multi: true
|
||||
@ -25,7 +35,7 @@ export const NUMBER_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider
|
||||
'(input)': 'onChange($event.target.value)',
|
||||
'(blur)': 'onTouched()'
|
||||
},
|
||||
bindings: [NUMBER_VALUE_ACCESSOR]
|
||||
providers: [NUMBER_VALUE_ACCESSOR]
|
||||
})
|
||||
export class NumberValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_: any) => {};
|
||||
@ -34,7 +44,9 @@ export class NumberValueAccessor implements ControlValueAccessor {
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: number): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', value);
|
||||
// The value needs to be normalized for IE9, otherwise it is set to 'null' when null
|
||||
const normalizedValue = isBlank(value) ? '' : value;
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
|
||||
}
|
||||
|
||||
registerOnChange(fn: (_: number) => void): void {
|
@ -1,21 +1,20 @@
|
||||
import {
|
||||
Directive,
|
||||
ElementRef,
|
||||
Renderer,
|
||||
forwardRef,
|
||||
Provider,
|
||||
Input,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Injector,
|
||||
Injectable
|
||||
} from '@angular/core';
|
||||
import {isPresent} from '../../../src/facade/lang';
|
||||
import {ListWrapper} from '../../../src/facade/collection';
|
||||
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Injectable, Injector, Input, OnDestroy, OnInit, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import {isPresent} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
|
||||
export const RADIO_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
export const RADIO_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => RadioControlValueAccessor),
|
||||
multi: true
|
||||
@ -44,15 +43,23 @@ export class RadioControlRegistry {
|
||||
|
||||
select(accessor: RadioControlValueAccessor) {
|
||||
this._accessors.forEach((c) => {
|
||||
if (c[0].control.root === accessor._control.control.root && c[1] !== accessor) {
|
||||
if (this._isSameGroup(c, accessor) && c[1] !== accessor) {
|
||||
c[1].fireUncheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _isSameGroup(
|
||||
controlPair: [NgControl, RadioControlValueAccessor], accessor: RadioControlValueAccessor) {
|
||||
return controlPair[0].control.root === accessor._control.control.root &&
|
||||
controlPair[1].name === accessor.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The value provided by the forms API for radio buttons.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class RadioButtonState {
|
||||
constructor(public checked: boolean, public value: string) {}
|
||||
@ -95,8 +102,9 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
|
||||
onChange = () => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef,
|
||||
private _registry: RadioControlRegistry, private _injector: Injector) {}
|
||||
constructor(
|
||||
private _renderer: Renderer, private _elementRef: ElementRef,
|
||||
private _registry: RadioControlRegistry, private _injector: Injector) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._control = this._injector.get(NgControl);
|
@ -1,26 +1,19 @@
|
||||
import {
|
||||
Directive,
|
||||
Renderer,
|
||||
forwardRef,
|
||||
Provider,
|
||||
ElementRef,
|
||||
Input,
|
||||
Host,
|
||||
OnDestroy,
|
||||
Optional
|
||||
} from '@angular/core';
|
||||
import {
|
||||
StringWrapper,
|
||||
isPrimitive,
|
||||
isPresent,
|
||||
isBlank,
|
||||
looseIdentical
|
||||
} from '../../../src/facade/lang';
|
||||
import {MapWrapper} from '../../../src/facade/collection';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
export const SELECT_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
import {MapWrapper} from '../../facade/collection';
|
||||
import {StringWrapper, isBlank, isPresent, isPrimitive, looseIdentical} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const SELECT_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectControlValueAccessor),
|
||||
multi: true
|
||||
@ -28,12 +21,12 @@ export const SELECT_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider
|
||||
|
||||
function _buildValueString(id: string, value: any): string {
|
||||
if (isBlank(id)) return `${value}`;
|
||||
if (!isPrimitive(value)) value = "Object";
|
||||
if (!isPrimitive(value)) value = 'Object';
|
||||
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
|
||||
}
|
||||
|
||||
function _extractId(valueString: string): string {
|
||||
return valueString.split(":")[0];
|
||||
return valueString.split(':')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,9 +37,11 @@ function _extractId(valueString: string): string {
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1024350
|
||||
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4660045/
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'select[ngControl],select[ngFormControl],select[ngModel]',
|
||||
selector:
|
||||
'select:not([multiple])[ngControl],select:not([multiple])[ngFormControl],select:not([multiple])[ngModel]',
|
||||
host: {'(change)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
|
||||
providers: [SELECT_VALUE_ACCESSOR]
|
||||
})
|
||||
@ -69,7 +64,10 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => any): void {
|
||||
this.onChange = (valueString: string) => { fn(this._getOptionValue(valueString)); };
|
||||
this.onChange = (valueString: string) => {
|
||||
this.value = valueString;
|
||||
fn(this._getOptionValue(valueString));
|
||||
};
|
||||
}
|
||||
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
|
||||
|
||||
@ -101,13 +99,16 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
* <option *ngFor="let c of cities" [value]="c"></option>
|
||||
* </select>
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: 'option'})
|
||||
export class NgSelectOption implements OnDestroy {
|
||||
id: string;
|
||||
|
||||
constructor(private _element: ElementRef, private _renderer: Renderer,
|
||||
@Optional() @Host() private _select: SelectControlValueAccessor) {
|
||||
constructor(
|
||||
private _element: ElementRef, private _renderer: Renderer,
|
||||
@Optional() @Host() private _select: SelectControlValueAccessor) {
|
||||
if (isPresent(this._select)) this.id = this._select._registerOption();
|
||||
}
|
||||
|
@ -0,0 +1,185 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core';
|
||||
|
||||
import {MapWrapper} from '../../facade/collection';
|
||||
import {StringWrapper, isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const SELECT_MULTIPLE_VALUE_ACCESSOR = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
function _buildValueString(id: string, value: any): string {
|
||||
if (isBlank(id)) return `${value}`;
|
||||
if (isString(value)) value = `'${value}'`;
|
||||
if (!isPrimitive(value)) value = 'Object';
|
||||
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
|
||||
}
|
||||
|
||||
function _extractId(valueString: string): string {
|
||||
return valueString.split(':')[0];
|
||||
}
|
||||
|
||||
/** Mock interface for HTML Options */
|
||||
interface HTMLOption {
|
||||
value: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
/** Mock interface for HTMLCollection */
|
||||
abstract class HTMLCollection {
|
||||
length: number;
|
||||
abstract item(_: number): HTMLOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* The accessor for writing a value and listening to changes on a select element.
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'select[multiple][ngControl],select[multiple][ngFormControl],select[multiple][ngModel]',
|
||||
host: {'(change)': 'onChange($event.target)', '(blur)': 'onTouched()'},
|
||||
providers: [SELECT_MULTIPLE_VALUE_ACCESSOR]
|
||||
})
|
||||
export class SelectMultipleControlValueAccessor implements ControlValueAccessor {
|
||||
value: any;
|
||||
/** @internal */
|
||||
_optionMap: Map<string, NgSelectMultipleOption> = new Map<string, NgSelectMultipleOption>();
|
||||
/** @internal */
|
||||
_idCounter: number = 0;
|
||||
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor() {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.value = value;
|
||||
if (value == null) return;
|
||||
let values: Array<any> = <Array<any>>value;
|
||||
// convert values to ids
|
||||
let ids = values.map((v) => this._getOptionId(v));
|
||||
this._optionMap.forEach((opt, o) => { opt._setSelected(ids.indexOf(o.toString()) > -1); });
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => any): void {
|
||||
this.onChange = (_: any) => {
|
||||
let selected: Array<any> = [];
|
||||
if (_.hasOwnProperty('selectedOptions')) {
|
||||
let options: HTMLCollection = _.selectedOptions;
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
let opt: any = options.item(i);
|
||||
let val: any = this._getOptionValue(opt.value);
|
||||
selected.push(val);
|
||||
}
|
||||
}
|
||||
// Degrade on IE
|
||||
else {
|
||||
let options: HTMLCollection = <HTMLCollection>_.options;
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
let opt: HTMLOption = options.item(i);
|
||||
if (opt.selected) {
|
||||
let val: any = this._getOptionValue(opt.value);
|
||||
selected.push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn(selected);
|
||||
};
|
||||
}
|
||||
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
|
||||
|
||||
/** @internal */
|
||||
_registerOption(value: NgSelectMultipleOption): string {
|
||||
let id: string = (this._idCounter++).toString();
|
||||
this._optionMap.set(id, value);
|
||||
return id;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getOptionId(value: any): string {
|
||||
for (let id of MapWrapper.keys(this._optionMap)) {
|
||||
if (looseIdentical(this._optionMap.get(id)._value, value)) return id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getOptionValue(valueString: string): any {
|
||||
let opt = this._optionMap.get(_extractId(valueString));
|
||||
return isPresent(opt) ? opt._value : valueString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks `<option>` as dynamic, so Angular can be notified when options change.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <select multiple ngControl="city">
|
||||
* <option *ngFor="let c of cities" [value]="c"></option>
|
||||
* </select>
|
||||
* ```
|
||||
*/
|
||||
@Directive({selector: 'option'})
|
||||
export class NgSelectMultipleOption implements OnDestroy {
|
||||
id: string;
|
||||
/** @internal */
|
||||
_value: any;
|
||||
|
||||
constructor(
|
||||
private _element: ElementRef, private _renderer: Renderer,
|
||||
@Optional() @Host() private _select: SelectMultipleControlValueAccessor) {
|
||||
if (isPresent(this._select)) {
|
||||
this.id = this._select._registerOption(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Input('ngValue')
|
||||
set ngValue(value: any) {
|
||||
if (this._select == null) return;
|
||||
this._value = value;
|
||||
this._setElementValue(_buildValueString(this.id, value));
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
|
||||
@Input('value')
|
||||
set value(value: any) {
|
||||
if (isPresent(this._select)) {
|
||||
this._value = value;
|
||||
this._setElementValue(_buildValueString(this.id, value));
|
||||
this._select.writeValue(this._select.value);
|
||||
} else {
|
||||
this._setElementValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setElementValue(value: string): void {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setSelected(selected: boolean) {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (isPresent(this._select)) {
|
||||
this._select._optionMap.delete(this.id);
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const SELECT_DIRECTIVES = [SelectMultipleControlValueAccessor, NgSelectMultipleOption];
|
@ -1,21 +1,30 @@
|
||||
import {ListWrapper, StringMapWrapper} from '../../../src/facade/collection';
|
||||
import {isBlank, isPresent, looseIdentical, hasConstructor} from '../../../src/facade/lang';
|
||||
import {BaseException} from '../../../src/facade/exceptions';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {NgControl} from './ng_control';
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
import {ListWrapper, StringMapWrapper} from '../../facade/collection';
|
||||
import {BaseException} from '../../facade/exceptions';
|
||||
import {hasConstructor, isBlank, isPresent, looseIdentical} from '../../facade/lang';
|
||||
import {Control, ControlGroup} from '../model';
|
||||
import {Validators} from '../validators';
|
||||
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
|
||||
import {ControlContainer} from './control_container';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {DefaultValueAccessor} from './default_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
import {normalizeAsyncValidator, normalizeValidator} from './normalize_validator';
|
||||
import {NumberValueAccessor} from './number_value_accessor';
|
||||
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
|
||||
import {SelectControlValueAccessor} from './select_control_value_accessor';
|
||||
import {RadioControlValueAccessor} from './radio_control_value_accessor';
|
||||
import {normalizeValidator, normalizeAsyncValidator} from './normalize_validator';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
import {SelectControlValueAccessor} from './select_control_value_accessor';
|
||||
import {SelectMultipleControlValueAccessor} from './select_multiple_control_value_accessor';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
|
||||
export function controlPath(name: string, parent: ControlContainer): string[] {
|
||||
@ -25,8 +34,8 @@ export function controlPath(name: string, parent: ControlContainer): string[] {
|
||||
}
|
||||
|
||||
export function setUpControl(control: Control, dir: NgControl): void {
|
||||
if (isBlank(control)) _throwError(dir, "Cannot find control");
|
||||
if (isBlank(dir.valueAccessor)) _throwError(dir, "No value accessor for");
|
||||
if (isBlank(control)) _throwError(dir, 'Cannot find control with');
|
||||
if (isBlank(dir.valueAccessor)) _throwError(dir, 'No value accessor for form control with');
|
||||
|
||||
control.validator = Validators.compose([control.validator, dir.validator]);
|
||||
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
||||
@ -47,37 +56,44 @@ export function setUpControl(control: Control, dir: NgControl): void {
|
||||
}
|
||||
|
||||
export function setUpControlGroup(control: ControlGroup, dir: NgControlGroup) {
|
||||
if (isBlank(control)) _throwError(dir, "Cannot find control");
|
||||
if (isBlank(control)) _throwError(dir, 'Cannot find control with');
|
||||
control.validator = Validators.compose([control.validator, dir.validator]);
|
||||
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
||||
}
|
||||
|
||||
function _throwError(dir: AbstractControlDirective, message: string): void {
|
||||
var path = dir.path.join(" -> ");
|
||||
throw new BaseException(`${message} '${path}'`);
|
||||
let messageEnd: string;
|
||||
if (dir.path.length > 1) {
|
||||
messageEnd = `path: '${dir.path.join(' -> ')}'`;
|
||||
} else if (dir.path[0]) {
|
||||
messageEnd = `name: '${dir.path}'`;
|
||||
} else {
|
||||
messageEnd = 'unspecified name';
|
||||
}
|
||||
throw new BaseException(`${message} ${messageEnd}`);
|
||||
}
|
||||
|
||||
export function composeValidators(validators: /* Array<Validator|Function> */ any[]): ValidatorFn {
|
||||
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) : null;
|
||||
}
|
||||
|
||||
export function composeAsyncValidators(
|
||||
validators: /* Array<Validator|Function> */ any[]): AsyncValidatorFn {
|
||||
export function composeAsyncValidators(validators: /* Array<Validator|Function> */ any[]):
|
||||
AsyncValidatorFn {
|
||||
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) :
|
||||
null;
|
||||
}
|
||||
|
||||
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
|
||||
if (!StringMapWrapper.contains(changes, "model")) return false;
|
||||
var change = changes["model"];
|
||||
if (!StringMapWrapper.contains(changes, 'model')) return false;
|
||||
var change = changes['model'];
|
||||
|
||||
if (change.isFirstChange()) return true;
|
||||
return !looseIdentical(viewModel, change.currentValue);
|
||||
}
|
||||
|
||||
// TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented
|
||||
export function selectValueAccessor(dir: NgControl,
|
||||
valueAccessors: ControlValueAccessor[]): ControlValueAccessor {
|
||||
export function selectValueAccessor(
|
||||
dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor {
|
||||
if (isBlank(valueAccessors)) return null;
|
||||
|
||||
var defaultAccessor: ControlValueAccessor;
|
||||
@ -87,17 +103,18 @@ export function selectValueAccessor(dir: NgControl,
|
||||
if (hasConstructor(v, DefaultValueAccessor)) {
|
||||
defaultAccessor = v;
|
||||
|
||||
} else if (hasConstructor(v, CheckboxControlValueAccessor) ||
|
||||
hasConstructor(v, NumberValueAccessor) ||
|
||||
hasConstructor(v, SelectControlValueAccessor) ||
|
||||
hasConstructor(v, RadioControlValueAccessor)) {
|
||||
} else if (
|
||||
hasConstructor(v, CheckboxControlValueAccessor) || hasConstructor(v, NumberValueAccessor) ||
|
||||
hasConstructor(v, SelectControlValueAccessor) ||
|
||||
hasConstructor(v, SelectMultipleControlValueAccessor) ||
|
||||
hasConstructor(v, RadioControlValueAccessor)) {
|
||||
if (isPresent(builtinAccessor))
|
||||
_throwError(dir, "More than one built-in value accessor matches");
|
||||
_throwError(dir, 'More than one built-in value accessor matches form control with');
|
||||
builtinAccessor = v;
|
||||
|
||||
} else {
|
||||
if (isPresent(customAccessor))
|
||||
_throwError(dir, "More than one custom value accessor matches");
|
||||
_throwError(dir, 'More than one custom value accessor matches form control with');
|
||||
customAccessor = v;
|
||||
}
|
||||
});
|
||||
@ -106,6 +123,6 @@ export function selectValueAccessor(dir: NgControl,
|
||||
if (isPresent(builtinAccessor)) return builtinAccessor;
|
||||
if (isPresent(defaultAccessor)) return defaultAccessor;
|
||||
|
||||
_throwError(dir, "No valid value accessor for");
|
||||
_throwError(dir, 'No valid value accessor for form control with');
|
||||
return null;
|
||||
}
|
@ -1,8 +1,16 @@
|
||||
import {forwardRef, Attribute, Directive} from '@angular/core';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Attribute, Directive, forwardRef} from '@angular/core';
|
||||
|
||||
import {NumberWrapper} from '../../facade/lang';
|
||||
import {Validators, NG_VALIDATORS} from '../validators';
|
||||
import {AbstractControl} from '../model';
|
||||
import * as modelModule from '../model';
|
||||
import {NG_VALIDATORS, Validators} from '../validators';
|
||||
|
||||
|
||||
|
||||
@ -14,7 +22,7 @@ import * as modelModule from '../model';
|
||||
* ```typescript
|
||||
* @Directive({
|
||||
* selector: '[custom-validator]',
|
||||
* providers: [provide(NG_VALIDATORS, {useExisting: CustomValidatorDirective, multi: true})]
|
||||
* providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}]
|
||||
* })
|
||||
* class CustomValidatorDirective implements Validator {
|
||||
* validate(c: Control): {[key: string]: any} {
|
||||
@ -22,12 +30,14 @@ import * as modelModule from '../model';
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface Validator { validate(c: modelModule.AbstractControl): {[key: string]: any}; }
|
||||
export interface Validator { validate(c: AbstractControl): {[key: string]: any}; }
|
||||
|
||||
const REQUIRED = /*@ts2dart_const*/ Validators.required;
|
||||
export const REQUIRED = Validators.required;
|
||||
|
||||
export const REQUIRED_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
export const REQUIRED_VALIDATOR: any = {
|
||||
provide: NG_VALIDATORS,
|
||||
useValue: REQUIRED,
|
||||
multi: true
|
||||
@ -42,6 +52,8 @@ export const REQUIRED_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/
|
||||
* ```
|
||||
* <input ngControl="fullName" required>
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[required][ngControl],[required][ngFormControl],[required][ngModel]',
|
||||
@ -62,7 +74,7 @@ export interface AsyncValidatorFn {
|
||||
*
|
||||
* {@example common/forms/ts/validators/validators.ts region='min'}
|
||||
*/
|
||||
export const MIN_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
export const MIN_LENGTH_VALIDATOR: any = {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => MinLengthValidator),
|
||||
multi: true
|
||||
@ -71,6 +83,8 @@ export const MIN_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*
|
||||
/**
|
||||
* A directive which installs the {@link MinLengthValidator} for any `ngControl`,
|
||||
* `ngFormControl`, or control with `ngModel` that also has a `minlength` attribute.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[minlength][ngControl],[minlength][ngFormControl],[minlength][ngModel]',
|
||||
@ -79,7 +93,7 @@ export const MIN_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*
|
||||
export class MinLengthValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute("minlength") minLength: string) {
|
||||
constructor(@Attribute('minlength') minLength: string) {
|
||||
this._validator = Validators.minLength(NumberWrapper.parseInt(minLength, 10));
|
||||
}
|
||||
|
||||
@ -93,7 +107,7 @@ export class MinLengthValidator implements Validator {
|
||||
*
|
||||
* {@example common/forms/ts/validators/validators.ts region='max'}
|
||||
*/
|
||||
export const MAX_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
export const MAX_LENGTH_VALIDATOR: any = {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => MaxLengthValidator),
|
||||
multi: true
|
||||
@ -102,6 +116,8 @@ export const MAX_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*
|
||||
/**
|
||||
* A directive which installs the {@link MaxLengthValidator} for any `ngControl, `ngFormControl`,
|
||||
* or control with `ngModel` that also has a `maxlength` attribute.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[maxlength][ngControl],[maxlength][ngFormControl],[maxlength][ngModel]',
|
||||
@ -110,7 +126,7 @@ export const MAX_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*
|
||||
export class MaxLengthValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute("maxlength") maxLength: string) {
|
||||
constructor(@Attribute('maxlength') maxLength: string) {
|
||||
this._validator = Validators.maxLength(NumberWrapper.parseInt(maxLength, 10));
|
||||
}
|
||||
|
||||
@ -118,6 +134,13 @@ export class MaxLengthValidator implements Validator {
|
||||
}
|
||||
|
||||
|
||||
export const PATTERN_VALIDATOR: any = {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => PatternValidator),
|
||||
multi: true
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A Directive that adds the `pattern` validator to any controls marked with the
|
||||
* `pattern` attribute, via the {@link NG_VALIDATORS} binding. Uses attribute value
|
||||
@ -129,12 +152,8 @@ export class MaxLengthValidator implements Validator {
|
||||
* ```
|
||||
* <input [ngControl]="fullName" pattern="[a-zA-Z ]*">
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
export const PATTERN_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => PatternValidator),
|
||||
multi: true
|
||||
};
|
||||
@Directive({
|
||||
selector: '[pattern][ngControl],[pattern][ngFormControl],[pattern][ngModel]',
|
||||
providers: [PATTERN_VALIDATOR]
|
||||
@ -142,7 +161,7 @@ export const PATTERN_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
|
||||
export class PatternValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute("pattern") pattern: string) {
|
||||
constructor(@Attribute('pattern') pattern: string) {
|
||||
this._validator = Validators.pattern(pattern);
|
||||
}
|
||||
|
@ -1,8 +1,19 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
import {isPresent, isArray} from '../../src/facade/lang';
|
||||
import * as modelModule from './model';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isArray, isPresent} from '../facade/lang';
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {AbstractControl, Control, ControlArray, ControlGroup} from './model';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@ -13,7 +24,7 @@ import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* viewBindings: [FORM_BINDINGS]
|
||||
* viewProviders: [FORM_BINDINGS]
|
||||
* template: `
|
||||
* <form [ngFormModel]="loginForm">
|
||||
* <p>Login <input ngControl="login"></p>
|
||||
@ -45,6 +56,8 @@ import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Injectable()
|
||||
export class FormBuilder {
|
||||
@ -54,38 +67,37 @@ export class FormBuilder {
|
||||
*
|
||||
* See the {@link ControlGroup} constructor for more details.
|
||||
*/
|
||||
group(controlsConfig: {[key: string]: any},
|
||||
extra: {[key: string]: any} = null): modelModule.ControlGroup {
|
||||
group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): ControlGroup {
|
||||
var controls = this._reduceControls(controlsConfig);
|
||||
var optionals = <{[key: string]: boolean}>(
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null);
|
||||
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null;
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, 'optionals') : null);
|
||||
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, 'validator') : null;
|
||||
var asyncValidator: AsyncValidatorFn =
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, "asyncValidator") : null;
|
||||
return new modelModule.ControlGroup(controls, optionals, validator, asyncValidator);
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, 'asyncValidator') : null;
|
||||
return new ControlGroup(controls, optionals, validator, asyncValidator);
|
||||
}
|
||||
/**
|
||||
* Construct a new {@link Control} with the given `value`,`validator`, and `asyncValidator`.
|
||||
*/
|
||||
control(value: Object, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null): modelModule.Control {
|
||||
return new modelModule.Control(value, validator, asyncValidator);
|
||||
control(value: Object, validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null):
|
||||
Control {
|
||||
return new Control(value, validator, asyncValidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an array of {@link Control}s from the given `controlsConfig` array of
|
||||
* configuration, with the given optional `validator` and `asyncValidator`.
|
||||
*/
|
||||
array(controlsConfig: any[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null): modelModule.ControlArray {
|
||||
array(
|
||||
controlsConfig: any[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null): ControlArray {
|
||||
var controls = controlsConfig.map(c => this._createControl(c));
|
||||
return new modelModule.ControlArray(controls, validator, asyncValidator);
|
||||
return new ControlArray(controls, validator, asyncValidator);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceControls(controlsConfig: {[k: string]:
|
||||
any}): {[key: string]: modelModule.AbstractControl} {
|
||||
var controls: {[key: string]: modelModule.AbstractControl} = {};
|
||||
_reduceControls(controlsConfig: {[k: string]: any}): {[key: string]: AbstractControl} {
|
||||
var controls: {[key: string]: AbstractControl} = {};
|
||||
StringMapWrapper.forEach(controlsConfig, (controlConfig: any, controlName: string) => {
|
||||
controls[controlName] = this._createControl(controlConfig);
|
||||
});
|
||||
@ -93,10 +105,9 @@ export class FormBuilder {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_createControl(controlConfig: any): modelModule.AbstractControl {
|
||||
if (controlConfig instanceof modelModule.Control ||
|
||||
controlConfig instanceof modelModule.ControlGroup ||
|
||||
controlConfig instanceof modelModule.ControlArray) {
|
||||
_createControl(controlConfig: any): AbstractControl {
|
||||
if (controlConfig instanceof Control || controlConfig instanceof ControlGroup ||
|
||||
controlConfig instanceof ControlArray) {
|
||||
return controlConfig;
|
||||
|
||||
} else if (isArray(controlConfig)) {
|
@ -1,56 +1,66 @@
|
||||
import {isPresent, isBlank, normalizeBool} from '../../src/facade/lang';
|
||||
import {Observable, EventEmitter, ObservableWrapper} from '../../src/facade/async';
|
||||
import {PromiseWrapper} from '../../src/facade/promise';
|
||||
import {StringMapWrapper, ListWrapper} from '../../src/facade/collection';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {PromiseObservable} from 'rxjs/observable/PromiseObservable';
|
||||
|
||||
import {EventEmitter, Observable} from '../facade/async';
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent, isPromise, normalizeBool} from '../facade/lang';
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that a Control is valid, i.e. that no errors exist in the input value.
|
||||
*/
|
||||
export const VALID = "VALID";
|
||||
export const VALID = 'VALID';
|
||||
|
||||
/**
|
||||
* Indicates that a Control is invalid, i.e. that an error exists in the input value.
|
||||
*/
|
||||
export const INVALID = "INVALID";
|
||||
export const INVALID = 'INVALID';
|
||||
|
||||
/**
|
||||
* Indicates that a Control is pending, i.e. that async validation is occurring and
|
||||
* errors are not yet available for the input value.
|
||||
*/
|
||||
export const PENDING = "PENDING";
|
||||
export const PENDING = 'PENDING';
|
||||
|
||||
export function isControl(control: Object): boolean {
|
||||
return control instanceof AbstractControl;
|
||||
}
|
||||
|
||||
function _find(control: AbstractControl, path: Array<string | number>| string) {
|
||||
function _find(control: AbstractControl, path: Array<string|number>| string) {
|
||||
if (isBlank(path)) return null;
|
||||
|
||||
if (!(path instanceof Array)) {
|
||||
path = (<string>path).split("/");
|
||||
path = (<string>path).split('/');
|
||||
}
|
||||
if (path instanceof Array && ListWrapper.isEmpty(path)) return null;
|
||||
|
||||
return (<Array<string | number>>path)
|
||||
.reduce((v, name) => {
|
||||
if (v instanceof ControlGroup) {
|
||||
return isPresent(v.controls[name]) ? v.controls[name] : null;
|
||||
} else if (v instanceof ControlArray) {
|
||||
var index = <number>name;
|
||||
return isPresent(v.at(index)) ? v.at(index) : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, control);
|
||||
return (<Array<string|number>>path).reduce((v, name) => {
|
||||
if (v instanceof ControlGroup) {
|
||||
return isPresent(v.controls[name]) ? v.controls[name] : null;
|
||||
} else if (v instanceof ControlArray) {
|
||||
var index = <number>name;
|
||||
return isPresent(v.at(index)) ? v.at(index) : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, control);
|
||||
}
|
||||
|
||||
function toObservable(r: any): Observable<any> {
|
||||
return PromiseWrapper.isPromise(r) ? ObservableWrapper.fromPromise(r) : r;
|
||||
return isPromise(r) ? PromiseObservable.create(r) : r;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class AbstractControl {
|
||||
/** @internal */
|
||||
@ -62,7 +72,7 @@ export abstract class AbstractControl {
|
||||
private _errors: {[key: string]: any};
|
||||
private _pristine: boolean = true;
|
||||
private _touched: boolean = false;
|
||||
private _parent: ControlGroup | ControlArray;
|
||||
private _parent: ControlGroup|ControlArray;
|
||||
private _asyncValidationSubscription: any;
|
||||
|
||||
constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {}
|
||||
@ -112,10 +122,10 @@ export abstract class AbstractControl {
|
||||
}
|
||||
}
|
||||
|
||||
setParent(parent: ControlGroup | ControlArray): void { this._parent = parent; }
|
||||
setParent(parent: ControlGroup|ControlArray): void { this._parent = parent; }
|
||||
|
||||
updateValueAndValidity(
|
||||
{onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
||||
void {
|
||||
onlySelf = normalizeBool(onlySelf);
|
||||
emitEvent = isPresent(emitEvent) ? emitEvent : true;
|
||||
|
||||
@ -129,8 +139,8 @@ export abstract class AbstractControl {
|
||||
}
|
||||
|
||||
if (emitEvent) {
|
||||
ObservableWrapper.callEmit(this._valueChanges, this._value);
|
||||
ObservableWrapper.callEmit(this._statusChanges, this._status);
|
||||
this._valueChanges.emit(this._value);
|
||||
this._statusChanges.emit(this._status);
|
||||
}
|
||||
|
||||
if (isPresent(this._parent) && !onlySelf) {
|
||||
@ -147,14 +157,14 @@ export abstract class AbstractControl {
|
||||
this._status = PENDING;
|
||||
this._cancelExistingSubscription();
|
||||
var obs = toObservable(this.asyncValidator(this));
|
||||
this._asyncValidationSubscription = ObservableWrapper.subscribe(
|
||||
obs, (res: {[key: string]: any}) => this.setErrors(res, {emitEvent: emitEvent}));
|
||||
this._asyncValidationSubscription = obs.subscribe(
|
||||
{next: (res: {[key: string]: any}) => this.setErrors(res, {emitEvent: emitEvent})});
|
||||
}
|
||||
}
|
||||
|
||||
private _cancelExistingSubscription(): void {
|
||||
if (isPresent(this._asyncValidationSubscription)) {
|
||||
ObservableWrapper.dispose(this._asyncValidationSubscription);
|
||||
this._asyncValidationSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +198,7 @@ export abstract class AbstractControl {
|
||||
this._status = this._calculateStatus();
|
||||
|
||||
if (emitEvent) {
|
||||
ObservableWrapper.callEmit(this._statusChanges, this._status);
|
||||
this._statusChanges.emit(this._status);
|
||||
}
|
||||
|
||||
if (isPresent(this._parent)) {
|
||||
@ -196,7 +206,7 @@ export abstract class AbstractControl {
|
||||
}
|
||||
}
|
||||
|
||||
find(path: Array<string | number>| string): AbstractControl { return _find(this, path); }
|
||||
find(path: Array<string|number>|string): AbstractControl { return _find(this, path); }
|
||||
|
||||
getError(errorCode: string, path: string[] = null): any {
|
||||
var control = isPresent(path) && !ListWrapper.isEmpty(path) ? this.find(path) : this;
|
||||
@ -266,13 +276,15 @@ export abstract class AbstractControl {
|
||||
* validation function.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class Control extends AbstractControl {
|
||||
/** @internal */
|
||||
_onChange: Function;
|
||||
|
||||
constructor(value: any = null, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
constructor(
|
||||
value: any = null, validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._value = value;
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
@ -331,13 +343,15 @@ export class Control extends AbstractControl {
|
||||
* controls, but is of variable length.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class ControlGroup extends AbstractControl {
|
||||
private _optionals: {[key: string]: boolean};
|
||||
|
||||
constructor(public controls: {[key: string]: AbstractControl},
|
||||
optionals: {[key: string]: boolean} = null, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
constructor(
|
||||
public controls: {[key: string]: AbstractControl}, optionals: {[key: string]: boolean} = null,
|
||||
validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._optionals = isPresent(optionals) ? optionals : {};
|
||||
this._initObservables();
|
||||
@ -346,17 +360,28 @@ export class ControlGroup extends AbstractControl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a control to this group.
|
||||
* Register a control with the group's list of controls.
|
||||
*/
|
||||
addControl(name: string, control: AbstractControl): void {
|
||||
registerControl(name: string, control: AbstractControl): void {
|
||||
this.controls[name] = control;
|
||||
control.setParent(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a control to this group.
|
||||
*/
|
||||
addControl(name: string, control: AbstractControl): void {
|
||||
this.registerControl(name, control);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a control from this group.
|
||||
*/
|
||||
removeControl(name: string): void { StringMapWrapper.delete(this.controls, name); }
|
||||
removeControl(name: string): void {
|
||||
StringMapWrapper.delete(this.controls, name);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the named control as non-optional.
|
||||
@ -448,10 +473,13 @@ export class ControlGroup extends AbstractControl {
|
||||
* as broken change detection.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class ControlArray extends AbstractControl {
|
||||
constructor(public controls: AbstractControl[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
constructor(
|
||||
public controls: AbstractControl[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._initObservables();
|
||||
this._setParentForControls();
|
@ -1,11 +1,20 @@
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
import {toPromise} from 'rxjs/operator/toPromise';
|
||||
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent, isPromise, isString} from '../facade/lang';
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {AbstractControl} from './model';
|
||||
|
||||
import {isBlank, isPresent, isString} from '../../src/facade/lang';
|
||||
import {PromiseWrapper} from '../../src/facade/promise';
|
||||
import {ObservableWrapper} from '../../src/facade/async';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
import * as modelModule from './model';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
|
||||
/**
|
||||
* Providers for validators to be used for {@link Control}s in a form.
|
||||
@ -15,8 +24,9 @@ import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/forms/ts/ng_validators/ng_validators.ts region='ng_validators'}
|
||||
* @experimental
|
||||
*/
|
||||
export const NG_VALIDATORS: OpaqueToken = /*@ts2dart_const*/ new OpaqueToken("NgValidators");
|
||||
export const NG_VALIDATORS: OpaqueToken = new OpaqueToken('NgValidators');
|
||||
|
||||
/**
|
||||
* Providers for asynchronous validators to be used for {@link Control}s
|
||||
@ -25,9 +35,10 @@ export const NG_VALIDATORS: OpaqueToken = /*@ts2dart_const*/ new OpaqueToken("Ng
|
||||
* Provide this using `multi: true` to add validators.
|
||||
*
|
||||
* See {@link NG_VALIDATORS} for more details.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const NG_ASYNC_VALIDATORS: OpaqueToken =
|
||||
/*@ts2dart_const*/ new OpaqueToken("NgAsyncValidators");
|
||||
export const NG_ASYNC_VALIDATORS: OpaqueToken = new OpaqueToken('NgAsyncValidators');
|
||||
|
||||
/**
|
||||
* Provides a set of validators used by form controls.
|
||||
@ -40,27 +51,29 @@ export const NG_ASYNC_VALIDATORS: OpaqueToken =
|
||||
* ```typescript
|
||||
* var loginControl = new Control("", Validators.required)
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a non-empty value.
|
||||
*/
|
||||
static required(control: modelModule.AbstractControl): {[key: string]: boolean} {
|
||||
return isBlank(control.value) || (isString(control.value) && control.value == "") ?
|
||||
{"required": true} :
|
||||
null;
|
||||
static required(control: AbstractControl): {[key: string]: boolean} {
|
||||
return isBlank(control.value) || (isString(control.value) && control.value == '') ?
|
||||
{'required': true} :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires controls to have a value of a minimum length.
|
||||
*/
|
||||
static minLength(minLength: number): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length < minLength ?
|
||||
{"minlength": {"requiredLength": minLength, "actualLength": v.length}} :
|
||||
null;
|
||||
{'minlength': {'requiredLength': minLength, 'actualLength': v.length}} :
|
||||
null;
|
||||
};
|
||||
}
|
||||
|
||||
@ -68,12 +81,12 @@ export class Validators {
|
||||
* Validator that requires controls to have a value of a maximum length.
|
||||
*/
|
||||
static maxLength(maxLength: number): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length > maxLength ?
|
||||
{"maxlength": {"requiredLength": maxLength, "actualLength": v.length}} :
|
||||
null;
|
||||
{'maxlength': {'requiredLength': maxLength, 'actualLength': v.length}} :
|
||||
null;
|
||||
};
|
||||
}
|
||||
|
||||
@ -81,19 +94,19 @@ export class Validators {
|
||||
* Validator that requires a control to match a regex to its value.
|
||||
*/
|
||||
static pattern(pattern: string): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
let regex = new RegExp(`^${pattern}$`);
|
||||
let v: string = control.value;
|
||||
return regex.test(v) ? null :
|
||||
{"pattern": {"requiredPattern": `^${pattern}$`, "actualValue": v}};
|
||||
{'pattern': {'requiredPattern': `^${pattern}$`, 'actualValue': v}};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op validator.
|
||||
*/
|
||||
static nullValidator(c: modelModule.AbstractControl): {[key: string]: boolean} { return null; }
|
||||
static nullValidator(c: AbstractControl): {[key: string]: boolean} { return null; }
|
||||
|
||||
/**
|
||||
* Compose multiple validators into a single function that returns the union
|
||||
@ -104,7 +117,7 @@ export class Validators {
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
|
||||
return function(control: modelModule.AbstractControl) {
|
||||
return function(control: AbstractControl) {
|
||||
return _mergeErrors(_executeValidators(control, presentValidators));
|
||||
};
|
||||
}
|
||||
@ -114,24 +127,22 @@ export class Validators {
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
|
||||
return function(control: modelModule.AbstractControl) {
|
||||
return function(control: AbstractControl) {
|
||||
let promises = _executeAsyncValidators(control, presentValidators).map(_convertToPromise);
|
||||
return PromiseWrapper.all(promises).then(_mergeErrors);
|
||||
return Promise.all(promises).then(_mergeErrors);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function _convertToPromise(obj: any): any {
|
||||
return PromiseWrapper.isPromise(obj) ? obj : ObservableWrapper.toPromise(obj);
|
||||
function _convertToPromise(obj: any): Promise<any> {
|
||||
return isPromise(obj) ? obj : toPromise.call(obj);
|
||||
}
|
||||
|
||||
function _executeValidators(control: modelModule.AbstractControl,
|
||||
validators: ValidatorFn[]): any[] {
|
||||
function _executeValidators(control: AbstractControl, validators: ValidatorFn[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
||||
function _executeAsyncValidators(control: modelModule.AbstractControl,
|
||||
validators: AsyncValidatorFn[]): any[] {
|
||||
function _executeAsyncValidators(control: AbstractControl, validators: AsyncValidatorFn[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* This module is used for handling user input, by defining and building a {@link ControlGroup} that
|
||||
* consists of
|
||||
* {@link Control} objects, and mapping them onto the DOM. {@link Control} objects can then be used
|
||||
* to read information
|
||||
* from the form DOM elements.
|
||||
*
|
||||
* Forms providers are not included in default providers; you must import these providers
|
||||
* explicitly.
|
||||
*/
|
||||
export {AbstractControl, Control, ControlGroup, ControlArray} from './forms/model';
|
||||
|
||||
export {AbstractControlDirective} from './forms/directives/abstract_control_directive';
|
||||
export {Form} from './forms/directives/form_interface';
|
||||
export {ControlContainer} from './forms/directives/control_container';
|
||||
export {NgControlName} from './forms/directives/ng_control_name';
|
||||
export {NgFormControl} from './forms/directives/ng_form_control';
|
||||
export {NgModel} from './forms/directives/ng_model';
|
||||
export {NgControl} from './forms/directives/ng_control';
|
||||
export {NgControlGroup} from './forms/directives/ng_control_group';
|
||||
export {NgFormModel} from './forms/directives/ng_form_model';
|
||||
export {NgForm} from './forms/directives/ng_form';
|
||||
export {ControlValueAccessor, NG_VALUE_ACCESSOR} from './forms/directives/control_value_accessor';
|
||||
export {DefaultValueAccessor} from './forms/directives/default_value_accessor';
|
||||
export {NgControlStatus} from './forms/directives/ng_control_status';
|
||||
export {CheckboxControlValueAccessor} from './forms/directives/checkbox_value_accessor';
|
||||
export {
|
||||
NgSelectOption,
|
||||
SelectControlValueAccessor
|
||||
} from './forms/directives/select_control_value_accessor';
|
||||
export {FORM_DIRECTIVES, RadioButtonState} from './forms/directives';
|
||||
export {NG_VALIDATORS, NG_ASYNC_VALIDATORS, Validators} from './forms/validators';
|
||||
export {
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
PatternValidator,
|
||||
Validator
|
||||
} from './forms/directives/validators';
|
||||
export {FormBuilder} from './forms/form_builder';
|
||||
import {FormBuilder} from './forms/form_builder';
|
||||
import {RadioControlRegistry} from './forms/directives/radio_control_value_accessor';
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Shorthand set of providers used for building Angular forms.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* bootstrap(MyApp, [FORM_PROVIDERS]);
|
||||
* ```
|
||||
*/
|
||||
export const FORM_PROVIDERS: Type[] = /*@ts2dart_const*/[FormBuilder, RadioControlRegistry];
|
||||
|
||||
/**
|
||||
* See {@link FORM_PROVIDERS} instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const FORM_BINDINGS = /*@ts2dart_const*/ FORM_PROVIDERS;
|
@ -1,101 +0,0 @@
|
||||
import {
|
||||
OnChanges,
|
||||
SimpleChange,
|
||||
Directive,
|
||||
forwardRef,
|
||||
Provider,
|
||||
Inject,
|
||||
Optional,
|
||||
Self
|
||||
} from '@angular/core';
|
||||
import {EventEmitter, ObservableWrapper} from '../../../src/facade/async';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {Control} from '../model';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {
|
||||
setUpControl,
|
||||
isPropertyUpdated,
|
||||
selectValueAccessor,
|
||||
composeValidators,
|
||||
composeAsyncValidators
|
||||
} from './shared';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
|
||||
export const formControlBinding: any =
|
||||
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgModel)
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds a domain model to a form control.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* `ngModel` binds an existing domain model to a form control. For a
|
||||
* two-way binding, use `[(ngModel)]` to ensure the model updates in
|
||||
* both directions.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/R3UX5qDaUqFO2VYR0UzH?p=preview))
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: "search-comp",
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: `<input type='text' [(ngModel)]="searchQuery">`
|
||||
* })
|
||||
* class SearchComp {
|
||||
* searchQuery: string;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngModel]:not([ngControl]):not([ngFormControl])',
|
||||
bindings: [formControlBinding],
|
||||
inputs: ['model: ngModel'],
|
||||
outputs: ['update: ngModelChange'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgModel extends NgControl implements OnChanges {
|
||||
/** @internal */
|
||||
_control = new Control();
|
||||
/** @internal */
|
||||
_added = false;
|
||||
update = new EventEmitter();
|
||||
model: any;
|
||||
viewModel: any;
|
||||
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: {[key: string]: SimpleChange}) {
|
||||
if (!this._added) {
|
||||
setUpControl(this._control, this);
|
||||
this._control.updateValueAndValidity({emitEvent: false});
|
||||
this._added = true;
|
||||
}
|
||||
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this._control.updateValue(this.model);
|
||||
this.viewModel = this.model;
|
||||
}
|
||||
}
|
||||
|
||||
get control(): Control { return this._control; }
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
ObservableWrapper.callEmit(this.update, newValue);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
library angular2.core.forms.normalize_validators;
|
||||
|
||||
import 'package:angular2/src/common/forms/directives/validators.dart' show Validator;
|
||||
|
||||
Function normalizeValidator(dynamic validator){
|
||||
if (validator is Validator) {
|
||||
return (c) => validator.validate(c);
|
||||
} else {
|
||||
return validator;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Function normalizeAsyncValidator(dynamic validator){
|
||||
if (validator is Validator) {
|
||||
return (c) => validator.validate(c);
|
||||
} else {
|
||||
return validator;
|
||||
}
|
||||
}
|
26
modules/@angular/common/src/localization.ts
Normal file
26
modules/@angular/common/src/localization.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
|
||||
|
||||
/**
|
||||
* Returns the plural category for a given value.
|
||||
* - "=value" when the case exists,
|
||||
* - the plural category otherwise
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function getPluralCategory(
|
||||
value: number, cases: string[], ngLocalization: NgLocalization): string {
|
||||
const nbCase = `=${value}`;
|
||||
|
||||
return cases.indexOf(nbCase) > -1 ? nbCase : ngLocalization.getPluralCategory(value);
|
||||
}
|
@ -1,3 +1,11 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export * from './location/platform_location';
|
||||
export * from './location/location_strategy';
|
||||
export * from './location/hash_location_strategy';
|
||||
|
@ -1,9 +1,19 @@
|
||||
import {Injectable, Inject, Optional} from '@angular/core';
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable, Optional} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
import {LocationStrategy, APP_BASE_HREF} from './location_strategy';
|
||||
import {Location} from './location';
|
||||
import {UrlChangeListener, PlatformLocation} from './platform_location';
|
||||
import {APP_BASE_HREF, LocationStrategy} from './location_strategy';
|
||||
import {PlatformLocation, UrlChangeListener} from './platform_location';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@ -42,15 +52,18 @@ import {UrlChangeListener, PlatformLocation} from './platform_location';
|
||||
*
|
||||
* bootstrap(AppCmp, [
|
||||
* ROUTER_PROVIDERS,
|
||||
* provide(LocationStrategy, {useClass: HashLocationStrategy})
|
||||
* {provide: LocationStrategy, useClass: HashLocationStrategy}
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
export class HashLocationStrategy extends LocationStrategy {
|
||||
private _baseHref: string = '';
|
||||
constructor(private _platformLocation: PlatformLocation,
|
||||
@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
|
||||
constructor(
|
||||
private _platformLocation: PlatformLocation,
|
||||
@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
|
||||
super();
|
||||
if (isPresent(_baseHref)) {
|
||||
this._baseHref = _baseHref;
|
||||
@ -64,16 +77,13 @@ export class HashLocationStrategy extends LocationStrategy {
|
||||
|
||||
getBaseHref(): string { return this._baseHref; }
|
||||
|
||||
path(): string {
|
||||
path(includeHash: boolean = false): string {
|
||||
// the hash value is always prefixed with a `#`
|
||||
// and if it is empty then it will stay empty
|
||||
var path = this._platformLocation.hash;
|
||||
if (!isPresent(path)) path = '#';
|
||||
|
||||
// Dart will complain if a call to substring is
|
||||
// executed with a position value that extends the
|
||||
// length of string.
|
||||
return (path.length > 0 ? path.substring(1) : path);
|
||||
return path.length > 0 ? path.substring(1) : path;
|
||||
}
|
||||
|
||||
prepareExternalUrl(internal: string): string {
|
||||
|
@ -1,8 +1,16 @@
|
||||
import {Injectable, Inject} from '@angular/core';
|
||||
import {EventEmitter, ObservableWrapper} from '../../src/facade/async';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {EventEmitter, Injectable} from '@angular/core';
|
||||
|
||||
import {LocationStrategy} from './location_strategy';
|
||||
|
||||
|
||||
/**
|
||||
* `Location` is a service that applications can use to interact with a browser's URL.
|
||||
* Depending on which {@link LocationStrategy} is used, `Location` will either persist
|
||||
@ -42,6 +50,8 @@ import {LocationStrategy} from './location_strategy';
|
||||
*
|
||||
* bootstrap(AppCmp, [ROUTER_PROVIDERS]);
|
||||
* ```
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
export class Location {
|
||||
@ -50,18 +60,32 @@ export class Location {
|
||||
/** @internal */
|
||||
_baseHref: string;
|
||||
|
||||
constructor(public platformStrategy: LocationStrategy) {
|
||||
var browserBaseHref = this.platformStrategy.getBaseHref();
|
||||
/** @internal */
|
||||
_platformStrategy: LocationStrategy;
|
||||
|
||||
constructor(platformStrategy: LocationStrategy) {
|
||||
this._platformStrategy = platformStrategy;
|
||||
var browserBaseHref = this._platformStrategy.getBaseHref();
|
||||
this._baseHref = Location.stripTrailingSlash(_stripIndexHtml(browserBaseHref));
|
||||
this.platformStrategy.onPopState((ev) => {
|
||||
ObservableWrapper.callEmit(this._subject, {'url': this.path(), 'pop': true, 'type': ev.type});
|
||||
});
|
||||
this._platformStrategy.onPopState(
|
||||
(ev) => { this._subject.emit({'url': this.path(true), 'pop': true, 'type': ev.type}); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the normalized URL path.
|
||||
*/
|
||||
path(): string { return this.normalize(this.platformStrategy.path()); }
|
||||
// TODO: vsavkin. Remove the boolean flag and always include hash once the deprecated router is
|
||||
// removed.
|
||||
path(includeHash: boolean = false): string {
|
||||
return this.normalize(this._platformStrategy.path(includeHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the given path and compares to the current normalized path.
|
||||
*/
|
||||
isCurrentPathEqualTo(path: string, query: string = ''): boolean {
|
||||
return this.path() == this.normalize(path + Location.normalizeQueryParams(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string representing a URL, returns the normalized URL path without leading or
|
||||
@ -81,7 +105,7 @@ export class Location {
|
||||
if (url.length > 0 && !url.startsWith('/')) {
|
||||
url = '/' + url;
|
||||
}
|
||||
return this.platformStrategy.prepareExternalUrl(url);
|
||||
return this._platformStrategy.prepareExternalUrl(url);
|
||||
}
|
||||
|
||||
// TODO: rename this method to pushState
|
||||
@ -90,7 +114,7 @@ export class Location {
|
||||
* new item onto the platform's history.
|
||||
*/
|
||||
go(path: string, query: string = ''): void {
|
||||
this.platformStrategy.pushState(null, '', path, query);
|
||||
this._platformStrategy.pushState(null, '', path, query);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,25 +122,26 @@ export class Location {
|
||||
* the top item on the platform's history stack.
|
||||
*/
|
||||
replaceState(path: string, query: string = ''): void {
|
||||
this.platformStrategy.replaceState(null, '', path, query);
|
||||
this._platformStrategy.replaceState(null, '', path, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates forward in the platform's history.
|
||||
*/
|
||||
forward(): void { this.platformStrategy.forward(); }
|
||||
forward(): void { this._platformStrategy.forward(); }
|
||||
|
||||
/**
|
||||
* Navigates back in the platform's history.
|
||||
*/
|
||||
back(): void { this.platformStrategy.back(); }
|
||||
back(): void { this._platformStrategy.back(); }
|
||||
|
||||
/**
|
||||
* Subscribe to the platform's `popState` events.
|
||||
*/
|
||||
subscribe(onNext: (value: any) => void, onThrow: (exception: any) => void = null,
|
||||
onReturn: () => void = null): Object {
|
||||
return ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
|
||||
subscribe(
|
||||
onNext: (value: any) => void, onThrow: (exception: any) => void = null,
|
||||
onReturn: () => void = null): Object {
|
||||
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,3 +1,11 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
import {UrlChangeListener} from './platform_location';
|
||||
|
||||
@ -16,9 +24,11 @@ import {UrlChangeListener} from './platform_location';
|
||||
* `http://example.com/foo` as an equivalent URL.
|
||||
*
|
||||
* See these two classes for more.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export abstract class LocationStrategy {
|
||||
abstract path(): string;
|
||||
abstract path(includeHash?: boolean): string;
|
||||
abstract prepareExternalUrl(internal: string): string;
|
||||
abstract pushState(state: any, title: string, url: string, queryParams: string): void;
|
||||
abstract replaceState(state: any, title: string, url: string, queryParams: string): void;
|
||||
@ -54,8 +64,9 @@ export abstract class LocationStrategy {
|
||||
*
|
||||
* bootstrap(AppCmp, [
|
||||
* ROUTER_PROVIDERS,
|
||||
* provide(APP_BASE_HREF, {useValue: '/my/app'})
|
||||
* {provide: APP_BASE_HREF, useValue: '/my/app'}
|
||||
* ]);
|
||||
* ```
|
||||
* @stable
|
||||
*/
|
||||
export const APP_BASE_HREF: OpaqueToken = /*@ts2dart_const*/ new OpaqueToken('appBaseHref');
|
||||
export const APP_BASE_HREF: OpaqueToken = new OpaqueToken('appBaseHref');
|
||||
|
@ -1,10 +1,20 @@
|
||||
import {Injectable, Inject, Optional} from '@angular/core';
|
||||
import {isBlank} from '../../src/facade/lang';
|
||||
import {BaseException} from '../../src/facade/exceptions';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable, Optional} from '@angular/core';
|
||||
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
import {isBlank} from '../facade/lang';
|
||||
|
||||
import {PlatformLocation, UrlChangeListener} from './platform_location';
|
||||
import {LocationStrategy, APP_BASE_HREF} from './location_strategy';
|
||||
import {Location} from './location';
|
||||
import {APP_BASE_HREF, LocationStrategy} from './location_strategy';
|
||||
import {PlatformLocation, UrlChangeListener} from './platform_location';
|
||||
|
||||
|
||||
/**
|
||||
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
|
||||
@ -15,51 +25,27 @@ import {Location} from './location';
|
||||
* `PathLocationStrategy` is the default binding for {@link LocationStrategy}
|
||||
* provided in {@link ROUTER_PROVIDERS}.
|
||||
*
|
||||
* If you're using `PathLocationStrategy`, you must provide a provider for
|
||||
* {@link APP_BASE_HREF} to a string representing the URL prefix that should
|
||||
* be preserved when generating and recognizing URLs.
|
||||
* If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF}
|
||||
* or add a base element to the document. This URL prefix that will be preserved
|
||||
* when generating and recognizing URLs.
|
||||
*
|
||||
* For instance, if you provide an `APP_BASE_HREF` of `'/my/app'` and call
|
||||
* `location.go('/foo')`, the browser's URL will become
|
||||
* `example.com/my/app/foo`.
|
||||
*
|
||||
* ### Example
|
||||
* Similarly, if you add `<base href='/my/app'/>` to the document and call
|
||||
* `location.go('/foo')`, the browser's URL will become
|
||||
* `example.com/my/app/foo`.
|
||||
*
|
||||
* ```
|
||||
* import {Component, provide} from '@angular/core';
|
||||
* import {bootstrap} from '@angular/platform-browser/browser';
|
||||
* import {
|
||||
* Location,
|
||||
* APP_BASE_HREF
|
||||
* } from '@angular/common';
|
||||
* import {
|
||||
* ROUTER_DIRECTIVES,
|
||||
* ROUTER_PROVIDERS,
|
||||
* RouteConfig
|
||||
* } from '@angular/router';
|
||||
*
|
||||
* @Component({directives: [ROUTER_DIRECTIVES]})
|
||||
* @RouteConfig([
|
||||
* {...},
|
||||
* ])
|
||||
* class AppCmp {
|
||||
* constructor(location: Location) {
|
||||
* location.go('/foo');
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* bootstrap(AppCmp, [
|
||||
* ROUTER_PROVIDERS, // includes binding to PathLocationStrategy
|
||||
* provide(APP_BASE_HREF, {useValue: '/my/app'})
|
||||
* ]);
|
||||
* ```
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
export class PathLocationStrategy extends LocationStrategy {
|
||||
private _baseHref: string;
|
||||
|
||||
constructor(private _platformLocation: PlatformLocation,
|
||||
@Optional() @Inject(APP_BASE_HREF) href?: string) {
|
||||
constructor(
|
||||
private _platformLocation: PlatformLocation,
|
||||
@Optional() @Inject(APP_BASE_HREF) href?: string) {
|
||||
super();
|
||||
|
||||
if (isBlank(href)) {
|
||||
@ -85,9 +71,11 @@ export class PathLocationStrategy extends LocationStrategy {
|
||||
return Location.joinWithSlash(this._baseHref, internal);
|
||||
}
|
||||
|
||||
path(): string {
|
||||
return this._platformLocation.pathname +
|
||||
Location.normalizeQueryParams(this._platformLocation.search);
|
||||
path(includeHash: boolean = false): string {
|
||||
const pathname = this._platformLocation.pathname +
|
||||
Location.normalizeQueryParams(this._platformLocation.search);
|
||||
const hash = this._platformLocation.hash;
|
||||
return hash && includeHash ? `${pathname}${hash}` : pathname;
|
||||
}
|
||||
|
||||
pushState(state: any, title: string, url: string, queryParams: string) {
|
||||
|
@ -1,3 +1,11 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class should not be used directly by an application developer. Instead, use
|
||||
* {@link Location}.
|
||||
@ -21,15 +29,17 @@
|
||||
* {@link Location} / {@link LocationStrategy} and DOM apis flow through the `PlatformLocation`
|
||||
* class
|
||||
* they are all platform independent.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export abstract class PlatformLocation {
|
||||
abstract getBaseHrefFromDOM(): string;
|
||||
abstract onPopState(fn: UrlChangeListener): void;
|
||||
abstract onHashChange(fn: UrlChangeListener): void;
|
||||
|
||||
/* abstract */ get pathname(): string { return null; }
|
||||
/* abstract */ get search(): string { return null; }
|
||||
/* abstract */ get hash(): string { return null; }
|
||||
get pathname(): string { return null; }
|
||||
get search(): string { return null; }
|
||||
get hash(): string { return null; }
|
||||
|
||||
abstract replaceState(state: any, title: string, url: string): void;
|
||||
|
||||
@ -42,7 +52,12 @@ export abstract class PlatformLocation {
|
||||
|
||||
/**
|
||||
* A serializable version of the event from onPopState or onHashChange
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface UrlChangeEvent { type: string; }
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface UrlChangeListener { (e: UrlChangeEvent): any; }
|
||||
|
@ -1,3 +1,11 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
@ -5,13 +13,13 @@
|
||||
*/
|
||||
|
||||
export {AsyncPipe} from './pipes/async_pipe';
|
||||
export {COMMON_PIPES} from './pipes/common_pipes';
|
||||
export {DatePipe} from './pipes/date_pipe';
|
||||
export {JsonPipe} from './pipes/json_pipe';
|
||||
export {SlicePipe} from './pipes/slice_pipe';
|
||||
export {LowerCasePipe} from './pipes/lowercase_pipe';
|
||||
export {NumberPipe, DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
|
||||
export {UpperCasePipe} from './pipes/uppercase_pipe';
|
||||
export {ReplacePipe} from './pipes/replace_pipe';
|
||||
export {I18nPluralPipe} from './pipes/i18n_plural_pipe';
|
||||
export {I18nSelectPipe} from './pipes/i18n_select_pipe';
|
||||
export {COMMON_PIPES} from './pipes/common_pipes';
|
||||
export {JsonPipe} from './pipes/json_pipe';
|
||||
export {LowerCasePipe} from './pipes/lowercase_pipe';
|
||||
export {CurrencyPipe, DecimalPipe, PercentPipe} from './pipes/number_pipe';
|
||||
export {ReplacePipe} from './pipes/replace_pipe';
|
||||
export {SlicePipe} from './pipes/slice_pipe';
|
||||
export {UpperCasePipe} from './pipes/uppercase_pipe';
|
||||
|
@ -1,23 +1,35 @@
|
||||
import {Pipe, Injectable, ChangeDetectorRef, OnDestroy, WrappedValue} from '@angular/core';
|
||||
|
||||
import {isBlank, isPresent, isPromise} from '../../src/facade/lang';
|
||||
import {ObservableWrapper, Observable, EventEmitter} from '../../src/facade/async';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, OnDestroy, Pipe, WrappedValue} from '@angular/core';
|
||||
import {EventEmitter, Observable} from '../facade/async';
|
||||
import {isBlank, isPresent, isPromise} from '../facade/lang';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
class ObservableStrategy {
|
||||
createSubscription(async: any, updateLatestValue: any): any {
|
||||
return ObservableWrapper.subscribe(async, updateLatestValue, e => { throw e; });
|
||||
}
|
||||
|
||||
dispose(subscription: any): void { ObservableWrapper.dispose(subscription); }
|
||||
|
||||
onDestroy(subscription: any): void { ObservableWrapper.dispose(subscription); }
|
||||
interface SubscriptionStrategy {
|
||||
createSubscription(async: any, updateLatestValue: any): any;
|
||||
dispose(subscription: any): void;
|
||||
onDestroy(subscription: any): void;
|
||||
}
|
||||
|
||||
class PromiseStrategy {
|
||||
class ObservableStrategy implements SubscriptionStrategy {
|
||||
createSubscription(async: any, updateLatestValue: any): any {
|
||||
return async.subscribe({next: updateLatestValue, error: (e: any) => { throw e; }});
|
||||
}
|
||||
|
||||
dispose(subscription: any): void { subscription.unsubscribe(); }
|
||||
|
||||
onDestroy(subscription: any): void { subscription.unsubscribe(); }
|
||||
}
|
||||
|
||||
class PromiseStrategy implements SubscriptionStrategy {
|
||||
createSubscription(async: Promise<any>, updateLatestValue: (v: any) => any): any {
|
||||
return async.then(updateLatestValue);
|
||||
return async.then(updateLatestValue, e => { throw e; });
|
||||
}
|
||||
|
||||
dispose(subscription: any): void {}
|
||||
@ -30,25 +42,33 @@ var _observableStrategy = new ObservableStrategy();
|
||||
var __unused: Promise<any>; // avoid unused import when Promise union types are erased
|
||||
|
||||
/**
|
||||
* The `async` pipe subscribes to an Observable or Promise and returns the latest value it has
|
||||
* The `async` pipe subscribes to an `Observable` or `Promise` and returns the latest value it has
|
||||
* emitted.
|
||||
* When a new value is emitted, the `async` pipe marks the component to be checked for changes.
|
||||
* When the component gets destroyed, the `async` pipe unsubscribes automatically to avoid
|
||||
* potential memory leaks.
|
||||
*
|
||||
* ### Example
|
||||
* ## Usage
|
||||
*
|
||||
* object | async
|
||||
*
|
||||
* where `object` is of type `Observable` or of type `Promise`.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* This example binds a `Promise` to the view. Clicking the `Resolve` button resolves the
|
||||
* promise.
|
||||
*
|
||||
* {@example core/pipes/ts/async_pipe/async_pipe_example.ts region='AsyncPipe'}
|
||||
* {@example core/pipes/ts/async_pipe/async_pipe_example.ts region='AsyncPipePromise'}
|
||||
*
|
||||
* It's also possible to use `async` with Observables. The example below binds the `time` Observable
|
||||
* to the view. Every 500ms, the `time` Observable updates the view with the current time.
|
||||
*
|
||||
* ```typescript
|
||||
* ```
|
||||
* {@example core/pipes/ts/async_pipe/async_pipe_example.ts region='AsyncPipeObservable'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'async', pure: false})
|
||||
@Injectable()
|
||||
export class AsyncPipe implements OnDestroy {
|
||||
/** @internal */
|
||||
_latestValue: Object = null;
|
||||
@ -58,10 +78,11 @@ export class AsyncPipe implements OnDestroy {
|
||||
/** @internal */
|
||||
_subscription: Object = null;
|
||||
/** @internal */
|
||||
_obj: Observable<any>| Promise<any>| EventEmitter<any> = null;
|
||||
private _strategy: any = null;
|
||||
_obj: Observable<any>|Promise<any>|EventEmitter<any> = null;
|
||||
/** @internal */
|
||||
public _ref: ChangeDetectorRef;
|
||||
_ref: ChangeDetectorRef;
|
||||
private _strategy: SubscriptionStrategy = null;
|
||||
|
||||
constructor(_ref: ChangeDetectorRef) { this._ref = _ref; }
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -70,7 +91,7 @@ export class AsyncPipe implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
transform(obj: Observable<any>| Promise<any>| EventEmitter<any>): any {
|
||||
transform(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
|
||||
if (isBlank(this._obj)) {
|
||||
if (isPresent(obj)) {
|
||||
this._subscribe(obj);
|
||||
@ -93,7 +114,7 @@ export class AsyncPipe implements OnDestroy {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_subscribe(obj: Observable<any>| Promise<any>| EventEmitter<any>): void {
|
||||
_subscribe(obj: Observable<any>|Promise<any>|EventEmitter<any>): void {
|
||||
this._obj = obj;
|
||||
this._strategy = this._selectStrategy(obj);
|
||||
this._subscription = this._strategy.createSubscription(
|
||||
@ -101,10 +122,10 @@ export class AsyncPipe implements OnDestroy {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_selectStrategy(obj: Observable<any>| Promise<any>| EventEmitter<any>): any {
|
||||
_selectStrategy(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
|
||||
if (isPromise(obj)) {
|
||||
return _promiseStrategy;
|
||||
} else if (ObservableWrapper.isObservable(obj)) {
|
||||
} else if ((<any>obj).subscribe) {
|
||||
return _observableStrategy;
|
||||
} else {
|
||||
throw new InvalidPipeArgumentException(AsyncPipe, obj);
|
||||
|
@ -1,18 +1,27 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* This module provides a set of common Pipes.
|
||||
*/
|
||||
import {AsyncPipe} from './async_pipe';
|
||||
import {UpperCasePipe} from './uppercase_pipe';
|
||||
import {LowerCasePipe} from './lowercase_pipe';
|
||||
import {JsonPipe} from './json_pipe';
|
||||
import {SlicePipe} from './slice_pipe';
|
||||
import {DatePipe} from './date_pipe';
|
||||
import {DecimalPipe, PercentPipe, CurrencyPipe} from './number_pipe';
|
||||
import {ReplacePipe} from './replace_pipe';
|
||||
import {I18nPluralPipe} from './i18n_plural_pipe';
|
||||
import {I18nSelectPipe} from './i18n_select_pipe';
|
||||
import {JsonPipe} from './json_pipe';
|
||||
import {LowerCasePipe} from './lowercase_pipe';
|
||||
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
|
||||
import {ReplacePipe} from './replace_pipe';
|
||||
import {SlicePipe} from './slice_pipe';
|
||||
import {UpperCasePipe} from './uppercase_pipe';
|
||||
|
||||
|
||||
/**
|
||||
* A collection of Angular core pipes that are likely to be used in each and every
|
||||
@ -20,8 +29,10 @@ import {I18nSelectPipe} from './i18n_select_pipe';
|
||||
*
|
||||
* This collection can be used to quickly enumerate all the built-in pipes in the `pipes`
|
||||
* property of the `@Component` decorator.
|
||||
*
|
||||
* @experimental Contains i18n pipes which are experimental
|
||||
*/
|
||||
export const COMMON_PIPES = /*@ts2dart_const*/[
|
||||
export const COMMON_PIPES = [
|
||||
AsyncPipe,
|
||||
UpperCasePipe,
|
||||
LowerCasePipe,
|
||||
@ -33,5 +44,5 @@ export const COMMON_PIPES = /*@ts2dart_const*/[
|
||||
DatePipe,
|
||||
ReplacePipe,
|
||||
I18nPluralPipe,
|
||||
I18nSelectPipe
|
||||
I18nSelectPipe,
|
||||
];
|
||||
|
@ -1,16 +1,17 @@
|
||||
import {PipeTransform, Pipe, Injectable} from '@angular/core';
|
||||
import {
|
||||
isDate,
|
||||
isNumber,
|
||||
DateWrapper,
|
||||
isBlank,
|
||||
} from '../../src/facade/lang';
|
||||
import {DateFormatter} from '../../src/facade/intl';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {DateFormatter} from '../facade/intl';
|
||||
import {DateWrapper, NumberWrapper, isBlank, isDate, isString} from '../facade/lang';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
|
||||
// TODO: move to a global configurable location along with other i18n components.
|
||||
var defaultLocale: string = 'en-US';
|
||||
|
||||
@ -29,8 +30,9 @@ var defaultLocale: string = 'en-US';
|
||||
*
|
||||
* expression | date[:format]
|
||||
*
|
||||
* where `expression` is a date object or a number (milliseconds since UTC epoch) and
|
||||
* `format` indicates which date/time components to include:
|
||||
* where `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
|
||||
* (https://www.w3.org/TR/NOTE-datetime) and `format` indicates which date/time components to
|
||||
* include:
|
||||
*
|
||||
* | Component | Symbol | Short Form | Long Form | Numeric | 2-digit |
|
||||
* |-----------|:------:|--------------|-------------------|-----------|-----------|
|
||||
@ -49,9 +51,6 @@ var defaultLocale: string = 'en-US';
|
||||
*
|
||||
* In javascript, only the components specified will be respected (not the ordering,
|
||||
* punctuations, ...) and details of the formatting will be dependent on the locale.
|
||||
* On the other hand in Dart version, you can also include quoted text as well as some extra
|
||||
* date/time components such as quarter. For more information see:
|
||||
* https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/intl/intl.DateFormat.
|
||||
*
|
||||
* `format` can also be one of the following predefined formats:
|
||||
*
|
||||
@ -79,9 +78,10 @@ var defaultLocale: string = 'en-US';
|
||||
* ```
|
||||
*
|
||||
* {@example core/pipes/ts/date_pipe/date_pipe_example.ts region='DatePipe'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Pipe({name: 'date', pure: true})
|
||||
@Injectable()
|
||||
export class DatePipe implements PipeTransform {
|
||||
/** @internal */
|
||||
static _ALIASES: {[key: string]: String} = {
|
||||
@ -103,8 +103,10 @@ export class DatePipe implements PipeTransform {
|
||||
throw new InvalidPipeArgumentException(DatePipe, value);
|
||||
}
|
||||
|
||||
if (isNumber(value)) {
|
||||
value = DateWrapper.fromMillis(value);
|
||||
if (NumberWrapper.isNumeric(value)) {
|
||||
value = DateWrapper.fromMillis(NumberWrapper.parseInt(value, 10));
|
||||
} else if (isString(value)) {
|
||||
value = DateWrapper.fromISOString(value);
|
||||
}
|
||||
if (StringMapWrapper.contains(DatePipe._ALIASES, pattern)) {
|
||||
pattern = <string>StringMapWrapper.get(DatePipe._ALIASES, pattern);
|
||||
@ -112,5 +114,13 @@ export class DatePipe implements PipeTransform {
|
||||
return DateFormatter.format(value, defaultLocale, pattern);
|
||||
}
|
||||
|
||||
supports(obj: any): boolean { return isDate(obj) || isNumber(obj); }
|
||||
private supports(obj: any): boolean {
|
||||
if (isDate(obj) || NumberWrapper.isNumeric(obj)) {
|
||||
return true;
|
||||
}
|
||||
if (isString(obj) && isDate(DateWrapper.fromISOString(obj))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,52 @@
|
||||
import {Injectable, PipeTransform, Pipe} from '@angular/core';
|
||||
import {isStringMap, StringWrapper, isPresent, RegExpWrapper} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {StringWrapper, isBlank, isStringMap} from '../facade/lang';
|
||||
import {NgLocalization, getPluralCategory} from '../localization';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
var interpolationExp: RegExp = RegExpWrapper.create('#');
|
||||
const _INTERPOLATION_REGEXP: RegExp = /#/g;
|
||||
|
||||
/**
|
||||
*
|
||||
* Maps a value to a string that pluralizes the value properly.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* expression | i18nPlural:mapping
|
||||
*
|
||||
* where `expression` is a number and `mapping` is an object that indicates the proper text for
|
||||
* when the `expression` evaluates to 0, 1, or some other number. You can interpolate the actual
|
||||
* value into the text using the `#` sign.
|
||||
* where `expression` is a number and `mapping` is an object that mimics the ICU format,
|
||||
* see http://userguide.icu-project.org/formatparse/messages
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* <div>
|
||||
* {{ messages.length | i18nPlural: messageMapping }}
|
||||
* </div>
|
||||
* class MyLocalization extends NgLocalization {
|
||||
* getPluralCategory(value: any) {
|
||||
* if(value > 1) {
|
||||
* return 'other';
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'app',
|
||||
* template: `
|
||||
* <div>
|
||||
* {{ messages.length | i18nPlural: messageMapping }}
|
||||
* </div>
|
||||
* `,
|
||||
* providers: [{provide: NgLocalization, useClass: MyLocalization}]
|
||||
* })
|
||||
*
|
||||
* class MyApp {
|
||||
* messages: any[];
|
||||
* messageMapping: any = {
|
||||
* messageMapping: {[k:string]: string} = {
|
||||
* '=0': 'No messages.',
|
||||
* '=1': 'One message.',
|
||||
* 'other': '# messages.'
|
||||
@ -34,21 +55,21 @@ var interpolationExp: RegExp = RegExpWrapper.create('#');
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Pipe({name: 'i18nPlural', pure: true})
|
||||
@Injectable()
|
||||
export class I18nPluralPipe implements PipeTransform {
|
||||
constructor(private _localization: NgLocalization) {}
|
||||
|
||||
transform(value: number, pluralMap: {[count: string]: string}): string {
|
||||
var key: string;
|
||||
var valueStr: string;
|
||||
if (isBlank(value)) return '';
|
||||
|
||||
if (!isStringMap(pluralMap)) {
|
||||
throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap);
|
||||
}
|
||||
|
||||
key = value === 0 || value === 1 ? `=${value}` : 'other';
|
||||
valueStr = isPresent(value) ? value.toString() : '';
|
||||
const key = getPluralCategory(value, Object.keys(pluralMap), this._localization);
|
||||
|
||||
return StringWrapper.replaceAll(pluralMap[key], interpolationExp, valueStr);
|
||||
return StringWrapper.replaceAll(pluralMap[key], _INTERPOLATION_REGEXP, value.toString());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,13 @@
|
||||
import {Injectable, PipeTransform, Pipe} from '@angular/core';
|
||||
import {isStringMap} from '../../src/facade/lang';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank, isStringMap} from '../facade/lang';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
/**
|
||||
@ -24,22 +31,25 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
* class MyApp {
|
||||
* gender: string = 'male';
|
||||
* inviteMap: any = {
|
||||
* 'male': 'Invite her.',
|
||||
* 'female': 'Invite him.',
|
||||
* 'male': 'Invite him.',
|
||||
* 'female': 'Invite her.',
|
||||
* 'other': 'Invite them.'
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Pipe({name: 'i18nSelect', pure: true})
|
||||
@Injectable()
|
||||
export class I18nSelectPipe implements PipeTransform {
|
||||
transform(value: string, mapping: {[key: string]: string}): string {
|
||||
if (isBlank(value)) return '';
|
||||
|
||||
if (!isStringMap(mapping)) {
|
||||
throw new InvalidPipeArgumentException(I18nSelectPipe, mapping);
|
||||
}
|
||||
|
||||
return StringMapWrapper.contains(mapping, value) ? mapping[value] : mapping['other'];
|
||||
return mapping.hasOwnProperty(value) ? mapping[value] : '';
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,13 @@
|
||||
import {Type, stringify} from '../../src/facade/lang';
|
||||
import {BaseException} from '../../src/facade/exceptions';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
import {Type, stringify} from '../facade/lang';
|
||||
|
||||
export class InvalidPipeArgumentException extends BaseException {
|
||||
constructor(type: Type, value: Object) {
|
||||
|
@ -1,5 +1,15 @@
|
||||
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
|
||||
import {Json} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
import {Json} from '../facade/lang';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@ -7,10 +17,10 @@ import {Json} from '../../src/facade/lang';
|
||||
*
|
||||
* ### Example
|
||||
* {@example core/pipes/ts/json_pipe/json_pipe_example.ts region='JsonPipe'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
/* @ts2dart_const */
|
||||
@Pipe({name: 'json', pure: false})
|
||||
@Injectable()
|
||||
export class JsonPipe implements PipeTransform {
|
||||
transform(value: any): string { return Json.stringify(value); }
|
||||
}
|
||||
|
@ -1,17 +1,26 @@
|
||||
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
|
||||
import {isString, isBlank} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank, isString} from '../facade/lang';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
|
||||
/**
|
||||
* Transforms text to lowercase.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
/* @ts2dart_const */
|
||||
@Pipe({name: 'lowercase'})
|
||||
@Injectable()
|
||||
export class LowerCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (isBlank(value)) return value;
|
||||
|
@ -1,62 +1,68 @@
|
||||
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
isNumber,
|
||||
isPresent,
|
||||
isBlank,
|
||||
NumberWrapper,
|
||||
RegExpWrapper,
|
||||
} from '../../src/facade/lang';
|
||||
import {BaseException} from '../../src/facade/exceptions';
|
||||
import {NumberFormatter, NumberFormatStyle} from '../../src/facade/intl';
|
||||
import {ListWrapper} from '../../src/facade/collection';
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
import {NumberFormatStyle, NumberFormatter} from '../facade/intl';
|
||||
import {NumberWrapper, Type, isBlank, isNumber, isPresent, isString} from '../facade/lang';
|
||||
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
var defaultLocale: string = 'en-US';
|
||||
var _re = RegExpWrapper.create('^(\\d+)?\\.((\\d+)(\\-(\\d+))?)?$');
|
||||
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(\-(\d+))?)?$/;
|
||||
|
||||
/**
|
||||
* Internal base class for numeric pipes.
|
||||
*/
|
||||
@Injectable()
|
||||
export class NumberPipe {
|
||||
/** @internal */
|
||||
static _format(value: number, style: NumberFormatStyle, digits: string, currency: string = null,
|
||||
currencyAsSymbol: boolean = false): string {
|
||||
if (isBlank(value)) return null;
|
||||
if (!isNumber(value)) {
|
||||
throw new InvalidPipeArgumentException(NumberPipe, value);
|
||||
}
|
||||
var minInt = 1, minFraction = 0, maxFraction = 3;
|
||||
if (isPresent(digits)) {
|
||||
var parts = RegExpWrapper.firstMatch(_re, digits);
|
||||
if (isBlank(parts)) {
|
||||
throw new BaseException(`${digits} is not a valid digit info for number pipes`);
|
||||
}
|
||||
if (isPresent(parts[1])) { // min integer digits
|
||||
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
||||
}
|
||||
if (isPresent(parts[3])) { // min fraction digits
|
||||
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
||||
}
|
||||
if (isPresent(parts[5])) { // max fraction digits
|
||||
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
||||
}
|
||||
}
|
||||
return NumberFormatter.format(value, defaultLocale, style, {
|
||||
minimumIntegerDigits: minInt,
|
||||
minimumFractionDigits: minFraction,
|
||||
maximumFractionDigits: maxFraction,
|
||||
currency: currency,
|
||||
currencyAsSymbol: currencyAsSymbol
|
||||
});
|
||||
function formatNumber(
|
||||
pipe: Type, value: number | string, style: NumberFormatStyle, digits: string,
|
||||
currency: string = null, currencyAsSymbol: boolean = false): string {
|
||||
if (isBlank(value)) return null;
|
||||
// Convert strings to numbers
|
||||
value = isString(value) && NumberWrapper.isNumeric(value) ? +value : value;
|
||||
if (!isNumber(value)) {
|
||||
throw new InvalidPipeArgumentException(pipe, value);
|
||||
}
|
||||
let minInt: number;
|
||||
let minFraction: number;
|
||||
let maxFraction: number;
|
||||
if (style !== NumberFormatStyle.Currency) {
|
||||
// rely on Intl default for currency
|
||||
minInt = 1;
|
||||
minFraction = 0;
|
||||
maxFraction = 3;
|
||||
}
|
||||
|
||||
if (isPresent(digits)) {
|
||||
var parts = digits.match(_NUMBER_FORMAT_REGEXP);
|
||||
if (parts === null) {
|
||||
throw new Error(`${digits} is not a valid digit info for number pipes`);
|
||||
}
|
||||
if (isPresent(parts[1])) { // min integer digits
|
||||
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
||||
}
|
||||
if (isPresent(parts[3])) { // min fraction digits
|
||||
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
||||
}
|
||||
if (isPresent(parts[5])) { // max fraction digits
|
||||
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
||||
}
|
||||
}
|
||||
return NumberFormatter.format(value as number, defaultLocale, style, {
|
||||
minimumIntegerDigits: minInt,
|
||||
minimumFractionDigits: minFraction,
|
||||
maximumFractionDigits: maxFraction,
|
||||
currency: currency,
|
||||
currencyAsSymbol: currencyAsSymbol
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this pipe uses the Internationalization API.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers. For other browsers please use an
|
||||
* polyfill, for example: [https://github.com/andyearnshaw/Intl.js/].
|
||||
*
|
||||
* Formats a number as local text. i.e. group sizing and separator and other locale-specific
|
||||
* configurations are based on the active locale.
|
||||
@ -79,18 +85,20 @@ export class NumberPipe {
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/pipes/ts/number_pipe/number_pipe_example.ts region='NumberPipe'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Pipe({name: 'number'})
|
||||
@Injectable()
|
||||
export class DecimalPipe extends NumberPipe implements PipeTransform {
|
||||
export class DecimalPipe implements PipeTransform {
|
||||
transform(value: any, digits: string = null): string {
|
||||
return NumberPipe._format(value, NumberFormatStyle.Decimal, digits);
|
||||
return formatNumber(DecimalPipe, value, NumberFormatStyle.Decimal, digits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this pipe uses the Internationalization API.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers. For other browsers please use an
|
||||
* polyfill, for example: [https://github.com/andyearnshaw/Intl.js/].
|
||||
*
|
||||
* Formats a number as local percent.
|
||||
*
|
||||
@ -103,18 +111,21 @@ export class DecimalPipe extends NumberPipe implements PipeTransform {
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/pipes/ts/number_pipe/number_pipe_example.ts region='PercentPipe'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Pipe({name: 'percent'})
|
||||
@Injectable()
|
||||
export class PercentPipe extends NumberPipe implements PipeTransform {
|
||||
export class PercentPipe implements PipeTransform {
|
||||
transform(value: any, digits: string = null): string {
|
||||
return NumberPipe._format(value, NumberFormatStyle.Percent, digits);
|
||||
return formatNumber(PercentPipe, value, NumberFormatStyle.Percent, digits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this pipe uses the Internationalization API.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers. For other browsers please use an
|
||||
* polyfill, for example: [https://github.com/andyearnshaw/Intl.js/].
|
||||
*
|
||||
*
|
||||
* Formats a number as local currency.
|
||||
*
|
||||
@ -131,13 +142,15 @@ export class PercentPipe extends NumberPipe implements PipeTransform {
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/pipes/ts/number_pipe/number_pipe_example.ts region='CurrencyPipe'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Pipe({name: 'currency'})
|
||||
@Injectable()
|
||||
export class CurrencyPipe extends NumberPipe implements PipeTransform {
|
||||
transform(value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false,
|
||||
digits: string = null): string {
|
||||
return NumberPipe._format(value, NumberFormatStyle.Currency, digits, currencyCode,
|
||||
symbolDisplay);
|
||||
export class CurrencyPipe implements PipeTransform {
|
||||
transform(
|
||||
value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false,
|
||||
digits: string = null): string {
|
||||
return formatNumber(
|
||||
CurrencyPipe, value, NumberFormatStyle.Currency, digits, currencyCode, symbolDisplay);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
import {Injectable, PipeTransform, Pipe} from '@angular/core';
|
||||
|
||||
import {
|
||||
isBlank,
|
||||
isString,
|
||||
isNumber,
|
||||
isFunction,
|
||||
RegExpWrapper,
|
||||
StringWrapper
|
||||
} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {StringWrapper, isBlank, isFunction, isNumber, isString} from '../facade/lang';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
/**
|
||||
@ -35,12 +34,15 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
* --Note--: The 'pattern' parameter will be converted to a RegExp instance. Make sure to escape the
|
||||
* string properly if you are matching for regular expression special characters like parenthesis,
|
||||
* brackets etc.
|
||||
*
|
||||
* @deprecated The current pipe has limited functionality. The pipe api is not meant to be able
|
||||
* express complex yet generic value transformations. We recommend that these transformations happen
|
||||
* in the component logic instead.
|
||||
*/
|
||||
|
||||
@Pipe({name: 'replace'})
|
||||
@Injectable()
|
||||
export class ReplacePipe implements PipeTransform {
|
||||
transform(value: any, pattern: string | RegExp, replacement: Function | string): any {
|
||||
transform(value: any, pattern: string|RegExp, replacement: Function|string): any {
|
||||
if (isBlank(value)) {
|
||||
return value;
|
||||
}
|
||||
@ -57,14 +59,14 @@ export class ReplacePipe implements PipeTransform {
|
||||
if (!this._supportedReplacement(replacement)) {
|
||||
throw new InvalidPipeArgumentException(ReplacePipe, replacement);
|
||||
}
|
||||
// template fails with literal RegExp e.g /pattern/igm
|
||||
// var rgx = pattern instanceof RegExp ? pattern : RegExpWrapper.create(pattern);
|
||||
|
||||
if (isFunction(replacement)) {
|
||||
var rgxPattern = isString(pattern) ? RegExpWrapper.create(<string>pattern) : <RegExp>pattern;
|
||||
const rgxPattern = isString(pattern) ? new RegExp(pattern, 'g') : pattern;
|
||||
|
||||
return StringWrapper.replaceAllMapped(input, rgxPattern, <Function>replacement);
|
||||
return StringWrapper.replaceAllMapped(
|
||||
input, rgxPattern, <(m: string[]) => string>replacement);
|
||||
}
|
||||
|
||||
if (pattern instanceof RegExp) {
|
||||
// use the replaceAll variant
|
||||
return StringWrapper.replaceAll(input, pattern, <string>replacement);
|
||||
|
@ -1,9 +1,14 @@
|
||||
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
|
||||
|
||||
import {isBlank, isString, isArray, StringWrapper} from '../../src/facade/lang';
|
||||
import {BaseException} from '../../src/facade/exceptions';
|
||||
import {ListWrapper} from '../../src/facade/collection';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {StringWrapper, isArray, isBlank, isString} from '../facade/lang';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
/**
|
||||
@ -42,6 +47,8 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
* When operating on a [List], the returned list is always a copy even when all
|
||||
* the elements are being returned.
|
||||
*
|
||||
* When operating on a blank value, returns it.
|
||||
*
|
||||
* ## List Example
|
||||
*
|
||||
* This `ngFor` example:
|
||||
@ -56,16 +63,17 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
* ## String Examples
|
||||
*
|
||||
* {@example core/pipes/ts/slice_pipe/slice_pipe_example.ts region='SlicePipe_string'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
|
||||
@Pipe({name: 'slice', pure: false})
|
||||
@Injectable()
|
||||
export class SlicePipe implements PipeTransform {
|
||||
transform(value: any, start: number, end: number = null): any {
|
||||
if (isBlank(value)) return value;
|
||||
if (!this.supports(value)) {
|
||||
throw new InvalidPipeArgumentException(SlicePipe, value);
|
||||
}
|
||||
if (isBlank(value)) return value;
|
||||
if (isString(value)) {
|
||||
return StringWrapper.slice(value, start, end);
|
||||
}
|
||||
|
@ -1,5 +1,13 @@
|
||||
import {PipeTransform, WrappedValue, Injectable, Pipe} from '@angular/core';
|
||||
import {isString, isBlank} from '../../src/facade/lang';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank, isString} from '../facade/lang';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
/**
|
||||
@ -8,9 +16,10 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Pipe({name: 'uppercase'})
|
||||
@Injectable()
|
||||
export class UpperCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (isBlank(value)) return value;
|
||||
|
@ -1,20 +1,16 @@
|
||||
import {
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {ComponentFixture, TestComponentBuilder} from '@angular/compiler/testing';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
import {ListWrapper, StringMapWrapper, SetWrapper} from '../../src/facade/collection';
|
||||
import {Component, provide} from '@angular/core';
|
||||
import {NgFor, NgClass} from '@angular/common';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgClass, NgFor} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
import {ComponentFixture, TestComponentBuilder} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
|
||||
import {ListWrapper, StringMapWrapper} from '../../src/facade/collection';
|
||||
|
||||
function detectChangesAndCheck(fixture: ComponentFixture<any>, classes: string) {
|
||||
fixture.detectChanges();
|
||||
@ -25,502 +21,567 @@ export function main() {
|
||||
describe('binding to CSS class list', () => {
|
||||
|
||||
it('should clean up when the directive is destroyed',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div *ngFor="let item of items" [ngClass]="item"></div>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [['0']];
|
||||
fixture.detectChanges();
|
||||
fixture.debugElement.componentInstance.items = [['1']];
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div *ngFor="let item of items" [ngClass]="item"></div>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture: ComponentFixture<TestComponent>) => {
|
||||
fixture.debugElement.componentInstance.items = [['0']];
|
||||
fixture.detectChanges();
|
||||
fixture.debugElement.componentInstance.items = [['1']];
|
||||
|
||||
detectChangesAndCheck(fixture, '1');
|
||||
detectChangesAndCheck(fixture, '1');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
describe('expressions evaluating to objects', () => {
|
||||
|
||||
it('should add classes specified in an object literal',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="{foo: true, bar: false}"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="{foo: true, bar: false}"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should add classes specified in an object literal without change in class names',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [ngClass]="{'foo-bar': true, 'fooBar': true}"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngClass]="{'foo-bar': true, 'fooBar': true}"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo-bar fooBar');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo-bar fooBar');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add and remove classes based on changes in object literal values',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="{foo: condition, bar: !condition}"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="{foo: condition, bar: !condition}"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
fixture.debugElement.componentInstance.condition = false;
|
||||
detectChangesAndCheck(fixture, 'bar');
|
||||
fixture.debugElement.componentInstance.condition = false;
|
||||
detectChangesAndCheck(fixture, 'bar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add and remove classes based on changes to the expression object',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'baz', true);
|
||||
detectChangesAndCheck(fixture, 'foo bar baz');
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'baz', true);
|
||||
detectChangesAndCheck(fixture, 'foo bar baz');
|
||||
|
||||
StringMapWrapper.delete(fixture.debugElement.componentInstance.objExpr, 'bar');
|
||||
detectChangesAndCheck(fixture, 'foo baz');
|
||||
StringMapWrapper.delete(fixture.debugElement.componentInstance.objExpr, 'bar');
|
||||
detectChangesAndCheck(fixture, 'foo baz');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add and remove classes based on reference changes to the expression object',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = {foo: true, bar: true};
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
fixture.debugElement.componentInstance.objExpr = {foo: true, bar: true};
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = {baz: true};
|
||||
detectChangesAndCheck(fixture, 'baz');
|
||||
fixture.debugElement.componentInstance.objExpr = {baz: true};
|
||||
detectChangesAndCheck(fixture, 'baz');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should remove active classes when expression evaluates to null',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, '');
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, '');
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = {'foo': false, 'bar': true};
|
||||
detectChangesAndCheck(fixture, 'bar');
|
||||
fixture.debugElement.componentInstance.objExpr = {'foo': false, 'bar': true};
|
||||
detectChangesAndCheck(fixture, 'bar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should allow multiple classes per expression',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = {
|
||||
'bar baz': true,
|
||||
'bar1 baz1': true
|
||||
};
|
||||
detectChangesAndCheck(fixture, 'bar baz bar1 baz1');
|
||||
fixture.debugElement.componentInstance.objExpr = {
|
||||
'bar baz': true,
|
||||
'bar1 baz1': true
|
||||
};
|
||||
detectChangesAndCheck(fixture, 'bar baz bar1 baz1');
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = {
|
||||
'bar baz': false,
|
||||
'bar1 baz1': true
|
||||
};
|
||||
detectChangesAndCheck(fixture, 'bar1 baz1');
|
||||
fixture.debugElement.componentInstance.objExpr = {
|
||||
'bar baz': false,
|
||||
'bar1 baz1': true
|
||||
};
|
||||
detectChangesAndCheck(fixture, 'bar1 baz1');
|
||||
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should split by one or more spaces between classes',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="objExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = {'foo bar baz': true};
|
||||
detectChangesAndCheck(fixture, 'foo bar baz');
|
||||
fixture.debugElement.componentInstance.objExpr = {'foo bar baz': true};
|
||||
detectChangesAndCheck(fixture, 'foo bar baz');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('expressions evaluating to lists', () => {
|
||||
|
||||
it('should add classes specified in a list literal',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [ngClass]="['foo', 'bar', 'foo-bar', 'fooBar']"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngClass]="['foo', 'bar', 'foo-bar', 'fooBar']"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add and remove classes based on changes to the expression',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="arrExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="arrExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var arrExpr: string[] = fixture.debugElement.componentInstance.arrExpr;
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var arrExpr: string[] = fixture.debugElement.componentInstance.arrExpr;
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
arrExpr.push('bar');
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
arrExpr.push('bar');
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
|
||||
arrExpr[1] = 'baz';
|
||||
detectChangesAndCheck(fixture, 'foo baz');
|
||||
arrExpr[1] = 'baz';
|
||||
detectChangesAndCheck(fixture, 'foo baz');
|
||||
|
||||
ListWrapper.remove(fixture.debugElement.componentInstance.arrExpr, 'baz');
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
ListWrapper.remove(fixture.debugElement.componentInstance.arrExpr, 'baz');
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add and remove classes when a reference changes',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="arrExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="arrExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
fixture.debugElement.componentInstance.arrExpr = ['bar'];
|
||||
detectChangesAndCheck(fixture, 'bar');
|
||||
fixture.debugElement.componentInstance.arrExpr = ['bar'];
|
||||
detectChangesAndCheck(fixture, 'bar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should take initial classes into account when a reference changes',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
fixture.debugElement.componentInstance.arrExpr = ['bar'];
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
fixture.debugElement.componentInstance.arrExpr = ['bar'];
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore empty or blank class names',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.debugElement.componentInstance.arrExpr = ['', ' '];
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
fixture.debugElement.componentInstance.arrExpr = ['', ' '];
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should trim blanks from class names',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.debugElement.componentInstance.arrExpr = [' bar '];
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
fixture.debugElement.componentInstance.arrExpr = [' bar '];
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should allow multiple classes per item in arrays',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="arrExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="arrExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.debugElement.componentInstance.arrExpr =
|
||||
['foo bar baz', 'foo1 bar1 baz1'];
|
||||
detectChangesAndCheck(fixture, 'foo bar baz foo1 bar1 baz1');
|
||||
fixture.debugElement.componentInstance.arrExpr =
|
||||
['foo bar baz', 'foo1 bar1 baz1'];
|
||||
detectChangesAndCheck(fixture, 'foo bar baz foo1 bar1 baz1');
|
||||
|
||||
fixture.debugElement.componentInstance.arrExpr = ['foo bar baz foobar'];
|
||||
detectChangesAndCheck(fixture, 'foo bar baz foobar');
|
||||
fixture.debugElement.componentInstance.arrExpr = ['foo bar baz foobar'];
|
||||
detectChangesAndCheck(fixture, 'foo bar baz foobar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('expressions evaluating to sets', () => {
|
||||
|
||||
it('should add and remove classes if the set instance changed',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="setExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="setExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var setExpr = new Set<string>();
|
||||
setExpr.add('bar');
|
||||
fixture.debugElement.componentInstance.setExpr = setExpr;
|
||||
detectChangesAndCheck(fixture, 'bar');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var setExpr = new Set<string>();
|
||||
setExpr.add('bar');
|
||||
fixture.debugElement.componentInstance.setExpr = setExpr;
|
||||
detectChangesAndCheck(fixture, 'bar');
|
||||
|
||||
setExpr = new Set<string>();
|
||||
setExpr.add('baz');
|
||||
fixture.debugElement.componentInstance.setExpr = setExpr;
|
||||
detectChangesAndCheck(fixture, 'baz');
|
||||
setExpr = new Set<string>();
|
||||
setExpr.add('baz');
|
||||
fixture.debugElement.componentInstance.setExpr = setExpr;
|
||||
detectChangesAndCheck(fixture, 'baz');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
describe('expressions evaluating to string', () => {
|
||||
|
||||
it('should add classes specified in a string literal',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [ngClass]="'foo bar foo-bar fooBar'"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngClass]="'foo bar foo-bar fooBar'"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add and remove classes based on changes to the expression',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="strExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="strExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
fixture.debugElement.componentInstance.strExpr = 'foo bar';
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
fixture.debugElement.componentInstance.strExpr = 'foo bar';
|
||||
detectChangesAndCheck(fixture, 'foo bar');
|
||||
|
||||
|
||||
fixture.debugElement.componentInstance.strExpr = 'baz';
|
||||
detectChangesAndCheck(fixture, 'baz');
|
||||
fixture.debugElement.componentInstance.strExpr = 'baz';
|
||||
detectChangesAndCheck(fixture, 'baz');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should remove active classes when switching from string to null',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [ngClass]="strExpr"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngClass]="strExpr"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
fixture.debugElement.componentInstance.strExpr = null;
|
||||
detectChangesAndCheck(fixture, '');
|
||||
fixture.debugElement.componentInstance.strExpr = null;
|
||||
detectChangesAndCheck(fixture, '');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should take initial classes into account when switching from string to null',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
fixture.debugElement.componentInstance.strExpr = null;
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
fixture.debugElement.componentInstance.strExpr = null;
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore empty and blank strings',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.strExpr = '';
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.strExpr = '';
|
||||
detectChangesAndCheck(fixture, 'foo');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('cooperation with other class-changing constructs', () => {
|
||||
|
||||
it('should co-operate with the class attribute',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div [ngClass]="objExpr" class="init foo"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div [ngClass]="objExpr" class="init foo"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, 'init foo bar');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, 'init foo bar');
|
||||
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
|
||||
detectChangesAndCheck(fixture, 'init bar');
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'foo', false);
|
||||
detectChangesAndCheck(fixture, 'init bar');
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, 'init foo');
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, 'init foo');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should co-operate with the interpolated class attribute',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, `init foo bar`);
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, `init foo bar`);
|
||||
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
|
||||
detectChangesAndCheck(fixture, `init bar`);
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'foo', false);
|
||||
detectChangesAndCheck(fixture, `init bar`);
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, `init foo`);
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, `init foo`);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should co-operate with the class attribute and binding to it',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, `init foo bar`);
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, `init foo bar`);
|
||||
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
|
||||
detectChangesAndCheck(fixture, `init bar`);
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'foo', false);
|
||||
detectChangesAndCheck(fixture, `init bar`);
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, `init foo`);
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, `init foo`);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should co-operate with the class attribute and class.name binding',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
'<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template =
|
||||
'<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'init foo baz');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'init foo baz');
|
||||
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, 'init foo baz bar');
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, 'init foo baz bar');
|
||||
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
|
||||
detectChangesAndCheck(fixture, 'init baz bar');
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'foo', false);
|
||||
detectChangesAndCheck(fixture, 'init baz bar');
|
||||
|
||||
fixture.debugElement.componentInstance.condition = false;
|
||||
detectChangesAndCheck(fixture, 'init bar');
|
||||
fixture.debugElement.componentInstance.condition = false;
|
||||
detectChangesAndCheck(fixture, 'init bar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should co-operate with initial class and class attribute binding when binding changes',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'init foo');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
detectChangesAndCheck(fixture, 'init foo');
|
||||
|
||||
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, 'init foo bar');
|
||||
StringMapWrapper.set(
|
||||
fixture.debugElement.componentInstance.objExpr, 'bar', true);
|
||||
detectChangesAndCheck(fixture, 'init foo bar');
|
||||
|
||||
fixture.debugElement.componentInstance.strExpr = 'baz';
|
||||
detectChangesAndCheck(fixture, 'init bar baz foo');
|
||||
fixture.debugElement.componentInstance.strExpr = 'baz';
|
||||
detectChangesAndCheck(fixture, 'init bar baz foo');
|
||||
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, 'init baz');
|
||||
fixture.debugElement.componentInstance.objExpr = null;
|
||||
detectChangesAndCheck(fixture, 'init baz');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@Component({selector: 'test-cmp', directives: [NgClass, NgFor], template: ''})
|
||||
|
@ -1,506 +1,566 @@
|
||||
import {
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
ddescribe,
|
||||
describe,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {expect} from '@angular/platform-browser/testing';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgFor, NgIf} from '@angular/common';
|
||||
import {Component, ContentChild, TemplateRef} from '@angular/core';
|
||||
import {TestComponentBuilder} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
import {ListWrapper} from '../../src/facade/collection';
|
||||
import {IS_DART} from '../../src/facade/lang';
|
||||
import {Component, TemplateRef, ContentChild} from '@angular/core';
|
||||
import {NgFor} from '@angular/common';
|
||||
import {NgIf} from '@angular/common';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
|
||||
let thisArg: any;
|
||||
|
||||
export function main() {
|
||||
describe('ngFor', () => {
|
||||
var TEMPLATE =
|
||||
const TEMPLATE =
|
||||
'<div><copy-me template="ngFor let item of items">{{item.toString()}};</copy-me></div>';
|
||||
|
||||
it('should reflect initial elements',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect added elements',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
(<number[]>fixture.debugElement.componentInstance.items).push(3);
|
||||
fixture.detectChanges();
|
||||
(<number[]>fixture.debugElement.componentInstance.items).push(3);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect removed elements',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 1);
|
||||
fixture.detectChanges();
|
||||
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect moved elements',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
|
||||
(<number[]>fixture.debugElement.componentInstance.items).push(1);
|
||||
fixture.detectChanges();
|
||||
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
|
||||
(<number[]>fixture.debugElement.componentInstance.items).push(1);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('2;1;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('2;1;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect a mix of all changes (additions/removals/moves)',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5];
|
||||
fixture.detectChanges();
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5];
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.debugElement.componentInstance.items = [6, 2, 7, 0, 4, 8];
|
||||
fixture.detectChanges();
|
||||
fixture.debugElement.componentInstance.items = [6, 2, 7, 0, 4, 8];
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('6;2;7;0;4;8;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('6;2;7;0;4;8;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should iterate over an array of objects',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
'<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
// INIT
|
||||
fixture.debugElement.componentInstance.items =
|
||||
[{'name': 'misko'}, {'name': 'shyam'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;');
|
||||
// INIT
|
||||
fixture.debugElement.componentInstance.items =
|
||||
[{'name': 'misko'}, {'name': 'shyam'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;');
|
||||
|
||||
// GROW
|
||||
(<any[]>fixture.debugElement.componentInstance.items).push({'name': 'adam'});
|
||||
fixture.detectChanges();
|
||||
// GROW
|
||||
(<any[]>fixture.debugElement.componentInstance.items).push({'name': 'adam'});
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;adam;');
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;adam;');
|
||||
|
||||
// SHRINK
|
||||
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 2);
|
||||
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
|
||||
fixture.detectChanges();
|
||||
// SHRINK
|
||||
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 2);
|
||||
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('shyam;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('shyam;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should gracefully handle nulls',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should gracefully handle ref changing to null and back',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
|
||||
|
||||
fixture.debugElement.componentInstance.items = null;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
fixture.debugElement.componentInstance.items = null;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
|
||||
fixture.debugElement.componentInstance.items = [1, 2, 3];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
|
||||
fixture.debugElement.componentInstance.items = [1, 2, 3];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw on non-iterable ref and suggest using an array',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE).createAsync(TestComponent).then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = 'whaaa';
|
||||
expect(() => fixture.detectChanges())
|
||||
.toThrowError(
|
||||
/Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays/);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
if (!IS_DART) {
|
||||
it('should throw on non-iterable ref and suggest using an array',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = 'whaaa';
|
||||
try {
|
||||
fixture.detectChanges()
|
||||
} catch (e) {
|
||||
expect(e.message).toContain(
|
||||
`Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays.`);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
||||
it('should throw on ref changing to string',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
|
||||
|
||||
fixture.debugElement.componentInstance.items = 'whaaa';
|
||||
expect(() => fixture.detectChanges()).toThrowError();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
fixture.debugElement.componentInstance.items = 'whaaa';
|
||||
expect(() => fixture.detectChanges()).toThrowError();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should works with duplicates',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var a = new Foo();
|
||||
fixture.debugElement.componentInstance.items = [a, a];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('foo;foo;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, TEMPLATE)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var a = new Foo();
|
||||
fixture.debugElement.componentInstance.items = [a, a];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('foo;foo;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should repeat over nested arrays',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div>' +
|
||||
'<div template="ngFor let item of items">' +
|
||||
'<div template="ngFor let subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div>|' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template = '<div>' +
|
||||
'<div template="ngFor let item of items">' +
|
||||
'<div template="ngFor let subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div>|' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;|c-1;|');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;|c-1;|');
|
||||
|
||||
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('e-1;|f-2;g-2;|');
|
||||
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('e-1;|f-2;g-2;|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should repeat over nested arrays with no intermediate element',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div><template ngFor let-item [ngForOf]="items">' +
|
||||
'<div template="ngFor let subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div></template></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template = '<div><template ngFor let-item [ngForOf]="items">' +
|
||||
'<div template="ngFor let subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div></template></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;c-1;');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;c-1;');
|
||||
|
||||
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('e-1;f-2;g-2;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('e-1;f-2;g-2;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should repeat over nested ngIf that are the last node in the ngFor temlate',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
|
||||
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
|
||||
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var el = fixture.debugElement.nativeElement;
|
||||
var items = [1];
|
||||
fixture.debugElement.componentInstance.items = items;
|
||||
fixture.detectChanges();
|
||||
expect(el).toHaveText('0|even|');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
const el = fixture.debugElement.nativeElement;
|
||||
const items = [1];
|
||||
fixture.debugElement.componentInstance.items = items;
|
||||
fixture.detectChanges();
|
||||
expect(el).toHaveText('0|even|');
|
||||
|
||||
items.push(1);
|
||||
fixture.detectChanges();
|
||||
expect(el).toHaveText('0|even|1|');
|
||||
items.push(1);
|
||||
fixture.detectChanges();
|
||||
expect(el).toHaveText('0|even|1|');
|
||||
|
||||
items.push(1);
|
||||
fixture.detectChanges();
|
||||
expect(el).toHaveText('0|even|1|2|even|');
|
||||
items.push(1);
|
||||
fixture.detectChanges();
|
||||
expect(el).toHaveText('0|even|1|2|even|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display indices correctly',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
'<div><copy-me template="ngFor: let item of items; let i=index">{{i.toString()}}</copy-me></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
'<div><copy-me template="ngFor: let item of items; let i=index">{{i.toString()}}</copy-me></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
|
||||
|
||||
fixture.debugElement.componentInstance.items = [1, 2, 6, 7, 4, 3, 5, 8, 9, 0];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
fixture.debugElement.componentInstance.items = [1, 2, 6, 7, 4, 3, 5, 8, 9, 0];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display first item correctly',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
'<div><copy-me template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</copy-me></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
'<div><copy-me template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</copy-me></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('truefalsefalse');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('truefalsefalse');
|
||||
|
||||
fixture.debugElement.componentInstance.items = [2, 1];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
fixture.debugElement.componentInstance.items = [2, 1];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display last item correctly',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
'<div><copy-me template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</copy-me></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
'<div><copy-me template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</copy-me></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('falsefalsetrue');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('falsefalsetrue');
|
||||
|
||||
fixture.debugElement.componentInstance.items = [2, 1];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
fixture.debugElement.componentInstance.items = [2, 1];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display even items correctly',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
'<div><copy-me template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</copy-me></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
'<div><copy-me template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</copy-me></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('truefalsetrue');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('truefalsetrue');
|
||||
|
||||
fixture.debugElement.componentInstance.items = [2, 1];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
fixture.debugElement.componentInstance.items = [2, 1];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display odd items correctly',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
'<div><copy-me template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</copy-me></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
'<div><copy-me template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</copy-me></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2, 3];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('falsetruefalsetrue');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [0, 1, 2, 3];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('falsetruefalsetrue');
|
||||
|
||||
fixture.debugElement.componentInstance.items = [2, 1];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
fixture.debugElement.componentInstance.items = [2, 1];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should allow to use a custom template',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(
|
||||
TestComponent,
|
||||
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>')
|
||||
.overrideTemplate(
|
||||
ComponentUsingTestComponent,
|
||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
|
||||
.createAsync(ComponentUsingTestComponent)
|
||||
.then((fixture) => {
|
||||
var testComponent = fixture.debugElement.children[0];
|
||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(
|
||||
TestComponent,
|
||||
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>')
|
||||
.overrideTemplate(
|
||||
ComponentUsingTestComponent,
|
||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
|
||||
.createAsync(ComponentUsingTestComponent)
|
||||
.then((fixture) => {
|
||||
const testComponent = fixture.debugElement.children[0];
|
||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should use a default template if a custom one is null',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
|
||||
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`)
|
||||
.overrideTemplate(ComponentUsingTestComponent, '<test-cmp></test-cmp>')
|
||||
.createAsync(ComponentUsingTestComponent)
|
||||
.then((fixture) => {
|
||||
var testComponent = fixture.debugElement.children[0];
|
||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
.overrideTemplate(ComponentUsingTestComponent, '<test-cmp></test-cmp>')
|
||||
.createAsync(ComponentUsingTestComponent)
|
||||
.then((fixture) => {
|
||||
const testComponent = fixture.debugElement.children[0];
|
||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should use a custom template when both default and a custom one are present',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
|
||||
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`)
|
||||
.overrideTemplate(
|
||||
ComponentUsingTestComponent,
|
||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
|
||||
.createAsync(ComponentUsingTestComponent)
|
||||
.then((fixture) => {
|
||||
var testComponent = fixture.debugElement.children[0];
|
||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
.overrideTemplate(
|
||||
ComponentUsingTestComponent,
|
||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
|
||||
.createAsync(ComponentUsingTestComponent)
|
||||
.then((fixture) => {
|
||||
const testComponent = fixture.debugElement.children[0];
|
||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('track by', () => {
|
||||
it('should set the context to the component instance',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
thisArg = null;
|
||||
fixture.detectChanges();
|
||||
expect(thisArg).toBe(fixture.debugElement.componentInstance);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('track by', function() {
|
||||
it('should not replace tracked items',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
|
||||
<p>{{items[i]}}</p>
|
||||
</template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var buildItemList =
|
||||
() => {
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var buildItemList = () => {
|
||||
fixture.debugElement.componentInstance.items = [{'id': 'a'}];
|
||||
fixture.detectChanges();
|
||||
return fixture.debugElement.queryAll(By.css('p'))[0];
|
||||
}
|
||||
};
|
||||
|
||||
var firstP = buildItemList();
|
||||
var finalP = buildItemList();
|
||||
expect(finalP.nativeElement).toBe(firstP.nativeElement);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
var firstP = buildItemList();
|
||||
var finalP = buildItemList();
|
||||
expect(finalP.nativeElement).toBe(firstP.nativeElement);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
it('should update implicit local variable on view',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'blue'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('blue');
|
||||
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'red'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('red');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'blue'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('blue');
|
||||
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'red'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('red');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
it('should move items around and keep them updated ',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items =
|
||||
[{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('blueyellow');
|
||||
fixture.debugElement.componentInstance.items =
|
||||
[{'id': 'b', 'color': 'orange'}, {'id': 'a', 'color': 'red'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('orangered');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items =
|
||||
[{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('blueyellow');
|
||||
fixture.debugElement.componentInstance.items =
|
||||
[{'id': 'b', 'color': 'orange'}, {'id': 'a', 'color': 'red'}];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('orangered');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should handle added and removed items properly when tracking by index',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = ['a', 'b', 'c', 'd'];
|
||||
fixture.detectChanges();
|
||||
fixture.debugElement.componentInstance.items = ['e', 'f', 'g', 'h'];
|
||||
fixture.detectChanges();
|
||||
fixture.debugElement.componentInstance.items = ['e', 'f', 'h'];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('efh');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.items = ['a', 'b', 'c', 'd'];
|
||||
fixture.detectChanges();
|
||||
fixture.debugElement.componentInstance.items = ['e', 'f', 'g', 'h'];
|
||||
fixture.detectChanges();
|
||||
fixture.debugElement.componentInstance.items = ['e', 'f', 'h'];
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('efh');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -516,6 +576,7 @@ class TestComponent {
|
||||
constructor() { this.items = [1, 2]; }
|
||||
trackById(index: number, item: any): string { return item['id']; }
|
||||
trackByIndex(index: number, item: any): number { return index; }
|
||||
trackByContext(): void { thisArg = this; }
|
||||
}
|
||||
|
||||
@Component({selector: 'outer-cmp', directives: [TestComponent], template: ''})
|
||||
|
@ -1,244 +1,243 @@
|
||||
import {
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {expect} from '@angular/platform-browser/testing';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
import {NgIf} from '@angular/common';
|
||||
|
||||
import {IS_DART} from '../../src/facade/lang';
|
||||
import {Component} from '@angular/core';
|
||||
import {TestComponentBuilder} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('ngIf directive', () => {
|
||||
it('should work in a template attribute',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should work in a template element',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var html =
|
||||
'<div><template [ngIf]="booleanCondition"><copy-me>hello2</copy-me></template></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var html =
|
||||
'<div><template [ngIf]="booleanCondition"><copy-me>hello2</copy-me></template></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello2');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello2');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should toggle node when condition changes',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
|
||||
fixture.debugElement.componentInstance.booleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
fixture.debugElement.componentInstance.booleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
|
||||
fixture.debugElement.componentInstance.booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
fixture.debugElement.componentInstance.booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should handle nested if correctly',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var html =
|
||||
'<div><template [ngIf]="booleanCondition"><copy-me *ngIf="nestedBooleanCondition">hello</copy-me></template></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var html =
|
||||
'<div><template [ngIf]="booleanCondition"><copy-me *ngIf="nestedBooleanCondition">hello</copy-me></template></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
|
||||
fixture.debugElement.componentInstance.booleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
fixture.debugElement.componentInstance.booleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
|
||||
fixture.debugElement.componentInstance.nestedBooleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
fixture.debugElement.componentInstance.nestedBooleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
|
||||
fixture.debugElement.componentInstance.nestedBooleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
fixture.debugElement.componentInstance.nestedBooleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
|
||||
fixture.debugElement.componentInstance.booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
fixture.debugElement.componentInstance.booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should update several nodes with if',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var html =
|
||||
'<div>' +
|
||||
'<copy-me template="ngIf numberCondition + 1 >= 2">helloNumber</copy-me>' +
|
||||
'<copy-me template="ngIf stringCondition == \'foo\'">helloString</copy-me>' +
|
||||
'<copy-me template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</copy-me>' +
|
||||
'</div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var html = '<div>' +
|
||||
'<copy-me template="ngIf numberCondition + 1 >= 2">helloNumber</copy-me>' +
|
||||
'<copy-me template="ngIf stringCondition == \'foo\'">helloString</copy-me>' +
|
||||
'<copy-me template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</copy-me>' +
|
||||
'</div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(3);
|
||||
expect(getDOM().getText(fixture.debugElement.nativeElement))
|
||||
.toEqual('helloNumberhelloStringhelloFunction');
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(3);
|
||||
expect(getDOM().getText(fixture.debugElement.nativeElement))
|
||||
.toEqual('helloNumberhelloStringhelloFunction');
|
||||
|
||||
fixture.debugElement.componentInstance.numberCondition = 0;
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('helloString');
|
||||
fixture.debugElement.componentInstance.numberCondition = 0;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('helloString');
|
||||
|
||||
fixture.debugElement.componentInstance.numberCondition = 1;
|
||||
fixture.debugElement.componentInstance.stringCondition = "bar";
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('helloNumber');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
fixture.debugElement.componentInstance.numberCondition = 1;
|
||||
fixture.debugElement.componentInstance.stringCondition = 'bar';
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('helloNumber');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not add the element twice if the condition goes from true to true (JS)',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
|
||||
|
||||
if (!IS_DART) {
|
||||
it('should not add the element twice if the condition goes from true to true (JS)',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
fixture.debugElement.componentInstance.numberCondition = 2;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
|
||||
fixture.debugElement.componentInstance.numberCondition = 2;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(1);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
it('should not recreate the element if the condition goes from true to true (JS)',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
|
||||
|
||||
it('should not recreate the element if the condition goes from true to true (JS)',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
getDOM().addClass(
|
||||
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'),
|
||||
'foo');
|
||||
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
getDOM().addClass(
|
||||
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'), "foo");
|
||||
|
||||
fixture.debugElement.componentInstance.numberCondition = 2;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().hasClass(
|
||||
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'),
|
||||
"foo"))
|
||||
.toBe(true);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
if (IS_DART) {
|
||||
it('should not create the element if the condition is not a boolean (DART)',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, html)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
expect(() => fixture.detectChanges()).toThrowError();
|
||||
expect(getDOM()
|
||||
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
|
||||
.length)
|
||||
.toEqual(0);
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
}
|
||||
fixture.debugElement.componentInstance.numberCondition = 2;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().hasClass(
|
||||
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'),
|
||||
'foo'))
|
||||
.toBe(true);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -253,7 +252,7 @@ class TestComponent {
|
||||
this.booleanCondition = true;
|
||||
this.nestedBooleanCondition = true;
|
||||
this.numberCondition = 1;
|
||||
this.stringCondition = "foo";
|
||||
this.functionCondition = function(s, n) { return s == "foo" && n == 1; };
|
||||
this.stringCondition = 'foo';
|
||||
this.functionCondition = function(s: any, n: any): boolean { return s == 'foo' && n == 1; };
|
||||
}
|
||||
}
|
||||
|
@ -1,135 +1,182 @@
|
||||
import {
|
||||
beforeEachProviders,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Injectable, provide} from '@angular/core';
|
||||
import {NgPlural, NgPluralCase, NgLocalization} from '@angular/common';
|
||||
import {NgLocalization, NgPlural, NgPluralCase} from '@angular/common';
|
||||
import {Component, Injectable} from '@angular/core';
|
||||
import {TestComponentBuilder} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('switch', () => {
|
||||
beforeEachProviders(() => [provide(NgLocalization, {useClass: TestLocalizationMap})]);
|
||||
beforeEachProviders(() => [{provide: NgLocalization, useClass: TestLocalization}]);
|
||||
|
||||
it('should display the template according to the exact value',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
|
||||
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
|
||||
'</ul></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
|
||||
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 0;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 0;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 1;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.');
|
||||
fixture.debugElement.componentInstance.switchValue = 1;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
// https://github.com/angular/angular/issues/9868
|
||||
// https://github.com/angular/angular/issues/9882
|
||||
it('should not throw when ngPluralCase contains expressions',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 0;
|
||||
expect(() => fixture.detectChanges()).not.toThrow();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should be applicable to <ng-container> elements',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ng-container [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0">you have no messages.</template>' +
|
||||
'<template ngPluralCase="=1">you have one message.</template>' +
|
||||
'</ng-container></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 0;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 1;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display the template according to the category',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
|
||||
'</ul></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 2;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 2;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('you have a few messages.');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 8;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have many messages.');
|
||||
fixture.debugElement.componentInstance.switchValue = 8;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have many messages.');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should default to other when no matches are found',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="other"><li>default message.</li></template>' +
|
||||
'</ul></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="other"><li>default message.</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 100;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('default message.');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 100;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('default message.');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should prioritize value matches over category matches',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="=2">you have two messages.</template>' +
|
||||
'</ul></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="=2">you have two messages.</template>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 2;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have two messages.');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.switchValue = 2;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have two messages.');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 3;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.');
|
||||
fixture.debugElement.componentInstance.switchValue = 3;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('you have a few messages.');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class TestLocalizationMap extends NgLocalization {
|
||||
class TestLocalization extends NgLocalization {
|
||||
getPluralCategory(value: number): string {
|
||||
if (value > 1 && value < 4) {
|
||||
return 'few';
|
||||
} else if (value >= 4 && value < 10) {
|
||||
return 'many';
|
||||
} else {
|
||||
return 'other';
|
||||
}
|
||||
if (value >= 4 && value < 10) {
|
||||
return 'many';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'test-cmp', directives: [NgPlural, NgPluralCase], template: ''})
|
||||
class TestComponent {
|
||||
switchValue: number;
|
||||
|
||||
constructor() { this.switchValue = null; }
|
||||
switchValue: number = null;
|
||||
}
|
||||
|
@ -1,155 +1,211 @@
|
||||
import {
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, xdescribe, describe, expect, iit, inject, it, xit,} from '@angular/core/testing/testing_internal';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/core/testing';
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {NgStyle} from '@angular/common/src/directives/ng_style';
|
||||
|
||||
function expectNativeEl(fixture: ComponentFixture<any>) {
|
||||
return <any>expect(fixture.debugElement.children[0].nativeElement);
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('binding to CSS styles', () => {
|
||||
|
||||
it('should add styles specified in an object literal',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
|
||||
.toEqual('40px');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add and change styles specified in an object expression',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [ngStyle]="expr"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngStyle]="expr"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var expr: Map<string, any>;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
var expr: Map<string, any>;
|
||||
|
||||
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
|
||||
.toEqual('40px');
|
||||
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||
|
||||
expr = fixture.debugElement.componentInstance.expr;
|
||||
expr['max-width'] = '30%';
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
|
||||
.toEqual('30%');
|
||||
expr = fixture.debugElement.componentInstance.expr;
|
||||
(expr as any)['max-width'] = '30%';
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add and remove styles specified using style.unit notation',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngStyle]="{'max-width.px': expr}"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.debugElement.componentInstance.expr = '40';
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||
|
||||
fixture.debugElement.componentInstance.expr = null;
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).not.toHaveCssStyle('max-width');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should update styles using style.unit notation when unit changes',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngStyle]="expr"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.debugElement.componentInstance.expr = {'max-width.px': '40'};
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||
|
||||
fixture.debugElement.componentInstance.expr = {'max-width.em': '40'};
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40em'});
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
// keyValueDiffer is sensitive to key order #9115
|
||||
it('should change styles specified in an object expression',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
const template = `<div [ngStyle]="expr"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.expr = {
|
||||
// height, width order is important here
|
||||
height: '10px',
|
||||
width: '10px'
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'height': '10px', 'width': '10px'});
|
||||
|
||||
fixture.debugElement.componentInstance.expr = {
|
||||
// width, height order is important here
|
||||
width: '5px',
|
||||
height: '5px',
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'height': '5px', 'width': '5px'});
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should remove styles when deleting a key in an object expression',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [ngStyle]="expr"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [ngStyle]="expr"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
|
||||
.toEqual('40px');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||
|
||||
StringMapWrapper.delete(fixture.debugElement.componentInstance.expr, 'max-width');
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
|
||||
.toEqual('');
|
||||
delete fixture.debugElement.componentInstance.expr['max-width'];
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).not.toHaveCssStyle('max-width');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should co-operate with the style attribute',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div style="font-size: 12px" [ngStyle]="expr"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div style="font-size: 12px" [ngStyle]="expr"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
|
||||
.toEqual('40px');
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
|
||||
.toEqual('12px');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle(
|
||||
{'max-width': '40px', 'font-size': '12px'});
|
||||
|
||||
StringMapWrapper.delete(fixture.debugElement.componentInstance.expr, 'max-width');
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
|
||||
.toEqual('');
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
|
||||
.toEqual('12px');
|
||||
delete fixture.debugElement.componentInstance.expr['max-width'];
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).not.toHaveCssStyle('max-width');
|
||||
expectNativeEl(fixture).toHaveCssStyle({'font-size': '12px'});
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should co-operate with the style.[styleName]="expr" special-case in the compiler',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<div [style.font-size.px]="12" [ngStyle]="expr"></div>`;
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<div [style.font-size.px]="12" [ngStyle]="expr"></div>`;
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
|
||||
.toEqual('40px');
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
|
||||
.toEqual('12px');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle(
|
||||
{'max-width': '40px', 'font-size': '12px'});
|
||||
|
||||
StringMapWrapper.delete(fixture.debugElement.componentInstance.expr, 'max-width');
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
|
||||
.toEqual('12px');
|
||||
delete fixture.debugElement.componentInstance.expr['max-width'];
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).not.toHaveCssStyle('max-width');
|
||||
expectNativeEl(fixture).toHaveCssStyle({'font-size': '12px'});
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(
|
||||
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
|
||||
.toEqual('');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
})
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@Component({selector: 'test-cmp', directives: [NgStyle], template: ''})
|
||||
class TestComponent {
|
||||
expr;
|
||||
expr: any;
|
||||
}
|
||||
|
@ -1,150 +1,185 @@
|
||||
import {
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
import {Component} from '@angular/core';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from '@angular/common';
|
||||
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
import {TestComponentBuilder} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('switch', () => {
|
||||
describe('switch value changes', () => {
|
||||
it('should switch amongst when values',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchWhen="a"><li>when a</li></template>' +
|
||||
'<template ngSwitchWhen="b"><li>when b</li></template>' +
|
||||
'</ul></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchCase="a"><li>when a</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'a';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when a');
|
||||
fixture.debugElement.componentInstance.switchValue = 'a';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when a');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'b';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when b');
|
||||
fixture.debugElement.componentInstance.switchValue = 'b';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when b');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
// TODO(robwormald): deprecate and remove
|
||||
it('should switch amongst when values using switchWhen',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchWhen="a"><li>when a</li></template>' +
|
||||
'<template ngSwitchWhen="b"><li>when b</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'a';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when a');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'b';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when b');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should switch amongst when values with fallback to default',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<li template="ngSwitchWhen \'a\'">when a</li>' +
|
||||
'<li template="ngSwitchDefault">when default</li>' +
|
||||
'</ul></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<li template="ngSwitchCase \'a\'">when a</li>' +
|
||||
'<li template="ngSwitchDefault">when default</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when default');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when default');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'a';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when a');
|
||||
fixture.debugElement.componentInstance.switchValue = 'a';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when a');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'b';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when default');
|
||||
fixture.debugElement.componentInstance.switchValue = 'b';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when default');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support multiple whens with the same value',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchWhen="a"><li>when a1;</li></template>' +
|
||||
'<template ngSwitchWhen="b"><li>when b1;</li></template>' +
|
||||
'<template ngSwitchWhen="a"><li>when a2;</li></template>' +
|
||||
'<template ngSwitchWhen="b"><li>when b2;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||
'</ul></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
|
||||
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('when default1;when default2;');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement)
|
||||
.toHaveText('when default1;when default2;');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'a';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when a1;when a2;');
|
||||
fixture.debugElement.componentInstance.switchValue = 'a';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when a1;when a2;');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'b';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when b1;when b2;');
|
||||
fixture.debugElement.componentInstance.switchValue = 'b';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when b1;when b2;');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('when values changes', () => {
|
||||
it('should switch amongst when values',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template [ngSwitchWhen]="when1"><li>when 1;</li></template>' +
|
||||
'<template [ngSwitchWhen]="when2"><li>when 2;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default;</li></template>' +
|
||||
'</ul></div>';
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' +
|
||||
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default;</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.when1 = 'a';
|
||||
fixture.debugElement.componentInstance.when2 = 'b';
|
||||
fixture.debugElement.componentInstance.switchValue = 'a';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.debugElement.componentInstance.when1 = 'a';
|
||||
fixture.debugElement.componentInstance.when2 = 'b';
|
||||
fixture.debugElement.componentInstance.switchValue = 'a';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'b';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when 2;');
|
||||
fixture.debugElement.componentInstance.switchValue = 'b';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when 2;');
|
||||
|
||||
fixture.debugElement.componentInstance.switchValue = 'c';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
|
||||
fixture.debugElement.componentInstance.switchValue = 'c';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
|
||||
|
||||
fixture.debugElement.componentInstance.when1 = 'c';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
|
||||
fixture.debugElement.componentInstance.when1 = 'c';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
|
||||
|
||||
fixture.debugElement.componentInstance.when1 = 'd';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
|
||||
fixture.debugElement.componentInstance.when1 = 'd';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Component(
|
||||
{selector: 'test-cmp', directives: [NgSwitch, NgSwitchWhen, NgSwitchDefault], template: ''})
|
||||
{selector: 'test-cmp', directives: [NgSwitch, NgSwitchCase, NgSwitchDefault], template: ''})
|
||||
class TestComponent {
|
||||
switchValue: any;
|
||||
when1: any;
|
||||
|
@ -1,100 +1,207 @@
|
||||
import {
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
import {Component, Directive, TemplateRef, ContentChildren, QueryList} from '@angular/core';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgTemplateOutlet} from '@angular/common';
|
||||
import {Component, ContentChildren, Directive, QueryList, TemplateRef} from '@angular/core';
|
||||
import {TestComponentBuilder} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('insert', () => {
|
||||
it('should do nothing if templateRef is null',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<template [ngTemplateOutlet]="null"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = `<template [ngTemplateOutlet]="null"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should insert content specified by TemplateRef',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should clear content if TemplateRef becomes null',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.detectChanges();
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
fixture.detectChanges();
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
|
||||
fixture.componentInstance.currentTplRef = null;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
fixture.componentInstance.currentTplRef = null;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should swap content if TemplateRef changes',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = `<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.detectChanges();
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
fixture.detectChanges();
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.last;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('bar');
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.last;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('bar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display template if context is null',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect initial context and changes',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template =
|
||||
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('bar');
|
||||
|
||||
fixture.componentInstance.context.foo = 'alter-bar';
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('alter-bar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect user defined $implicit property in the context',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template =
|
||||
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
|
||||
fixture.componentInstance.context = {
|
||||
$implicit: fixture.componentInstance.context
|
||||
};
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('bar');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect context re-binding',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template =
|
||||
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
var refs = fixture.debugElement.children[0].references['refs'];
|
||||
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
|
||||
fixture.componentInstance.context = {shawshank: 'brooks'};
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('brooks');
|
||||
|
||||
fixture.componentInstance.context = {shawshank: 'was here'};
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('was here');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -107,4 +214,5 @@ class CaptureTplRefs {
|
||||
@Component({selector: 'test-cmp', directives: [NgTemplateOutlet, CaptureTplRefs], template: ''})
|
||||
class TestComponent {
|
||||
currentTplRef: TemplateRef<any>;
|
||||
context: any = {foo: 'bar'};
|
||||
}
|
||||
|
@ -1,62 +1,67 @@
|
||||
import {
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive} from '@angular/core';
|
||||
import {ElementRef} from '@angular/core/src/linker/element_ref';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
import {TestComponentBuilder} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('non-bindable', () => {
|
||||
it('should not interpolate children',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div>{{text}}<span ngNonBindable>{{text}}</span></div>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('foo{{text}}');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div>{{text}}<span ngNonBindable>{{text}}</span></div>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('foo{{text}}');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore directives on child nodes',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div ngNonBindable><span id=child test-dec>{{text}}</span></div>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div ngNonBindable><span id=child test-dec>{{text}}</span></div>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
// We must use getDOM().querySelector instead of fixture.query here
|
||||
// since the elements inside are not compiled.
|
||||
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
|
||||
expect(getDOM().hasClass(span, 'compiled')).toBeFalsy();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
// We must use getDOM().querySelector instead of fixture.query here
|
||||
// since the elements inside are not compiled.
|
||||
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
|
||||
expect(getDOM().hasClass(span, 'compiled')).toBeFalsy();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should trigger directives on the same node',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var template = '<div><span id=child ngNonBindable test-dec>{{text}}</span></div>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
|
||||
expect(getDOM().hasClass(span, 'compiled')).toBeTruthy();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
})
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
var template = '<div><span id=child ngNonBindable test-dec>{{text}}</span></div>';
|
||||
tcb.overrideTemplate(TestComponent, template)
|
||||
.createAsync(TestComponent)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
|
||||
expect(getDOM().hasClass(span, 'compiled')).toBeTruthy();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@Directive({selector: '[test-dec]'})
|
||||
|
@ -1,102 +0,0 @@
|
||||
library angular2.test.directives.observable_list_iterable_diff_spec;
|
||||
|
||||
import 'package:angular2/testing_internal.dart';
|
||||
import 'package:observe/observe.dart' show ObservableList;
|
||||
import 'package:angular2/core.dart' show ChangeDetectorRef;
|
||||
import 'package:angular2/common.dart' show ObservableListDiffFactory;
|
||||
|
||||
@proxy
|
||||
class SpyChangeDetectorRef extends SpyObject implements ChangeDetectorRef {}
|
||||
|
||||
main() {
|
||||
describe('ObservableListDiff', () {
|
||||
var factory, changeDetectorRef;
|
||||
|
||||
beforeEach(() {
|
||||
factory = const ObservableListDiffFactory();
|
||||
changeDetectorRef = new SpyChangeDetectorRef();
|
||||
});
|
||||
|
||||
describe("supports", () {
|
||||
it("should be true for ObservableList", () {
|
||||
expect(factory.supports(new ObservableList())).toBe(true);
|
||||
});
|
||||
|
||||
it("should be false otherwise", () {
|
||||
expect(factory.supports([1, 2, 3])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return itself when called the first time", () {
|
||||
final d = factory.create(changeDetectorRef);
|
||||
final c = new ObservableList.from([1, 2]);
|
||||
expect(d.diff(c)).toBe(d);
|
||||
});
|
||||
|
||||
it("should return itself when no changes between the calls", () {
|
||||
final d = factory.create(changeDetectorRef);
|
||||
|
||||
final c = new ObservableList.from([1, 2]);
|
||||
|
||||
d.diff(c);
|
||||
|
||||
expect(d.diff(c)).toBe(null);
|
||||
});
|
||||
|
||||
it("should return the wrapped value once a change has been triggered",
|
||||
fakeAsync(() {
|
||||
final d = factory.create(changeDetectorRef);
|
||||
|
||||
final c = new ObservableList.from([1, 2]);
|
||||
|
||||
d.diff(c);
|
||||
|
||||
c.add(3);
|
||||
|
||||
// same value, because we have not detected the change yet
|
||||
expect(d.diff(c)).toBe(null);
|
||||
|
||||
// now we detect the change
|
||||
flushMicrotasks();
|
||||
expect(d.diff(c)).toBe(d);
|
||||
}));
|
||||
|
||||
it("should request a change detection check upon receiving a change",
|
||||
fakeAsync(() {
|
||||
final d = factory.create(changeDetectorRef);
|
||||
|
||||
final c = new ObservableList.from([1, 2]);
|
||||
d.diff(c);
|
||||
|
||||
c.add(3);
|
||||
flushMicrotasks();
|
||||
|
||||
expect(changeDetectorRef.spy("markForCheck")).toHaveBeenCalledOnce();
|
||||
}));
|
||||
|
||||
it("should return the wrapped value after changing a collection", () {
|
||||
final d = factory.create(changeDetectorRef);
|
||||
|
||||
final c1 = new ObservableList.from([1, 2]);
|
||||
final c2 = new ObservableList.from([3, 4]);
|
||||
|
||||
expect(d.diff(c1)).toBe(d);
|
||||
expect(d.diff(c2)).toBe(d);
|
||||
});
|
||||
|
||||
it("should not unbsubscribe from the stream of chagnes after changing a collection",
|
||||
() {
|
||||
final d = factory.create(changeDetectorRef);
|
||||
|
||||
final c1 = new ObservableList.from([1, 2]);
|
||||
expect(d.diff(c1)).toBe(d);
|
||||
|
||||
final c2 = new ObservableList.from([3, 4]);
|
||||
expect(d.diff(c2)).toBe(d);
|
||||
|
||||
// pushing into the first collection has no effect, and we do not see the change
|
||||
c1.add(3);
|
||||
expect(d.diff(c2)).toBe(null);
|
||||
});
|
||||
});
|
||||
}
|
489
modules/@angular/common/test/forms-deprecated/directives_spec.ts
Normal file
489
modules/@angular/common/test/forms-deprecated/directives_spec.ts
Normal file
@ -0,0 +1,489 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CheckboxControlValueAccessor, Control, ControlGroup, ControlValueAccessor, DefaultValueAccessor, NgControl, NgControlGroup, NgControlName, NgForm, NgFormControl, NgFormModel, NgModel, SelectControlValueAccessor, Validator, Validators} from '@angular/common/src/forms-deprecated';
|
||||
import {composeValidators, selectValueAccessor} from '@angular/common/src/forms-deprecated/directives/shared';
|
||||
import {SimpleChange} from '@angular/core/src/change_detection';
|
||||
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {SpyNgControl, SpyValueAccessor} from '../spies';
|
||||
|
||||
class DummyControlValueAccessor implements ControlValueAccessor {
|
||||
writtenValue: any /** TODO #9100 */;
|
||||
|
||||
registerOnChange(fn: any /** TODO #9100 */) {}
|
||||
registerOnTouched(fn: any /** TODO #9100 */) {}
|
||||
|
||||
writeValue(obj: any): void { this.writtenValue = obj; }
|
||||
}
|
||||
|
||||
class CustomValidatorDirective implements Validator {
|
||||
validate(c: Control): {[key: string]: any} { return {'custom': true}; }
|
||||
}
|
||||
|
||||
function asyncValidator(expected: any /** TODO #9100 */, timeout = 0) {
|
||||
return (c: any /** TODO #9100 */) => {
|
||||
return new Promise((resolve) => {
|
||||
var res = c.value != expected ? {'async': true} : null;
|
||||
if (timeout == 0) {
|
||||
resolve(res);
|
||||
} else {
|
||||
setTimeout(() => { resolve(res); }, timeout);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('Form Directives', () => {
|
||||
var defaultAccessor: DefaultValueAccessor;
|
||||
|
||||
beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null); });
|
||||
|
||||
describe('shared', () => {
|
||||
describe('selectValueAccessor', () => {
|
||||
var dir: NgControl;
|
||||
|
||||
beforeEach(() => { dir = <any>new SpyNgControl(); });
|
||||
|
||||
it('should throw when given an empty array',
|
||||
() => { expect(() => selectValueAccessor(dir, [])).toThrowError(); });
|
||||
|
||||
it('should return the default value accessor when no other provided',
|
||||
() => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); });
|
||||
|
||||
it('should return checkbox accessor when provided', () => {
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, [
|
||||
defaultAccessor, checkboxAccessor
|
||||
])).toEqual(checkboxAccessor);
|
||||
});
|
||||
|
||||
it('should return select accessor when provided', () => {
|
||||
var selectAccessor = new SelectControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, [
|
||||
defaultAccessor, selectAccessor
|
||||
])).toEqual(selectAccessor);
|
||||
});
|
||||
|
||||
it('should throw when more than one build-in accessor is provided', () => {
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
var selectAccessor = new SelectControlValueAccessor(null, null);
|
||||
expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
|
||||
});
|
||||
|
||||
it('should return custom accessor when provided', () => {
|
||||
var customAccessor = new SpyValueAccessor();
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, <any>[defaultAccessor, customAccessor, checkboxAccessor]))
|
||||
.toEqual(customAccessor);
|
||||
});
|
||||
|
||||
it('should throw when more than one custom accessor is provided', () => {
|
||||
var customAccessor: ControlValueAccessor = <any>new SpyValueAccessor();
|
||||
expect(() => selectValueAccessor(dir, [customAccessor, customAccessor])).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('composeValidators', () => {
|
||||
it('should compose functions', () => {
|
||||
var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
||||
var dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true});
|
||||
var v = composeValidators([dummy1, dummy2]);
|
||||
expect(v(new Control(''))).toEqual({'dummy1': true, 'dummy2': true});
|
||||
});
|
||||
|
||||
it('should compose validator directives', () => {
|
||||
var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
||||
var v = composeValidators([dummy1, new CustomValidatorDirective()]);
|
||||
expect(v(new Control(''))).toEqual({'dummy1': true, 'custom': true});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgFormModel', () => {
|
||||
var form: any /** TODO #9100 */;
|
||||
var formModel: ControlGroup;
|
||||
var loginControlDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new NgFormModel([], []);
|
||||
formModel = new ControlGroup({
|
||||
'login': new Control(),
|
||||
'passwords':
|
||||
new ControlGroup({'password': new Control(), 'passwordConfirm': new Control()})
|
||||
});
|
||||
form.form = formModel;
|
||||
|
||||
loginControlDir = new NgControlName(
|
||||
form, [Validators.required], [asyncValidator('expected')], [defaultAccessor]);
|
||||
loginControlDir.name = 'login';
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(form.control).toBe(formModel);
|
||||
expect(form.value).toBe(formModel.value);
|
||||
expect(form.valid).toBe(formModel.valid);
|
||||
expect(form.errors).toBe(formModel.errors);
|
||||
expect(form.pristine).toBe(formModel.pristine);
|
||||
expect(form.dirty).toBe(formModel.dirty);
|
||||
expect(form.touched).toBe(formModel.touched);
|
||||
expect(form.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
|
||||
describe('addControl', () => {
|
||||
it('should throw when no control found', () => {
|
||||
var dir = new NgControlName(form, null, null, [defaultAccessor]);
|
||||
dir.name = 'invalidName';
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp(`Cannot find control with name: 'invalidName'`));
|
||||
});
|
||||
|
||||
it('should throw when no value accessor', () => {
|
||||
var dir = new NgControlName(form, null, null, null);
|
||||
dir.name = 'login';
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp(`No value accessor for form control with name: 'login'`));
|
||||
});
|
||||
|
||||
it('should throw when no value accessor with path', () => {
|
||||
const group = new NgControlGroup(form, null, null);
|
||||
const dir = new NgControlName(group, null, null, null);
|
||||
group.name = 'passwords';
|
||||
dir.name = 'password';
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp(
|
||||
`No value accessor for form control with path: 'passwords -> password'`));
|
||||
});
|
||||
|
||||
it('should set up validators', fakeAsync(() => {
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
// sync validators are set
|
||||
expect(formModel.hasError('required', ['login'])).toBe(true);
|
||||
expect(formModel.hasError('async', ['login'])).toBe(false);
|
||||
|
||||
(<Control>formModel.find(['login'])).updateValue('invalid value');
|
||||
|
||||
// sync validator passes, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError('required', ['login'])).toBe(false);
|
||||
expect(formModel.hasError('async', ['login'])).toBe(true);
|
||||
}));
|
||||
|
||||
it('should write value to the DOM', () => {
|
||||
(<Control>formModel.find(['login'])).updateValue('initValue');
|
||||
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual('initValue');
|
||||
});
|
||||
|
||||
it('should add the directive to the list of directives included in the form', () => {
|
||||
form.addControl(loginControlDir);
|
||||
expect(form.directives).toEqual([loginControlDir]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addControlGroup', () => {
|
||||
var matchingPasswordsValidator = (g: any /** TODO #9100 */) => {
|
||||
if (g.controls['password'].value != g.controls['passwordConfirm'].value) {
|
||||
return {'differentPasswords': true};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
it('should set up validator', fakeAsync(() => {
|
||||
var group = new NgControlGroup(
|
||||
form, [matchingPasswordsValidator], [asyncValidator('expected')]);
|
||||
group.name = 'passwords';
|
||||
form.addControlGroup(group);
|
||||
|
||||
(<Control>formModel.find(['passwords', 'password'])).updateValue('somePassword');
|
||||
(<Control>formModel.find([
|
||||
'passwords', 'passwordConfirm'
|
||||
])).updateValue('someOtherPassword');
|
||||
|
||||
// sync validators are set
|
||||
expect(formModel.hasError('differentPasswords', ['passwords'])).toEqual(true);
|
||||
|
||||
(<Control>formModel.find([
|
||||
'passwords', 'passwordConfirm'
|
||||
])).updateValue('somePassword');
|
||||
|
||||
// sync validators pass, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError('async', ['passwords'])).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('removeControl', () => {
|
||||
it('should remove the directive to the list of directives included in the form', () => {
|
||||
form.addControl(loginControlDir);
|
||||
form.removeControl(loginControlDir);
|
||||
expect(form.directives).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngOnChanges', () => {
|
||||
it('should update dom values of all the directives', () => {
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
(<Control>formModel.find(['login'])).updateValue('new value');
|
||||
|
||||
form.ngOnChanges({});
|
||||
|
||||
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual('new value');
|
||||
});
|
||||
|
||||
it('should set up a sync validator', () => {
|
||||
var formValidator = (c: any /** TODO #9100 */) => ({'custom': true});
|
||||
var f = new NgFormModel([formValidator], []);
|
||||
f.form = formModel;
|
||||
f.ngOnChanges({'form': new SimpleChange(null, null)});
|
||||
|
||||
expect(formModel.errors).toEqual({'custom': true});
|
||||
});
|
||||
|
||||
it('should set up an async validator', fakeAsync(() => {
|
||||
var f = new NgFormModel([], [asyncValidator('expected')]);
|
||||
f.form = formModel;
|
||||
f.ngOnChanges({'form': new SimpleChange(null, null)});
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgForm', () => {
|
||||
var form: any /** TODO #9100 */;
|
||||
var formModel: ControlGroup;
|
||||
var loginControlDir: any /** TODO #9100 */;
|
||||
var personControlGroupDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new NgForm([], []);
|
||||
formModel = form.form;
|
||||
|
||||
personControlGroupDir = new NgControlGroup(form, [], []);
|
||||
personControlGroupDir.name = 'person';
|
||||
|
||||
loginControlDir = new NgControlName(personControlGroupDir, null, null, [defaultAccessor]);
|
||||
loginControlDir.name = 'login';
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(form.control).toBe(formModel);
|
||||
expect(form.value).toBe(formModel.value);
|
||||
expect(form.valid).toBe(formModel.valid);
|
||||
expect(form.errors).toBe(formModel.errors);
|
||||
expect(form.pristine).toBe(formModel.pristine);
|
||||
expect(form.dirty).toBe(formModel.dirty);
|
||||
expect(form.touched).toBe(formModel.touched);
|
||||
expect(form.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
|
||||
describe('addControl & addControlGroup', () => {
|
||||
it('should create a control with the given name', fakeAsync(() => {
|
||||
form.addControlGroup(personControlGroupDir);
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(formModel.find(['person', 'login'])).not.toBeNull;
|
||||
}));
|
||||
|
||||
// should update the form's value and validity
|
||||
});
|
||||
|
||||
describe('removeControl & removeControlGroup', () => {
|
||||
it('should remove control', fakeAsync(() => {
|
||||
form.addControlGroup(personControlGroupDir);
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
form.removeControlGroup(personControlGroupDir);
|
||||
form.removeControl(loginControlDir);
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(formModel.find(['person'])).toBeNull();
|
||||
expect(formModel.find(['person', 'login'])).toBeNull();
|
||||
}));
|
||||
|
||||
// should update the form's value and validity
|
||||
});
|
||||
|
||||
it('should set up sync validator', fakeAsync(() => {
|
||||
var formValidator = (c: any /** TODO #9100 */) => ({'custom': true});
|
||||
var f = new NgForm([formValidator], []);
|
||||
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({'custom': true});
|
||||
}));
|
||||
|
||||
it('should set up async validator', fakeAsync(() => {
|
||||
var f = new NgForm([], [asyncValidator('expected')]);
|
||||
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('NgControlGroup', () => {
|
||||
var formModel: any /** TODO #9100 */;
|
||||
var controlGroupDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
formModel = new ControlGroup({'login': new Control(null)});
|
||||
|
||||
var parent = new NgFormModel([], []);
|
||||
parent.form = new ControlGroup({'group': formModel});
|
||||
controlGroupDir = new NgControlGroup(parent, [], []);
|
||||
controlGroupDir.name = 'group';
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(controlGroupDir.control).toBe(formModel);
|
||||
expect(controlGroupDir.value).toBe(formModel.value);
|
||||
expect(controlGroupDir.valid).toBe(formModel.valid);
|
||||
expect(controlGroupDir.errors).toBe(formModel.errors);
|
||||
expect(controlGroupDir.pristine).toBe(formModel.pristine);
|
||||
expect(controlGroupDir.dirty).toBe(formModel.dirty);
|
||||
expect(controlGroupDir.touched).toBe(formModel.touched);
|
||||
expect(controlGroupDir.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgFormControl', () => {
|
||||
var controlDir: any /** TODO #9100 */;
|
||||
var control: any /** TODO #9100 */;
|
||||
var checkProperties = function(control: any /** TODO #9100 */) {
|
||||
expect(controlDir.control).toBe(control);
|
||||
expect(controlDir.value).toBe(control.value);
|
||||
expect(controlDir.valid).toBe(control.valid);
|
||||
expect(controlDir.errors).toBe(control.errors);
|
||||
expect(controlDir.pristine).toBe(control.pristine);
|
||||
expect(controlDir.dirty).toBe(control.dirty);
|
||||
expect(controlDir.touched).toBe(control.touched);
|
||||
expect(controlDir.untouched).toBe(control.untouched);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
controlDir = new NgFormControl([Validators.required], [], [defaultAccessor]);
|
||||
controlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
|
||||
control = new Control(null);
|
||||
controlDir.form = control;
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => { checkProperties(control); });
|
||||
|
||||
it('should reexport new control properties', () => {
|
||||
var newControl = new Control(null);
|
||||
controlDir.form = newControl;
|
||||
controlDir.ngOnChanges({'form': new SimpleChange(control, newControl)});
|
||||
|
||||
checkProperties(newControl);
|
||||
});
|
||||
|
||||
it('should set up validator', () => {
|
||||
expect(control.valid).toBe(true);
|
||||
|
||||
// this will add the required validator and recalculate the validity
|
||||
controlDir.ngOnChanges({'form': new SimpleChange(null, control)});
|
||||
|
||||
expect(control.valid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgModel', () => {
|
||||
var ngModel: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
ngModel =
|
||||
new NgModel([Validators.required], [asyncValidator('expected')], [defaultAccessor]);
|
||||
ngModel.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
var control = ngModel.control;
|
||||
expect(ngModel.control).toBe(control);
|
||||
expect(ngModel.value).toBe(control.value);
|
||||
expect(ngModel.valid).toBe(control.valid);
|
||||
expect(ngModel.errors).toBe(control.errors);
|
||||
expect(ngModel.pristine).toBe(control.pristine);
|
||||
expect(ngModel.dirty).toBe(control.dirty);
|
||||
expect(ngModel.touched).toBe(control.touched);
|
||||
expect(ngModel.untouched).toBe(control.untouched);
|
||||
});
|
||||
|
||||
it('should throw when no value accessor with unnamed control', () => {
|
||||
const unnamedDir = new NgModel(null, null, null);
|
||||
|
||||
expect(() => unnamedDir.ngOnChanges({}))
|
||||
.toThrowError(new RegExp(`No value accessor for form control with unspecified name`));
|
||||
});
|
||||
|
||||
|
||||
it('should set up validator', fakeAsync(() => {
|
||||
// this will add the required validator and recalculate the validity
|
||||
ngModel.ngOnChanges({});
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.errors).toEqual({'required': true});
|
||||
|
||||
ngModel.control.updateValue('someValue');
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('NgControlName', () => {
|
||||
var formModel: any /** TODO #9100 */;
|
||||
var controlNameDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
formModel = new Control('name');
|
||||
|
||||
var parent = new NgFormModel([], []);
|
||||
parent.form = new ControlGroup({'name': formModel});
|
||||
controlNameDir = new NgControlName(parent, [], [], [defaultAccessor]);
|
||||
controlNameDir.name = 'name';
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(controlNameDir.control).toBe(formModel);
|
||||
expect(controlNameDir.value).toBe(formModel.value);
|
||||
expect(controlNameDir.valid).toBe(formModel.valid);
|
||||
expect(controlNameDir.errors).toBe(formModel.errors);
|
||||
expect(controlNameDir.pristine).toBe(formModel.pristine);
|
||||
expect(controlNameDir.dirty).toBe(formModel.dirty);
|
||||
expect(controlNameDir.touched).toBe(formModel.touched);
|
||||
expect(controlNameDir.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Control, FormBuilder} from '@angular/common/src/forms-deprecated';
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
function syncValidator(_: any): any { return null; }
|
||||
function asyncValidator(_: any) { return Promise.resolve(null); }
|
||||
|
||||
describe('Form Builder', () => {
|
||||
var b: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => { b = new FormBuilder(); });
|
||||
|
||||
it('should create controls from a value', () => {
|
||||
var g = b.group({'login': 'some value'});
|
||||
|
||||
expect(g.controls['login'].value).toEqual('some value');
|
||||
});
|
||||
|
||||
it('should create controls from an array', () => {
|
||||
var g = b.group(
|
||||
{'login': ['some value'], 'password': ['some value', syncValidator, asyncValidator]});
|
||||
|
||||
expect(g.controls['login'].value).toEqual('some value');
|
||||
expect(g.controls['password'].value).toEqual('some value');
|
||||
expect(g.controls['password'].validator).toEqual(syncValidator);
|
||||
expect(g.controls['password'].asyncValidator).toEqual(asyncValidator);
|
||||
});
|
||||
|
||||
it('should use controls', () => {
|
||||
var g = b.group({'login': b.control('some value', syncValidator, asyncValidator)});
|
||||
|
||||
expect(g.controls['login'].value).toEqual('some value');
|
||||
expect(g.controls['login'].validator).toBe(syncValidator);
|
||||
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
|
||||
it('should create groups with optional controls', () => {
|
||||
var g = b.group({'login': 'some value'}, {'optionals': {'login': false}});
|
||||
|
||||
expect(g.contains('login')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should create groups with a custom validator', () => {
|
||||
var g = b.group(
|
||||
{'login': 'some value'}, {'validator': syncValidator, 'asyncValidator': asyncValidator});
|
||||
|
||||
expect(g.validator).toBe(syncValidator);
|
||||
expect(g.asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
|
||||
it('should create control arrays', () => {
|
||||
var c = b.control('three');
|
||||
var a = b.array(
|
||||
['one', ['two', syncValidator], c, b.array(['four'])], syncValidator, asyncValidator);
|
||||
|
||||
expect(a.value).toEqual(['one', 'two', 'three', ['four']]);
|
||||
expect(a.validator).toBe(syncValidator);
|
||||
expect(a.asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
});
|
||||
}
|
1550
modules/@angular/common/test/forms-deprecated/integration_spec.ts
Normal file
1550
modules/@angular/common/test/forms-deprecated/integration_spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
856
modules/@angular/common/test/forms-deprecated/model_spec.ts
Normal file
856
modules/@angular/common/test/forms-deprecated/model_spec.ts
Normal file
@ -0,0 +1,856 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Control, ControlArray, ControlGroup, Validators} from '@angular/common/src/forms-deprecated';
|
||||
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {EventEmitter} from '../../src/facade/async';
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
function asyncValidator(expected: any /** TODO #9100 */, timeouts = {}) {
|
||||
return (c: any /** TODO #9100 */) => {
|
||||
return new Promise((resolve) => {
|
||||
var t = isPresent((timeouts as any /** TODO #9100 */)[c.value]) ?
|
||||
(timeouts as any /** TODO #9100 */)[c.value] :
|
||||
0;
|
||||
var res = c.value != expected ? {'async': true} : null;
|
||||
|
||||
if (t == 0) {
|
||||
resolve(res);
|
||||
} else {
|
||||
setTimeout(() => { resolve(res); }, t);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function asyncValidatorReturningObservable(c: any /** TODO #9100 */) {
|
||||
var e = new EventEmitter();
|
||||
Promise.resolve(null).then(() => { e.emit({'async': true}); });
|
||||
return e;
|
||||
}
|
||||
|
||||
describe('Form Model', () => {
|
||||
describe('Control', () => {
|
||||
it('should default the value to null', () => {
|
||||
var c = new Control();
|
||||
expect(c.value).toBe(null);
|
||||
});
|
||||
|
||||
describe('validator', () => {
|
||||
it('should run validator with the initial value', () => {
|
||||
var c = new Control('value', Validators.required);
|
||||
expect(c.valid).toEqual(true);
|
||||
});
|
||||
|
||||
it('should rerun the validator when the value changes', () => {
|
||||
var c = new Control('value', Validators.required);
|
||||
c.updateValue(null);
|
||||
expect(c.valid).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return errors', () => {
|
||||
var c = new Control(null, Validators.required);
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncValidator', () => {
|
||||
it('should run validator with the initial value', fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidator('expected'));
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'async': true});
|
||||
}));
|
||||
|
||||
it('should support validators returning observables', fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidatorReturningObservable);
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'async': true});
|
||||
}));
|
||||
|
||||
it('should rerun the validator when the value changes', fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidator('expected'));
|
||||
|
||||
c.updateValue('expected');
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should run the async validator only when the sync validator passes', fakeAsync(() => {
|
||||
var c = new Control('', Validators.required, asyncValidator('expected'));
|
||||
tick();
|
||||
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
|
||||
c.updateValue('some value');
|
||||
tick();
|
||||
|
||||
expect(c.errors).toEqual({'async': true});
|
||||
}));
|
||||
|
||||
it('should mark the control as pending while running the async validation',
|
||||
fakeAsync(() => {
|
||||
var c = new Control('', null, asyncValidator('expected'));
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(c.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should only use the latest async validation run', fakeAsync(() => {
|
||||
var c =
|
||||
new Control('', null, asyncValidator('expected', {'long': 200, 'expected': 100}));
|
||||
|
||||
c.updateValue('long');
|
||||
c.updateValue('expected');
|
||||
|
||||
tick(300);
|
||||
|
||||
expect(c.valid).toEqual(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('dirty', () => {
|
||||
it('should be false after creating a control', () => {
|
||||
var c = new Control('value');
|
||||
expect(c.dirty).toEqual(false);
|
||||
});
|
||||
|
||||
it('should be true after changing the value of the control', () => {
|
||||
var c = new Control('value');
|
||||
c.markAsDirty();
|
||||
expect(c.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateValue', () => {
|
||||
var g: any /** TODO #9100 */, c: any /** TODO #9100 */;
|
||||
beforeEach(() => {
|
||||
c = new Control('oldValue');
|
||||
g = new ControlGroup({'one': c});
|
||||
});
|
||||
|
||||
it('should update the value of the control', () => {
|
||||
c.updateValue('newValue');
|
||||
expect(c.value).toEqual('newValue');
|
||||
});
|
||||
|
||||
it('should invoke ngOnChanges if it is present', () => {
|
||||
var ngOnChanges: any /** TODO #9100 */;
|
||||
c.registerOnChange((v: any /** TODO #9100 */) => ngOnChanges = ['invoked', v]);
|
||||
|
||||
c.updateValue('newValue');
|
||||
|
||||
expect(ngOnChanges).toEqual(['invoked', 'newValue']);
|
||||
});
|
||||
|
||||
it('should not invoke on change when explicitly specified', () => {
|
||||
var onChange: any /** TODO #9100 */ = null;
|
||||
c.registerOnChange((v: any /** TODO #9100 */) => onChange = ['invoked', v]);
|
||||
|
||||
c.updateValue('newValue', {emitModelToViewChange: false});
|
||||
|
||||
expect(onChange).toBeNull();
|
||||
});
|
||||
|
||||
it('should update the parent', () => {
|
||||
c.updateValue('newValue');
|
||||
expect(g.value).toEqual({'one': 'newValue'});
|
||||
});
|
||||
|
||||
it('should not update the parent when explicitly specified', () => {
|
||||
c.updateValue('newValue', {onlySelf: true});
|
||||
expect(g.value).toEqual({'one': 'oldValue'});
|
||||
});
|
||||
|
||||
it('should fire an event', fakeAsync(() => {
|
||||
|
||||
c.valueChanges.subscribe(
|
||||
{next: (value: any) => { expect(value).toEqual('newValue'); }});
|
||||
|
||||
c.updateValue('newValue');
|
||||
tick();
|
||||
}));
|
||||
|
||||
it('should not fire an event when explicitly specified', fakeAsync(() => {
|
||||
c.valueChanges.subscribe({next: (value: any) => { throw 'Should not happen'; }});
|
||||
|
||||
c.updateValue('newValue', {emitEvent: false});
|
||||
|
||||
tick();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('valueChanges & statusChanges', () => {
|
||||
var c: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => { c = new Control('old', Validators.required); });
|
||||
|
||||
it('should fire an event after the value has been updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
c.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(c.value).toEqual('new');
|
||||
expect(value).toEqual('new');
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
c.updateValue('new');
|
||||
}));
|
||||
|
||||
it('should fire an event after the status has been updated to invalid', fakeAsync(() => {
|
||||
c.statusChanges.subscribe({
|
||||
next: (status: any) => {
|
||||
expect(c.status).toEqual('INVALID');
|
||||
expect(status).toEqual('INVALID');
|
||||
}
|
||||
});
|
||||
|
||||
c.updateValue('');
|
||||
tick();
|
||||
}));
|
||||
|
||||
it('should fire an event after the status has been updated to pending', fakeAsync(() => {
|
||||
var c = new Control('old', Validators.required, asyncValidator('expected'));
|
||||
|
||||
var log: any[] /** TODO #9100 */ = [];
|
||||
c.valueChanges.subscribe({next: (value: any) => log.push(`value: '${value}'`)});
|
||||
|
||||
c.statusChanges.subscribe({next: (status: any) => log.push(`status: '${status}'`)});
|
||||
|
||||
c.updateValue('');
|
||||
tick();
|
||||
|
||||
c.updateValue('nonEmpty');
|
||||
tick();
|
||||
|
||||
c.updateValue('expected');
|
||||
tick();
|
||||
|
||||
expect(log).toEqual([
|
||||
'' +
|
||||
'value: \'\'',
|
||||
'status: \'INVALID\'',
|
||||
'value: \'nonEmpty\'',
|
||||
'status: \'PENDING\'',
|
||||
'status: \'INVALID\'',
|
||||
'value: \'expected\'',
|
||||
'status: \'PENDING\'',
|
||||
'status: \'VALID\'',
|
||||
]);
|
||||
}));
|
||||
|
||||
// TODO: remove the if statement after making observable delivery sync
|
||||
it('should update set errors and status before emitting an event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
c.valueChanges.subscribe((value: any /** TODO #9100 */) => {
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
async.done();
|
||||
});
|
||||
c.updateValue('');
|
||||
}));
|
||||
|
||||
it('should return a cold observable',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
c.updateValue('will be ignored');
|
||||
c.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual('new');
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
c.updateValue('new');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('setErrors', () => {
|
||||
it('should set errors on a control', () => {
|
||||
var c = new Control('someValue');
|
||||
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'someError': true});
|
||||
});
|
||||
|
||||
it('should reset the errors and validity when the value changes', () => {
|
||||
var c = new Control('someValue', Validators.required);
|
||||
|
||||
c.setErrors({'someError': true});
|
||||
c.updateValue('');
|
||||
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
});
|
||||
|
||||
it('should update the parent group\'s validity', () => {
|
||||
var c = new Control('someValue');
|
||||
var g = new ControlGroup({'one': c});
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not reset parent\'s errors', () => {
|
||||
var c = new Control('someValue');
|
||||
var g = new ControlGroup({'one': c});
|
||||
|
||||
g.setErrors({'someGroupError': true});
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
expect(g.errors).toEqual({'someGroupError': true});
|
||||
});
|
||||
|
||||
it('should reset errors when updating a value', () => {
|
||||
var c = new Control('oldValue');
|
||||
var g = new ControlGroup({'one': c});
|
||||
|
||||
g.setErrors({'someGroupError': true});
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
c.updateValue('newValue');
|
||||
|
||||
expect(c.errors).toEqual(null);
|
||||
expect(g.errors).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ControlGroup', () => {
|
||||
describe('value', () => {
|
||||
it('should be the reduced value of the child controls', () => {
|
||||
var g = new ControlGroup({'one': new Control('111'), 'two': new Control('222')});
|
||||
expect(g.value).toEqual({'one': '111', 'two': '222'});
|
||||
});
|
||||
|
||||
it('should be empty when there are no child controls', () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.value).toEqual({});
|
||||
});
|
||||
|
||||
it('should support nested groups', () => {
|
||||
var g = new ControlGroup(
|
||||
{'one': new Control('111'), 'nested': new ControlGroup({'two': new Control('222')})});
|
||||
expect(g.value).toEqual({'one': '111', 'nested': {'two': '222'}});
|
||||
|
||||
(<Control>(g.controls['nested'].find('two'))).updateValue('333');
|
||||
|
||||
expect(g.value).toEqual({'one': '111', 'nested': {'two': '333'}});
|
||||
});
|
||||
});
|
||||
|
||||
describe('adding and removing controls', () => {
|
||||
it('should update value and validity when control is added', () => {
|
||||
var g = new ControlGroup({'one': new Control('1')});
|
||||
expect(g.value).toEqual({'one': '1'});
|
||||
expect(g.valid).toBe(true);
|
||||
|
||||
g.addControl('two', new Control('2', Validators.minLength(10)));
|
||||
|
||||
expect(g.value).toEqual({'one': '1', 'two': '2'});
|
||||
expect(g.valid).toBe(false);
|
||||
});
|
||||
|
||||
it('should update value and validity when control is removed', () => {
|
||||
var g = new ControlGroup(
|
||||
{'one': new Control('1'), 'two': new Control('2', Validators.minLength(10))});
|
||||
expect(g.value).toEqual({'one': '1', 'two': '2'});
|
||||
expect(g.valid).toBe(false);
|
||||
|
||||
g.removeControl('two');
|
||||
|
||||
expect(g.value).toEqual({'one': '1'});
|
||||
expect(g.valid).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should run the validator when the value changes', () => {
|
||||
var simpleValidator = (c: any /** TODO #9100 */) =>
|
||||
c.controls['one'].value != 'correct' ? {'broken': true} : null;
|
||||
|
||||
var c = new Control(null);
|
||||
var g = new ControlGroup({'one': c}, null, simpleValidator);
|
||||
|
||||
c.updateValue('correct');
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
expect(g.errors).toEqual(null);
|
||||
|
||||
c.updateValue('incorrect');
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
expect(g.errors).toEqual({'broken': true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dirty', () => {
|
||||
var c: any /** TODO #9100 */, g: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new Control('value');
|
||||
g = new ControlGroup({'one': c});
|
||||
});
|
||||
|
||||
it('should be false after creating a control', () => { expect(g.dirty).toEqual(false); });
|
||||
|
||||
it('should be false after changing the value of the control', () => {
|
||||
c.markAsDirty();
|
||||
|
||||
expect(g.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('optional components', () => {
|
||||
describe('contains', () => {
|
||||
var group: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
group = new ControlGroup(
|
||||
{
|
||||
'required': new Control('requiredValue'),
|
||||
'optional': new Control('optionalValue')
|
||||
},
|
||||
{'optional': false});
|
||||
});
|
||||
|
||||
// rename contains into has
|
||||
it('should return false when the component is not included',
|
||||
() => { expect(group.contains('optional')).toEqual(false); });
|
||||
|
||||
it('should return false when there is no component with the given name',
|
||||
() => { expect(group.contains('something else')).toEqual(false); });
|
||||
|
||||
it('should return true when the component is included', () => {
|
||||
expect(group.contains('required')).toEqual(true);
|
||||
|
||||
group.include('optional');
|
||||
|
||||
expect(group.contains('optional')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include an inactive component into the group value', () => {
|
||||
var group = new ControlGroup(
|
||||
{'required': new Control('requiredValue'), 'optional': new Control('optionalValue')},
|
||||
{'optional': false});
|
||||
|
||||
expect(group.value).toEqual({'required': 'requiredValue'});
|
||||
|
||||
group.include('optional');
|
||||
|
||||
expect(group.value).toEqual({'required': 'requiredValue', 'optional': 'optionalValue'});
|
||||
});
|
||||
|
||||
it('should not run Validators on an inactive component', () => {
|
||||
var group = new ControlGroup(
|
||||
{
|
||||
'required': new Control('requiredValue', Validators.required),
|
||||
'optional': new Control('', Validators.required)
|
||||
},
|
||||
{'optional': false});
|
||||
|
||||
expect(group.valid).toEqual(true);
|
||||
|
||||
group.include('optional');
|
||||
|
||||
expect(group.valid).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('valueChanges', () => {
|
||||
var g: any /** TODO #9100 */, c1: any /** TODO #9100 */, c2: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
c1 = new Control('old1');
|
||||
c2 = new Control('old2');
|
||||
g = new ControlGroup({'one': c1, 'two': c2}, {'two': true});
|
||||
});
|
||||
|
||||
it('should fire an event after the value has been updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(g.value).toEqual({'one': 'new1', 'two': 'old2'});
|
||||
expect(value).toEqual({'one': 'new1', 'two': 'old2'});
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event after the control\'s observable fired an event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var controlCallbackIsCalled = false;
|
||||
|
||||
|
||||
c1.valueChanges.subscribe({next: (value: any) => { controlCallbackIsCalled = true; }});
|
||||
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(controlCallbackIsCalled).toBe(true);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is excluded',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual({'one': 'old1'});
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
g.exclude('two');
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is included',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
g.exclude('two');
|
||||
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual({'one': 'old1', 'two': 'old2'});
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
g.include('two');
|
||||
}));
|
||||
|
||||
it('should fire an event every time a control is updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var loggedValues: any[] /** TODO #9100 */ = [];
|
||||
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
loggedValues.push(value);
|
||||
|
||||
if (loggedValues.length == 2) {
|
||||
expect(loggedValues).toEqual([
|
||||
{'one': 'new1', 'two': 'old2'}, {'one': 'new1', 'two': 'new2'}
|
||||
]);
|
||||
async.done();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
c1.updateValue('new1');
|
||||
c2.updateValue('new2');
|
||||
}));
|
||||
|
||||
xit('should not fire an event when an excluded control is updated',
|
||||
inject(
|
||||
[AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
// hard to test without hacking zones
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getError', () => {
|
||||
it('should return the error when it is present', () => {
|
||||
var c = new Control('', Validators.required);
|
||||
var g = new ControlGroup({'one': c});
|
||||
expect(c.getError('required')).toEqual(true);
|
||||
expect(g.getError('required', ['one'])).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return null otherwise', () => {
|
||||
var c = new Control('not empty', Validators.required);
|
||||
var g = new ControlGroup({'one': c});
|
||||
expect(c.getError('invalid')).toEqual(null);
|
||||
expect(g.getError('required', ['one'])).toEqual(null);
|
||||
expect(g.getError('required', ['invalid'])).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncValidator', () => {
|
||||
it('should run the async validator', fakeAsync(() => {
|
||||
var c = new Control('value');
|
||||
var g = new ControlGroup({'one': c}, null, null, asyncValidator('expected'));
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({'async': true});
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should set the parent group\'s status to pending', fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidator('expected'));
|
||||
var g = new ControlGroup({'one': c});
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should run the parent group\'s async validator when children are pending',
|
||||
fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidator('expected'));
|
||||
var g = new ControlGroup({'one': c}, null, null, asyncValidator('expected'));
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({'async': true});
|
||||
expect(g.find(['one']).errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('ControlArray', () => {
|
||||
describe('adding/removing', () => {
|
||||
var a: ControlArray;
|
||||
var c1: any /** TODO #9100 */, c2: any /** TODO #9100 */, c3: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
a = new ControlArray([]);
|
||||
c1 = new Control(1);
|
||||
c2 = new Control(2);
|
||||
c3 = new Control(3);
|
||||
});
|
||||
|
||||
it('should support pushing', () => {
|
||||
a.push(c1);
|
||||
expect(a.length).toEqual(1);
|
||||
expect(a.controls).toEqual([c1]);
|
||||
});
|
||||
|
||||
it('should support removing', () => {
|
||||
a.push(c1);
|
||||
a.push(c2);
|
||||
a.push(c3);
|
||||
|
||||
a.removeAt(1);
|
||||
|
||||
expect(a.controls).toEqual([c1, c3]);
|
||||
});
|
||||
|
||||
it('should support inserting', () => {
|
||||
a.push(c1);
|
||||
a.push(c3);
|
||||
|
||||
a.insert(1, c2);
|
||||
|
||||
expect(a.controls).toEqual([c1, c2, c3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('value', () => {
|
||||
it('should be the reduced value of the child controls', () => {
|
||||
var a = new ControlArray([new Control(1), new Control(2)]);
|
||||
expect(a.value).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('should be an empty array when there are no child controls', () => {
|
||||
var a = new ControlArray([]);
|
||||
expect(a.value).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should run the validator when the value changes', () => {
|
||||
var simpleValidator = (c: any /** TODO #9100 */) =>
|
||||
c.controls[0].value != 'correct' ? {'broken': true} : null;
|
||||
|
||||
var c = new Control(null);
|
||||
var g = new ControlArray([c], simpleValidator);
|
||||
|
||||
c.updateValue('correct');
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
expect(g.errors).toEqual(null);
|
||||
|
||||
c.updateValue('incorrect');
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
expect(g.errors).toEqual({'broken': true});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('dirty', () => {
|
||||
var c: Control;
|
||||
var a: ControlArray;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new Control('value');
|
||||
a = new ControlArray([c]);
|
||||
});
|
||||
|
||||
it('should be false after creating a control', () => { expect(a.dirty).toEqual(false); });
|
||||
|
||||
it('should be false after changing the value of the control', () => {
|
||||
c.markAsDirty();
|
||||
|
||||
expect(a.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pending', () => {
|
||||
var c: Control;
|
||||
var a: ControlArray;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new Control('value');
|
||||
a = new ControlArray([c]);
|
||||
});
|
||||
|
||||
it('should be false after creating a control', () => {
|
||||
expect(c.pending).toEqual(false);
|
||||
expect(a.pending).toEqual(false);
|
||||
});
|
||||
|
||||
it('should be true after changing the value of the control', () => {
|
||||
c.markAsPending();
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
expect(a.pending).toEqual(true);
|
||||
});
|
||||
|
||||
it('should not update the parent when onlySelf = true', () => {
|
||||
c.markAsPending({onlySelf: true});
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
expect(a.pending).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('valueChanges', () => {
|
||||
var a: ControlArray;
|
||||
var c1: any /** TODO #9100 */, c2: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
c1 = new Control('old1');
|
||||
c2 = new Control('old2');
|
||||
a = new ControlArray([c1, c2]);
|
||||
});
|
||||
|
||||
it('should fire an event after the value has been updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
a.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(a.value).toEqual(['new1', 'old2']);
|
||||
expect(value).toEqual(['new1', 'old2']);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event after the control\'s observable fired an event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var controlCallbackIsCalled = false;
|
||||
|
||||
|
||||
c1.valueChanges.subscribe({next: (value: any) => { controlCallbackIsCalled = true; }});
|
||||
|
||||
a.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(controlCallbackIsCalled).toBe(true);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is removed',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
a.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual(['old1']);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
a.removeAt(1);
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is added',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
a.removeAt(1);
|
||||
|
||||
a.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual(['old1', 'old2']);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
a.push(c2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('find', () => {
|
||||
it('should return null when path is null', () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.find(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null when path is empty', () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.find([])).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null when path is invalid', () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.find(['one', 'two'])).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return a child of a control group', () => {
|
||||
var g = new ControlGroup(
|
||||
{'one': new Control('111'), 'nested': new ControlGroup({'two': new Control('222')})});
|
||||
|
||||
expect(g.find(['nested', 'two']).value).toEqual('222');
|
||||
expect(g.find(['one']).value).toEqual('111');
|
||||
expect(g.find('nested/two').value).toEqual('222');
|
||||
expect(g.find('one').value).toEqual('111');
|
||||
});
|
||||
|
||||
it('should return an element of an array', () => {
|
||||
var g = new ControlGroup({'array': new ControlArray([new Control('111')])});
|
||||
|
||||
expect(g.find(['array', 0]).value).toEqual('111');
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncValidator', () => {
|
||||
it('should run the async validator', fakeAsync(() => {
|
||||
var c = new Control('value');
|
||||
var g = new ControlArray([c], null, asyncValidator('expected'));
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({'async': true});
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
196
modules/@angular/common/test/forms-deprecated/validators_spec.ts
Normal file
196
modules/@angular/common/test/forms-deprecated/validators_spec.ts
Normal file
@ -0,0 +1,196 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbstractControl, Control, ControlArray, ControlGroup, Validators} from '@angular/common/src/forms-deprecated';
|
||||
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
import {EventEmitter} from '../../src/facade/async';
|
||||
import {normalizeAsyncValidator} from '../../src/forms-deprecated/directives/normalize_validator';
|
||||
|
||||
export function main() {
|
||||
function validator(key: string, error: any) {
|
||||
return function(c: AbstractControl) {
|
||||
var r = {};
|
||||
(r as any)[key] = error;
|
||||
return r;
|
||||
};
|
||||
}
|
||||
|
||||
class AsyncValidatorDirective {
|
||||
constructor(private expected: string, private error: any) {}
|
||||
|
||||
validate(c: any): {[key: string]: any;} {
|
||||
return Observable.create((obs: any) => {
|
||||
const error = this.expected !== c.value ? this.error : null;
|
||||
obs.next(error);
|
||||
obs.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
describe('Validators', () => {
|
||||
describe('required', () => {
|
||||
it('should error on an empty string',
|
||||
() => { expect(Validators.required(new Control(''))).toEqual({'required': true}); });
|
||||
|
||||
it('should error on null',
|
||||
() => { expect(Validators.required(new Control(null))).toEqual({'required': true}); });
|
||||
|
||||
it('should not error on a non-empty string',
|
||||
() => { expect(Validators.required(new Control('not empty'))).toEqual(null); });
|
||||
|
||||
it('should accept zero as valid',
|
||||
() => { expect(Validators.required(new Control(0))).toEqual(null); });
|
||||
});
|
||||
|
||||
describe('minLength', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.minLength(2)(new Control(''))).toEqual(null); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.minLength(2)(new Control(null))).toEqual(null); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.minLength(2)(new Control('aa'))).toEqual(null); });
|
||||
|
||||
it('should error on short strings', () => {
|
||||
expect(Validators.minLength(2)(new Control('a'))).toEqual({
|
||||
'minlength': {'requiredLength': 2, 'actualLength': 1}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('maxLength', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.maxLength(2)(new Control(''))).toEqual(null); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.maxLength(2)(new Control(null))).toEqual(null); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.maxLength(2)(new Control('aa'))).toEqual(null); });
|
||||
|
||||
it('should error on long strings', () => {
|
||||
expect(Validators.maxLength(2)(new Control('aaa'))).toEqual({
|
||||
'maxlength': {'requiredLength': 2, 'actualLength': 3}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pattern', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new Control(''))).toEqual(null); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new Control(null))).toEqual(null); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new Control('aaAA'))).toEqual(null); });
|
||||
|
||||
it('should error on failure to match string', () => {
|
||||
expect(Validators.pattern('[a-zA-Z ]*')(new Control('aaa0'))).toEqual({
|
||||
'pattern': {'requiredPattern': '^[a-zA-Z ]*$', 'actualValue': 'aaa0'}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
|
||||
const c = Validators.composeAsync(
|
||||
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]);
|
||||
|
||||
let value: any = null;
|
||||
c(new Control()).then((v: any) => value = v);
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({'one': true});
|
||||
}));
|
||||
|
||||
describe('compose', () => {
|
||||
it('should return null when given null',
|
||||
() => { expect(Validators.compose(null)).toBe(null); });
|
||||
|
||||
it('should collect errors from all the validators', () => {
|
||||
var c = Validators.compose([validator('a', true), validator('b', true)]);
|
||||
expect(c(new Control(''))).toEqual({'a': true, 'b': true});
|
||||
});
|
||||
|
||||
it('should run validators left to right', () => {
|
||||
var c = Validators.compose([validator('a', 1), validator('a', 2)]);
|
||||
expect(c(new Control(''))).toEqual({'a': 2});
|
||||
});
|
||||
|
||||
it('should return null when no errors', () => {
|
||||
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
|
||||
expect(c(new Control(''))).toEqual(null);
|
||||
});
|
||||
|
||||
it('should ignore nulls', () => {
|
||||
var c = Validators.compose([null, Validators.required]);
|
||||
expect(c(new Control(''))).toEqual({'required': true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('composeAsync', () => {
|
||||
function asyncValidator(expected: any /** TODO #9100 */, response: any /** TODO #9100 */) {
|
||||
return (c: any /** TODO #9100 */) => {
|
||||
var emitter = new EventEmitter();
|
||||
var res = c.value != expected ? response : null;
|
||||
Promise.resolve(null).then(() => {
|
||||
emitter.emit(res);
|
||||
// this is required because of a bug in ObservableWrapper
|
||||
// where callComplete can fire before callEmit
|
||||
// remove this one the bug is fixed
|
||||
setTimeout(() => { emitter.complete(); }, 0);
|
||||
});
|
||||
|
||||
return emitter;
|
||||
};
|
||||
}
|
||||
|
||||
it('should return null when given null',
|
||||
() => { expect(Validators.composeAsync(null)).toEqual(null); });
|
||||
|
||||
it('should collect errors from all the validators', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([
|
||||
asyncValidator('expected', {'one': true}), asyncValidator('expected', {'two': true})
|
||||
]);
|
||||
|
||||
var value: any /** TODO #9100 */ = null;
|
||||
(<Promise<any>>c(new Control('invalid'))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({'one': true, 'two': true});
|
||||
}));
|
||||
|
||||
it('should return null when no errors', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([asyncValidator('expected', {'one': true})]);
|
||||
|
||||
var value: any /** TODO #9100 */ = null;
|
||||
(<Promise<any>>c(new Control('expected'))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual(null);
|
||||
}));
|
||||
|
||||
it('should ignore nulls', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([asyncValidator('expected', {'one': true}), null]);
|
||||
|
||||
var value: any /** TODO #9100 */ = null;
|
||||
(<Promise<any>>c(new Control('invalid'))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({'one': true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
@ -1,495 +0,0 @@
|
||||
import {
|
||||
ddescribe,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
inject
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {
|
||||
fakeAsync,
|
||||
flushMicrotasks,
|
||||
Log,
|
||||
tick,
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import {SpyNgControl, SpyValueAccessor} from '../spies';
|
||||
|
||||
import {
|
||||
ControlGroup,
|
||||
Control,
|
||||
NgControlName,
|
||||
NgControlGroup,
|
||||
NgFormModel,
|
||||
ControlValueAccessor,
|
||||
Validators,
|
||||
NgForm,
|
||||
NgModel,
|
||||
NgFormControl,
|
||||
NgControl,
|
||||
DefaultValueAccessor,
|
||||
CheckboxControlValueAccessor,
|
||||
SelectControlValueAccessor,
|
||||
Validator
|
||||
} from '@angular/common';
|
||||
|
||||
|
||||
import {selectValueAccessor, composeValidators} from '@angular/common/src/forms/directives/shared';
|
||||
import {TimerWrapper} from '../../src/facade/async';
|
||||
import {PromiseWrapper} from '../../src/facade/promise';
|
||||
import {SimpleChange} from '@angular/core/src/change_detection';
|
||||
|
||||
class DummyControlValueAccessor implements ControlValueAccessor {
|
||||
writtenValue;
|
||||
|
||||
registerOnChange(fn) {}
|
||||
registerOnTouched(fn) {}
|
||||
|
||||
writeValue(obj: any): void { this.writtenValue = obj; }
|
||||
}
|
||||
|
||||
class CustomValidatorDirective implements Validator {
|
||||
validate(c: Control): {[key: string]: any} { return {"custom": true}; }
|
||||
}
|
||||
|
||||
function asyncValidator(expected, timeout = 0) {
|
||||
return (c) => {
|
||||
var completer = PromiseWrapper.completer();
|
||||
var res = c.value != expected ? {"async": true} : null;
|
||||
if (timeout == 0) {
|
||||
completer.resolve(res);
|
||||
} else {
|
||||
TimerWrapper.setTimeout(() => { completer.resolve(res); }, timeout);
|
||||
}
|
||||
return completer.promise;
|
||||
};
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe("Form Directives", () => {
|
||||
var defaultAccessor: DefaultValueAccessor;
|
||||
|
||||
beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null); });
|
||||
|
||||
describe("shared", () => {
|
||||
describe("selectValueAccessor", () => {
|
||||
var dir: NgControl;
|
||||
|
||||
beforeEach(() => { dir = <any>new SpyNgControl(); });
|
||||
|
||||
it("should throw when given an empty array",
|
||||
() => { expect(() => selectValueAccessor(dir, [])).toThrowError(); });
|
||||
|
||||
it("should return the default value accessor when no other provided",
|
||||
() => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); });
|
||||
|
||||
it("should return checkbox accessor when provided", () => {
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, [defaultAccessor, checkboxAccessor]))
|
||||
.toEqual(checkboxAccessor);
|
||||
});
|
||||
|
||||
it("should return select accessor when provided", () => {
|
||||
var selectAccessor = new SelectControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, [defaultAccessor, selectAccessor]))
|
||||
.toEqual(selectAccessor);
|
||||
});
|
||||
|
||||
it("should throw when more than one build-in accessor is provided", () => {
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
var selectAccessor = new SelectControlValueAccessor(null, null);
|
||||
expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
|
||||
});
|
||||
|
||||
it("should return custom accessor when provided", () => {
|
||||
var customAccessor = new SpyValueAccessor();
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, <any>[defaultAccessor, customAccessor, checkboxAccessor]))
|
||||
.toEqual(customAccessor);
|
||||
});
|
||||
|
||||
it("should throw when more than one custom accessor is provided", () => {
|
||||
var customAccessor: ControlValueAccessor = <any>new SpyValueAccessor();
|
||||
expect(() => selectValueAccessor(dir, [customAccessor, customAccessor])).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe("composeValidators", () => {
|
||||
it("should compose functions", () => {
|
||||
var dummy1 = (_) => ({"dummy1": true});
|
||||
var dummy2 = (_) => ({"dummy2": true});
|
||||
var v = composeValidators([dummy1, dummy2]);
|
||||
expect(v(new Control(""))).toEqual({"dummy1": true, "dummy2": true});
|
||||
});
|
||||
|
||||
it("should compose validator directives", () => {
|
||||
var dummy1 = (_) => ({"dummy1": true});
|
||||
var v = composeValidators([dummy1, new CustomValidatorDirective()]);
|
||||
expect(v(new Control(""))).toEqual({"dummy1": true, "custom": true});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("NgFormModel", () => {
|
||||
var form;
|
||||
var formModel: ControlGroup;
|
||||
var loginControlDir;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new NgFormModel([], []);
|
||||
formModel = new ControlGroup({
|
||||
"login": new Control(),
|
||||
"passwords":
|
||||
new ControlGroup({"password": new Control(), "passwordConfirm": new Control()})
|
||||
});
|
||||
form.form = formModel;
|
||||
|
||||
loginControlDir = new NgControlName(form, [Validators.required],
|
||||
[asyncValidator("expected")], [defaultAccessor]);
|
||||
loginControlDir.name = "login";
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it("should reexport control properties", () => {
|
||||
expect(form.control).toBe(formModel);
|
||||
expect(form.value).toBe(formModel.value);
|
||||
expect(form.valid).toBe(formModel.valid);
|
||||
expect(form.errors).toBe(formModel.errors);
|
||||
expect(form.pristine).toBe(formModel.pristine);
|
||||
expect(form.dirty).toBe(formModel.dirty);
|
||||
expect(form.touched).toBe(formModel.touched);
|
||||
expect(form.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
|
||||
describe("addControl", () => {
|
||||
it("should throw when no control found", () => {
|
||||
var dir = new NgControlName(form, null, null, [defaultAccessor]);
|
||||
dir.name = "invalidName";
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp("Cannot find control 'invalidName'"));
|
||||
});
|
||||
|
||||
it("should throw when no value accessor", () => {
|
||||
var dir = new NgControlName(form, null, null, null);
|
||||
dir.name = "login";
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp("No value accessor for 'login'"));
|
||||
});
|
||||
|
||||
it("should set up validators", fakeAsync(() => {
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
// sync validators are set
|
||||
expect(formModel.hasError("required", ["login"])).toBe(true);
|
||||
expect(formModel.hasError("async", ["login"])).toBe(false);
|
||||
|
||||
(<Control>formModel.find(["login"])).updateValue("invalid value");
|
||||
|
||||
// sync validator passes, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError("required", ["login"])).toBe(false);
|
||||
expect(formModel.hasError("async", ["login"])).toBe(true);
|
||||
}));
|
||||
|
||||
it("should write value to the DOM", () => {
|
||||
(<Control>formModel.find(["login"])).updateValue("initValue");
|
||||
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual("initValue");
|
||||
});
|
||||
|
||||
it("should add the directive to the list of directives included in the form", () => {
|
||||
form.addControl(loginControlDir);
|
||||
expect(form.directives).toEqual([loginControlDir]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addControlGroup", () => {
|
||||
var matchingPasswordsValidator = (g) => {
|
||||
if (g.controls["password"].value != g.controls["passwordConfirm"].value) {
|
||||
return {"differentPasswords": true};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
it("should set up validator", fakeAsync(() => {
|
||||
var group = new NgControlGroup(form, [matchingPasswordsValidator],
|
||||
[asyncValidator('expected')]);
|
||||
group.name = "passwords";
|
||||
form.addControlGroup(group);
|
||||
|
||||
(<Control>formModel.find(["passwords", "password"])).updateValue("somePassword");
|
||||
(<Control>formModel.find(["passwords", "passwordConfirm"]))
|
||||
.updateValue("someOtherPassword");
|
||||
|
||||
// sync validators are set
|
||||
expect(formModel.hasError("differentPasswords", ["passwords"])).toEqual(true);
|
||||
|
||||
(<Control>formModel.find(["passwords", "passwordConfirm"]))
|
||||
.updateValue("somePassword");
|
||||
|
||||
// sync validators pass, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError("async", ["passwords"])).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe("removeControl", () => {
|
||||
it("should remove the directive to the list of directives included in the form", () => {
|
||||
form.addControl(loginControlDir);
|
||||
form.removeControl(loginControlDir);
|
||||
expect(form.directives).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ngOnChanges", () => {
|
||||
it("should update dom values of all the directives", () => {
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
(<Control>formModel.find(["login"])).updateValue("new value");
|
||||
|
||||
form.ngOnChanges({});
|
||||
|
||||
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual("new value");
|
||||
});
|
||||
|
||||
it("should set up a sync validator", () => {
|
||||
var formValidator = (c) => ({"custom": true});
|
||||
var f = new NgFormModel([formValidator], []);
|
||||
f.form = formModel;
|
||||
f.ngOnChanges({"form": new SimpleChange(null, null)});
|
||||
|
||||
expect(formModel.errors).toEqual({"custom": true});
|
||||
});
|
||||
|
||||
it("should set up an async validator", fakeAsync(() => {
|
||||
var f = new NgFormModel([], [asyncValidator("expected")]);
|
||||
f.form = formModel;
|
||||
f.ngOnChanges({"form": new SimpleChange(null, null)});
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.errors).toEqual({"async": true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("NgForm", () => {
|
||||
var form;
|
||||
var formModel: ControlGroup;
|
||||
var loginControlDir;
|
||||
var personControlGroupDir;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new NgForm([], []);
|
||||
formModel = form.form;
|
||||
|
||||
personControlGroupDir = new NgControlGroup(form, [], []);
|
||||
personControlGroupDir.name = "person";
|
||||
|
||||
loginControlDir = new NgControlName(personControlGroupDir, null, null, [defaultAccessor]);
|
||||
loginControlDir.name = "login";
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it("should reexport control properties", () => {
|
||||
expect(form.control).toBe(formModel);
|
||||
expect(form.value).toBe(formModel.value);
|
||||
expect(form.valid).toBe(formModel.valid);
|
||||
expect(form.errors).toBe(formModel.errors);
|
||||
expect(form.pristine).toBe(formModel.pristine);
|
||||
expect(form.dirty).toBe(formModel.dirty);
|
||||
expect(form.touched).toBe(formModel.touched);
|
||||
expect(form.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
|
||||
describe("addControl & addControlGroup", () => {
|
||||
it("should create a control with the given name", fakeAsync(() => {
|
||||
form.addControlGroup(personControlGroupDir);
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(formModel.find(["person", "login"])).not.toBeNull;
|
||||
}));
|
||||
|
||||
// should update the form's value and validity
|
||||
});
|
||||
|
||||
describe("removeControl & removeControlGroup", () => {
|
||||
it("should remove control", fakeAsync(() => {
|
||||
form.addControlGroup(personControlGroupDir);
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
form.removeControlGroup(personControlGroupDir);
|
||||
form.removeControl(loginControlDir);
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(formModel.find(["person"])).toBeNull();
|
||||
expect(formModel.find(["person", "login"])).toBeNull();
|
||||
}));
|
||||
|
||||
// should update the form's value and validity
|
||||
});
|
||||
|
||||
it("should set up sync validator", fakeAsync(() => {
|
||||
var formValidator = (c) => ({"custom": true});
|
||||
var f = new NgForm([formValidator], []);
|
||||
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({"custom": true});
|
||||
}));
|
||||
|
||||
it("should set up async validator", fakeAsync(() => {
|
||||
var f = new NgForm([], [asyncValidator("expected")]);
|
||||
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({"async": true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe("NgControlGroup", () => {
|
||||
var formModel;
|
||||
var controlGroupDir;
|
||||
|
||||
beforeEach(() => {
|
||||
formModel = new ControlGroup({"login": new Control(null)});
|
||||
|
||||
var parent = new NgFormModel([], []);
|
||||
parent.form = new ControlGroup({"group": formModel});
|
||||
controlGroupDir = new NgControlGroup(parent, [], []);
|
||||
controlGroupDir.name = "group";
|
||||
});
|
||||
|
||||
it("should reexport control properties", () => {
|
||||
expect(controlGroupDir.control).toBe(formModel);
|
||||
expect(controlGroupDir.value).toBe(formModel.value);
|
||||
expect(controlGroupDir.valid).toBe(formModel.valid);
|
||||
expect(controlGroupDir.errors).toBe(formModel.errors);
|
||||
expect(controlGroupDir.pristine).toBe(formModel.pristine);
|
||||
expect(controlGroupDir.dirty).toBe(formModel.dirty);
|
||||
expect(controlGroupDir.touched).toBe(formModel.touched);
|
||||
expect(controlGroupDir.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
});
|
||||
|
||||
describe("NgFormControl", () => {
|
||||
var controlDir;
|
||||
var control;
|
||||
var checkProperties = function(control) {
|
||||
expect(controlDir.control).toBe(control);
|
||||
expect(controlDir.value).toBe(control.value);
|
||||
expect(controlDir.valid).toBe(control.valid);
|
||||
expect(controlDir.errors).toBe(control.errors);
|
||||
expect(controlDir.pristine).toBe(control.pristine);
|
||||
expect(controlDir.dirty).toBe(control.dirty);
|
||||
expect(controlDir.touched).toBe(control.touched);
|
||||
expect(controlDir.untouched).toBe(control.untouched);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
controlDir = new NgFormControl([Validators.required], [], [defaultAccessor]);
|
||||
controlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
|
||||
control = new Control(null);
|
||||
controlDir.form = control;
|
||||
});
|
||||
|
||||
it("should reexport control properties", () => { checkProperties(control); });
|
||||
|
||||
it("should reexport new control properties", () => {
|
||||
var newControl = new Control(null);
|
||||
controlDir.form = newControl;
|
||||
controlDir.ngOnChanges({"form": new SimpleChange(control, newControl)});
|
||||
|
||||
checkProperties(newControl);
|
||||
});
|
||||
|
||||
it("should set up validator", () => {
|
||||
expect(control.valid).toBe(true);
|
||||
|
||||
// this will add the required validator and recalculate the validity
|
||||
controlDir.ngOnChanges({"form": new SimpleChange(null, control)});
|
||||
|
||||
expect(control.valid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("NgModel", () => {
|
||||
var ngModel;
|
||||
|
||||
beforeEach(() => {
|
||||
ngModel =
|
||||
new NgModel([Validators.required], [asyncValidator("expected")], [defaultAccessor]);
|
||||
ngModel.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it("should reexport control properties", () => {
|
||||
var control = ngModel.control;
|
||||
expect(ngModel.control).toBe(control);
|
||||
expect(ngModel.value).toBe(control.value);
|
||||
expect(ngModel.valid).toBe(control.valid);
|
||||
expect(ngModel.errors).toBe(control.errors);
|
||||
expect(ngModel.pristine).toBe(control.pristine);
|
||||
expect(ngModel.dirty).toBe(control.dirty);
|
||||
expect(ngModel.touched).toBe(control.touched);
|
||||
expect(ngModel.untouched).toBe(control.untouched);
|
||||
});
|
||||
|
||||
it("should set up validator", fakeAsync(() => {
|
||||
// this will add the required validator and recalculate the validity
|
||||
ngModel.ngOnChanges({});
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.errors).toEqual({"required": true});
|
||||
|
||||
ngModel.control.updateValue("someValue");
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.errors).toEqual({"async": true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe("NgControlName", () => {
|
||||
var formModel;
|
||||
var controlNameDir;
|
||||
|
||||
beforeEach(() => {
|
||||
formModel = new Control("name");
|
||||
|
||||
var parent = new NgFormModel([], []);
|
||||
parent.form = new ControlGroup({"name": formModel});
|
||||
controlNameDir = new NgControlName(parent, [], [], [defaultAccessor]);
|
||||
controlNameDir.name = "name";
|
||||
});
|
||||
|
||||
it("should reexport control properties", () => {
|
||||
expect(controlNameDir.control).toBe(formModel);
|
||||
expect(controlNameDir.value).toBe(formModel.value);
|
||||
expect(controlNameDir.valid).toBe(formModel.valid);
|
||||
expect(controlNameDir.errors).toBe(formModel.errors);
|
||||
expect(controlNameDir.pristine).toBe(formModel.pristine);
|
||||
expect(controlNameDir.dirty).toBe(formModel.dirty);
|
||||
expect(controlNameDir.touched).toBe(formModel.touched);
|
||||
expect(controlNameDir.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
import {
|
||||
ddescribe,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {Control, FormBuilder} from '@angular/common';
|
||||
import {PromiseWrapper} from '../../src/facade/promise';
|
||||
|
||||
export function main() {
|
||||
function syncValidator(_) { return null; }
|
||||
function asyncValidator(_) { return PromiseWrapper.resolve(null); }
|
||||
|
||||
describe("Form Builder", () => {
|
||||
var b;
|
||||
|
||||
beforeEach(() => { b = new FormBuilder(); });
|
||||
|
||||
it("should create controls from a value", () => {
|
||||
var g = b.group({"login": "some value"});
|
||||
|
||||
expect(g.controls["login"].value).toEqual("some value");
|
||||
});
|
||||
|
||||
it("should create controls from an array", () => {
|
||||
var g = b.group(
|
||||
{"login": ["some value"], "password": ["some value", syncValidator, asyncValidator]});
|
||||
|
||||
expect(g.controls["login"].value).toEqual("some value");
|
||||
expect(g.controls["password"].value).toEqual("some value");
|
||||
expect(g.controls["password"].validator).toEqual(syncValidator);
|
||||
expect(g.controls["password"].asyncValidator).toEqual(asyncValidator);
|
||||
});
|
||||
|
||||
it("should use controls", () => {
|
||||
var g = b.group({"login": b.control("some value", syncValidator, asyncValidator)});
|
||||
|
||||
expect(g.controls["login"].value).toEqual("some value");
|
||||
expect(g.controls["login"].validator).toBe(syncValidator);
|
||||
expect(g.controls["login"].asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
|
||||
it("should create groups with optional controls", () => {
|
||||
var g = b.group({"login": "some value"}, {"optionals": {"login": false}});
|
||||
|
||||
expect(g.contains("login")).toEqual(false);
|
||||
});
|
||||
|
||||
it("should create groups with a custom validator", () => {
|
||||
var g = b.group({"login": "some value"},
|
||||
{"validator": syncValidator, "asyncValidator": asyncValidator});
|
||||
|
||||
expect(g.validator).toBe(syncValidator);
|
||||
expect(g.asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
|
||||
it("should create control arrays", () => {
|
||||
var c = b.control("three");
|
||||
var a = b.array(["one", ["two", syncValidator], c, b.array(['four'])], syncValidator,
|
||||
asyncValidator);
|
||||
|
||||
expect(a.value).toEqual(['one', 'two', 'three', ['four']]);
|
||||
expect(a.validator).toBe(syncValidator);
|
||||
expect(a.asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,804 +0,0 @@
|
||||
import {
|
||||
ddescribe,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
inject,
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {fakeAsync, flushMicrotasks, Log, tick} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
import {ControlGroup, Control, ControlArray, Validators} from '@angular/common';
|
||||
import {IS_DART, isPresent} from '../../src/facade/lang';
|
||||
import {PromiseWrapper} from '../../src/facade/promise';
|
||||
import {TimerWrapper, ObservableWrapper, EventEmitter} from '../../src/facade/async';
|
||||
|
||||
export function main() {
|
||||
function asyncValidator(expected, timeouts = /*@ts2dart_const*/ {}) {
|
||||
return (c) => {
|
||||
var completer = PromiseWrapper.completer();
|
||||
var t = isPresent(timeouts[c.value]) ? timeouts[c.value] : 0;
|
||||
var res = c.value != expected ? {"async": true} : null;
|
||||
|
||||
if (t == 0) {
|
||||
completer.resolve(res);
|
||||
} else {
|
||||
TimerWrapper.setTimeout(() => { completer.resolve(res); }, t);
|
||||
}
|
||||
|
||||
return completer.promise;
|
||||
};
|
||||
}
|
||||
|
||||
function asyncValidatorReturningObservable(c) {
|
||||
var e = new EventEmitter();
|
||||
PromiseWrapper.scheduleMicrotask(() => ObservableWrapper.callEmit(e, {"async": true}));
|
||||
return e;
|
||||
}
|
||||
|
||||
describe("Form Model", () => {
|
||||
describe("Control", () => {
|
||||
it("should default the value to null", () => {
|
||||
var c = new Control();
|
||||
expect(c.value).toBe(null);
|
||||
});
|
||||
|
||||
describe("validator", () => {
|
||||
it("should run validator with the initial value", () => {
|
||||
var c = new Control("value", Validators.required);
|
||||
expect(c.valid).toEqual(true);
|
||||
});
|
||||
|
||||
it("should rerun the validator when the value changes", () => {
|
||||
var c = new Control("value", Validators.required);
|
||||
c.updateValue(null);
|
||||
expect(c.valid).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return errors", () => {
|
||||
var c = new Control(null, Validators.required);
|
||||
expect(c.errors).toEqual({"required": true});
|
||||
});
|
||||
});
|
||||
|
||||
describe("asyncValidator", () => {
|
||||
it("should run validator with the initial value", fakeAsync(() => {
|
||||
var c = new Control("value", null, asyncValidator("expected"));
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({"async": true});
|
||||
}));
|
||||
|
||||
it("should support validators returning observables", fakeAsync(() => {
|
||||
var c = new Control("value", null, asyncValidatorReturningObservable);
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({"async": true});
|
||||
}));
|
||||
|
||||
it("should rerun the validator when the value changes", fakeAsync(() => {
|
||||
var c = new Control("value", null, asyncValidator("expected"));
|
||||
|
||||
c.updateValue("expected");
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(true);
|
||||
}));
|
||||
|
||||
it("should run the async validator only when the sync validator passes", fakeAsync(() => {
|
||||
var c = new Control("", Validators.required, asyncValidator("expected"));
|
||||
tick();
|
||||
|
||||
expect(c.errors).toEqual({"required": true});
|
||||
|
||||
c.updateValue("some value");
|
||||
tick();
|
||||
|
||||
expect(c.errors).toEqual({"async": true});
|
||||
}));
|
||||
|
||||
it("should mark the control as pending while running the async validation",
|
||||
fakeAsync(() => {
|
||||
var c = new Control("", null, asyncValidator("expected"));
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(c.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it("should only use the latest async validation run", fakeAsync(() => {
|
||||
var c =
|
||||
new Control("", null, asyncValidator("expected", {"long": 200, "expected": 100}));
|
||||
|
||||
c.updateValue("long");
|
||||
c.updateValue("expected");
|
||||
|
||||
tick(300);
|
||||
|
||||
expect(c.valid).toEqual(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe("dirty", () => {
|
||||
it("should be false after creating a control", () => {
|
||||
var c = new Control("value");
|
||||
expect(c.dirty).toEqual(false);
|
||||
});
|
||||
|
||||
it("should be true after changing the value of the control", () => {
|
||||
var c = new Control("value");
|
||||
c.markAsDirty();
|
||||
expect(c.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateValue", () => {
|
||||
var g, c;
|
||||
beforeEach(() => {
|
||||
c = new Control("oldValue");
|
||||
g = new ControlGroup({"one": c});
|
||||
});
|
||||
|
||||
it("should update the value of the control", () => {
|
||||
c.updateValue("newValue");
|
||||
expect(c.value).toEqual("newValue");
|
||||
});
|
||||
|
||||
it("should invoke ngOnChanges if it is present", () => {
|
||||
var ngOnChanges;
|
||||
c.registerOnChange((v) => ngOnChanges = ["invoked", v]);
|
||||
|
||||
c.updateValue("newValue");
|
||||
|
||||
expect(ngOnChanges).toEqual(["invoked", "newValue"]);
|
||||
});
|
||||
|
||||
it("should not invoke on change when explicitly specified", () => {
|
||||
var onChange = null;
|
||||
c.registerOnChange((v) => onChange = ["invoked", v]);
|
||||
|
||||
c.updateValue("newValue", {emitModelToViewChange: false});
|
||||
|
||||
expect(onChange).toBeNull();
|
||||
});
|
||||
|
||||
it("should update the parent", () => {
|
||||
c.updateValue("newValue");
|
||||
expect(g.value).toEqual({"one": "newValue"});
|
||||
});
|
||||
|
||||
it("should not update the parent when explicitly specified", () => {
|
||||
c.updateValue("newValue", {onlySelf: true});
|
||||
expect(g.value).toEqual({"one": "oldValue"});
|
||||
});
|
||||
|
||||
it("should fire an event", fakeAsync(() => {
|
||||
ObservableWrapper.subscribe(c.valueChanges,
|
||||
(value) => { expect(value).toEqual("newValue"); });
|
||||
|
||||
c.updateValue("newValue");
|
||||
tick();
|
||||
}));
|
||||
|
||||
it("should not fire an event when explicitly specified", fakeAsync(() => {
|
||||
ObservableWrapper.subscribe(c.valueChanges, (value) => { throw "Should not happen"; });
|
||||
|
||||
c.updateValue("newValue", {emitEvent: false});
|
||||
|
||||
tick();
|
||||
}));
|
||||
});
|
||||
|
||||
describe("valueChanges & statusChanges", () => {
|
||||
var c;
|
||||
|
||||
beforeEach(() => { c = new Control("old", Validators.required); });
|
||||
|
||||
it("should fire an event after the value has been updated",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
ObservableWrapper.subscribe(c.valueChanges, (value) => {
|
||||
expect(c.value).toEqual('new');
|
||||
expect(value).toEqual('new');
|
||||
async.done();
|
||||
});
|
||||
c.updateValue("new");
|
||||
}));
|
||||
|
||||
it("should fire an event after the status has been updated to invalid", fakeAsync(() => {
|
||||
ObservableWrapper.subscribe(c.statusChanges, (status) => {
|
||||
expect(c.status).toEqual('INVALID');
|
||||
expect(status).toEqual('INVALID');
|
||||
});
|
||||
|
||||
c.updateValue("");
|
||||
tick();
|
||||
}));
|
||||
|
||||
it("should fire an event after the status has been updated to pending", fakeAsync(() => {
|
||||
var c = new Control("old", Validators.required, asyncValidator("expected"));
|
||||
|
||||
var log = [];
|
||||
ObservableWrapper.subscribe(c.valueChanges, (value) => log.push(`value: '${value}'`));
|
||||
ObservableWrapper.subscribe(c.statusChanges,
|
||||
(status) => log.push(`status: '${status}'`));
|
||||
|
||||
c.updateValue("");
|
||||
tick();
|
||||
|
||||
c.updateValue("nonEmpty");
|
||||
tick();
|
||||
|
||||
c.updateValue("expected");
|
||||
tick();
|
||||
|
||||
expect(log).toEqual([
|
||||
"" + "value: ''",
|
||||
"status: 'INVALID'",
|
||||
"value: 'nonEmpty'",
|
||||
"status: 'PENDING'",
|
||||
"status: 'INVALID'",
|
||||
"value: 'expected'",
|
||||
"status: 'PENDING'",
|
||||
"status: 'VALID'",
|
||||
]);
|
||||
}));
|
||||
|
||||
// TODO: remove the if statement after making observable delivery sync
|
||||
if (!IS_DART) {
|
||||
it("should update set errors and status before emitting an event",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
c.valueChanges.subscribe(value => {
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({"required": true});
|
||||
async.done();
|
||||
});
|
||||
c.updateValue("");
|
||||
}));
|
||||
}
|
||||
|
||||
it("should return a cold observable", inject([AsyncTestCompleter], (async) => {
|
||||
c.updateValue("will be ignored");
|
||||
ObservableWrapper.subscribe(c.valueChanges, (value) => {
|
||||
expect(value).toEqual('new');
|
||||
async.done();
|
||||
});
|
||||
c.updateValue("new");
|
||||
}));
|
||||
});
|
||||
|
||||
describe("setErrors", () => {
|
||||
it("should set errors on a control", () => {
|
||||
var c = new Control("someValue");
|
||||
|
||||
c.setErrors({"someError": true});
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({"someError": true});
|
||||
});
|
||||
|
||||
it("should reset the errors and validity when the value changes", () => {
|
||||
var c = new Control("someValue", Validators.required);
|
||||
|
||||
c.setErrors({"someError": true});
|
||||
c.updateValue("");
|
||||
|
||||
expect(c.errors).toEqual({"required": true});
|
||||
});
|
||||
|
||||
it("should update the parent group's validity", () => {
|
||||
var c = new Control("someValue");
|
||||
var g = new ControlGroup({"one": c});
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
|
||||
c.setErrors({"someError": true});
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
});
|
||||
|
||||
it("should not reset parent's errors", () => {
|
||||
var c = new Control("someValue");
|
||||
var g = new ControlGroup({"one": c});
|
||||
|
||||
g.setErrors({"someGroupError": true});
|
||||
c.setErrors({"someError": true});
|
||||
|
||||
expect(g.errors).toEqual({"someGroupError": true});
|
||||
});
|
||||
|
||||
it("should reset errors when updating a value", () => {
|
||||
var c = new Control("oldValue");
|
||||
var g = new ControlGroup({"one": c});
|
||||
|
||||
g.setErrors({"someGroupError": true});
|
||||
c.setErrors({"someError": true});
|
||||
|
||||
c.updateValue("newValue");
|
||||
|
||||
expect(c.errors).toEqual(null);
|
||||
expect(g.errors).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ControlGroup", () => {
|
||||
describe("value", () => {
|
||||
it("should be the reduced value of the child controls", () => {
|
||||
var g = new ControlGroup({"one": new Control("111"), "two": new Control("222")});
|
||||
expect(g.value).toEqual({"one": "111", "two": "222"});
|
||||
});
|
||||
|
||||
it("should be empty when there are no child controls", () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.value).toEqual({});
|
||||
});
|
||||
|
||||
it("should support nested groups", () => {
|
||||
var g = new ControlGroup(
|
||||
{"one": new Control("111"), "nested": new ControlGroup({"two": new Control("222")})});
|
||||
expect(g.value).toEqual({"one": "111", "nested": {"two": "222"}});
|
||||
|
||||
(<Control>(g.controls["nested"].find("two"))).updateValue("333");
|
||||
|
||||
expect(g.value).toEqual({"one": "111", "nested": {"two": "333"}});
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
it("should run the validator when the value changes", () => {
|
||||
var simpleValidator = (c) =>
|
||||
c.controls["one"].value != "correct" ? {"broken": true} : null;
|
||||
|
||||
var c = new Control(null);
|
||||
var g = new ControlGroup({"one": c}, null, simpleValidator);
|
||||
|
||||
c.updateValue("correct");
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
expect(g.errors).toEqual(null);
|
||||
|
||||
c.updateValue("incorrect");
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
expect(g.errors).toEqual({"broken": true});
|
||||
});
|
||||
});
|
||||
|
||||
describe("dirty", () => {
|
||||
var c, g;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new Control('value');
|
||||
g = new ControlGroup({"one": c});
|
||||
});
|
||||
|
||||
it("should be false after creating a control", () => { expect(g.dirty).toEqual(false); });
|
||||
|
||||
it("should be false after changing the value of the control", () => {
|
||||
c.markAsDirty();
|
||||
|
||||
expect(g.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("optional components", () => {
|
||||
describe("contains", () => {
|
||||
var group;
|
||||
|
||||
beforeEach(() => {
|
||||
group = new ControlGroup(
|
||||
{
|
||||
"required": new Control("requiredValue"),
|
||||
"optional": new Control("optionalValue")
|
||||
},
|
||||
{"optional": false});
|
||||
});
|
||||
|
||||
// rename contains into has
|
||||
it("should return false when the component is not included",
|
||||
() => { expect(group.contains("optional")).toEqual(false); })
|
||||
|
||||
it("should return false when there is no component with the given name",
|
||||
() => { expect(group.contains("something else")).toEqual(false); });
|
||||
|
||||
it("should return true when the component is included", () => {
|
||||
expect(group.contains("required")).toEqual(true);
|
||||
|
||||
group.include("optional");
|
||||
|
||||
expect(group.contains("optional")).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not include an inactive component into the group value", () => {
|
||||
var group = new ControlGroup(
|
||||
{"required": new Control("requiredValue"), "optional": new Control("optionalValue")},
|
||||
{"optional": false});
|
||||
|
||||
expect(group.value).toEqual({"required": "requiredValue"});
|
||||
|
||||
group.include("optional");
|
||||
|
||||
expect(group.value).toEqual({"required": "requiredValue", "optional": "optionalValue"});
|
||||
});
|
||||
|
||||
it("should not run Validators on an inactive component", () => {
|
||||
var group = new ControlGroup(
|
||||
{
|
||||
"required": new Control("requiredValue", Validators.required),
|
||||
"optional": new Control("", Validators.required)
|
||||
},
|
||||
{"optional": false});
|
||||
|
||||
expect(group.valid).toEqual(true);
|
||||
|
||||
group.include("optional");
|
||||
|
||||
expect(group.valid).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("valueChanges", () => {
|
||||
var g, c1, c2;
|
||||
|
||||
beforeEach(() => {
|
||||
c1 = new Control("old1");
|
||||
c2 = new Control("old2");
|
||||
g = new ControlGroup({"one": c1, "two": c2}, {"two": true});
|
||||
});
|
||||
|
||||
it("should fire an event after the value has been updated",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
expect(g.value).toEqual({'one': 'new1', 'two': 'old2'});
|
||||
expect(value).toEqual({'one': 'new1', 'two': 'old2'});
|
||||
async.done();
|
||||
});
|
||||
c1.updateValue("new1");
|
||||
}));
|
||||
|
||||
it("should fire an event after the control's observable fired an event",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var controlCallbackIsCalled = false;
|
||||
|
||||
ObservableWrapper.subscribe(c1.valueChanges,
|
||||
(value) => { controlCallbackIsCalled = true; });
|
||||
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
expect(controlCallbackIsCalled).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
|
||||
c1.updateValue("new1");
|
||||
}));
|
||||
|
||||
it("should fire an event when a control is excluded",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
expect(value).toEqual({'one': 'old1'});
|
||||
async.done();
|
||||
});
|
||||
|
||||
g.exclude("two");
|
||||
}));
|
||||
|
||||
it("should fire an event when a control is included",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
g.exclude("two");
|
||||
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
expect(value).toEqual({'one': 'old1', 'two': 'old2'});
|
||||
async.done();
|
||||
});
|
||||
|
||||
g.include("two");
|
||||
}));
|
||||
|
||||
it("should fire an event every time a control is updated",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var loggedValues = [];
|
||||
|
||||
ObservableWrapper.subscribe(g.valueChanges, (value) => {
|
||||
loggedValues.push(value);
|
||||
|
||||
if (loggedValues.length == 2) {
|
||||
expect(loggedValues)
|
||||
.toEqual([{"one": "new1", "two": "old2"}, {"one": "new1", "two": "new2"}]);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
c1.updateValue("new1");
|
||||
c2.updateValue("new2");
|
||||
}));
|
||||
|
||||
xit("should not fire an event when an excluded control is updated",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
// hard to test without hacking zones
|
||||
}));
|
||||
});
|
||||
|
||||
describe("getError", () => {
|
||||
it("should return the error when it is present", () => {
|
||||
var c = new Control("", Validators.required);
|
||||
var g = new ControlGroup({"one": c});
|
||||
expect(c.getError("required")).toEqual(true);
|
||||
expect(g.getError("required", ["one"])).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return null otherwise", () => {
|
||||
var c = new Control("not empty", Validators.required);
|
||||
var g = new ControlGroup({"one": c});
|
||||
expect(c.getError("invalid")).toEqual(null);
|
||||
expect(g.getError("required", ["one"])).toEqual(null);
|
||||
expect(g.getError("required", ["invalid"])).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("asyncValidator", () => {
|
||||
it("should run the async validator", fakeAsync(() => {
|
||||
var c = new Control("value");
|
||||
var g = new ControlGroup({"one": c}, null, null, asyncValidator("expected"));
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({"async": true});
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it("should set the parent group's status to pending", fakeAsync(() => {
|
||||
var c = new Control("value", null, asyncValidator("expected"));
|
||||
var g = new ControlGroup({"one": c});
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it("should run the parent group's async validator when children are pending",
|
||||
fakeAsync(() => {
|
||||
var c = new Control("value", null, asyncValidator("expected"));
|
||||
var g = new ControlGroup({"one": c}, null, null, asyncValidator("expected"));
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({"async": true});
|
||||
expect(g.find(["one"]).errors).toEqual({"async": true});
|
||||
}));
|
||||
})
|
||||
});
|
||||
|
||||
describe("ControlArray", () => {
|
||||
describe("adding/removing", () => {
|
||||
var a: ControlArray;
|
||||
var c1, c2, c3;
|
||||
|
||||
beforeEach(() => {
|
||||
a = new ControlArray([]);
|
||||
c1 = new Control(1);
|
||||
c2 = new Control(2);
|
||||
c3 = new Control(3);
|
||||
});
|
||||
|
||||
it("should support pushing", () => {
|
||||
a.push(c1);
|
||||
expect(a.length).toEqual(1);
|
||||
expect(a.controls).toEqual([c1]);
|
||||
});
|
||||
|
||||
it("should support removing", () => {
|
||||
a.push(c1);
|
||||
a.push(c2);
|
||||
a.push(c3);
|
||||
|
||||
a.removeAt(1);
|
||||
|
||||
expect(a.controls).toEqual([c1, c3]);
|
||||
});
|
||||
|
||||
it("should support inserting", () => {
|
||||
a.push(c1);
|
||||
a.push(c3);
|
||||
|
||||
a.insert(1, c2);
|
||||
|
||||
expect(a.controls).toEqual([c1, c2, c3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("value", () => {
|
||||
it("should be the reduced value of the child controls", () => {
|
||||
var a = new ControlArray([new Control(1), new Control(2)]);
|
||||
expect(a.value).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it("should be an empty array when there are no child controls", () => {
|
||||
var a = new ControlArray([]);
|
||||
expect(a.value).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
it("should run the validator when the value changes", () => {
|
||||
var simpleValidator = (c) => c.controls[0].value != "correct" ? {"broken": true} : null;
|
||||
|
||||
var c = new Control(null);
|
||||
var g = new ControlArray([c], simpleValidator);
|
||||
|
||||
c.updateValue("correct");
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
expect(g.errors).toEqual(null);
|
||||
|
||||
c.updateValue("incorrect");
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
expect(g.errors).toEqual({"broken": true});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("dirty", () => {
|
||||
var c: Control;
|
||||
var a: ControlArray;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new Control('value');
|
||||
a = new ControlArray([c]);
|
||||
});
|
||||
|
||||
it("should be false after creating a control", () => { expect(a.dirty).toEqual(false); });
|
||||
|
||||
it("should be false after changing the value of the control", () => {
|
||||
c.markAsDirty();
|
||||
|
||||
expect(a.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pending", () => {
|
||||
var c: Control;
|
||||
var a: ControlArray;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new Control('value');
|
||||
a = new ControlArray([c]);
|
||||
});
|
||||
|
||||
it("should be false after creating a control", () => {
|
||||
expect(c.pending).toEqual(false);
|
||||
expect(a.pending).toEqual(false);
|
||||
});
|
||||
|
||||
it("should be true after changing the value of the control", () => {
|
||||
c.markAsPending();
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
expect(a.pending).toEqual(true);
|
||||
});
|
||||
|
||||
it("should not update the parent when onlySelf = true", () => {
|
||||
c.markAsPending({onlySelf: true});
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
expect(a.pending).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("valueChanges", () => {
|
||||
var a: ControlArray;
|
||||
var c1, c2;
|
||||
|
||||
beforeEach(() => {
|
||||
c1 = new Control("old1");
|
||||
c2 = new Control("old2");
|
||||
a = new ControlArray([c1, c2]);
|
||||
});
|
||||
|
||||
it("should fire an event after the value has been updated",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
ObservableWrapper.subscribe(a.valueChanges, (value) => {
|
||||
expect(a.value).toEqual(['new1', 'old2']);
|
||||
expect(value).toEqual(['new1', 'old2']);
|
||||
async.done();
|
||||
});
|
||||
c1.updateValue("new1");
|
||||
}));
|
||||
|
||||
it("should fire an event after the control's observable fired an event",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var controlCallbackIsCalled = false;
|
||||
|
||||
ObservableWrapper.subscribe(c1.valueChanges,
|
||||
(value) => { controlCallbackIsCalled = true; });
|
||||
|
||||
ObservableWrapper.subscribe(a.valueChanges, (value) => {
|
||||
expect(controlCallbackIsCalled).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
|
||||
c1.updateValue("new1");
|
||||
}));
|
||||
|
||||
it("should fire an event when a control is removed",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
ObservableWrapper.subscribe(a.valueChanges, (value) => {
|
||||
expect(value).toEqual(['old1']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
a.removeAt(1);
|
||||
}));
|
||||
|
||||
it("should fire an event when a control is added", inject([AsyncTestCompleter], (async) => {
|
||||
a.removeAt(1);
|
||||
|
||||
ObservableWrapper.subscribe(a.valueChanges, (value) => {
|
||||
expect(value).toEqual(['old1', 'old2']);
|
||||
async.done();
|
||||
});
|
||||
|
||||
a.push(c2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe("find", () => {
|
||||
it("should return null when path is null", () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.find(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it("should return null when path is empty", () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.find([])).toEqual(null);
|
||||
});
|
||||
|
||||
it("should return null when path is invalid", () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.find(["one", "two"])).toEqual(null);
|
||||
});
|
||||
|
||||
it("should return a child of a control group", () => {
|
||||
var g = new ControlGroup(
|
||||
{"one": new Control("111"), "nested": new ControlGroup({"two": new Control("222")})});
|
||||
|
||||
expect(g.find(["nested", "two"]).value).toEqual("222");
|
||||
expect(g.find(["one"]).value).toEqual("111");
|
||||
expect(g.find("nested/two").value).toEqual("222");
|
||||
expect(g.find("one").value).toEqual("111");
|
||||
});
|
||||
|
||||
it("should return an element of an array", () => {
|
||||
var g = new ControlGroup({"array": new ControlArray([new Control("111")])});
|
||||
|
||||
expect(g.find(["array", 0]).value).toEqual("111");
|
||||
});
|
||||
});
|
||||
|
||||
describe("asyncValidator", () => {
|
||||
it("should run the async validator", fakeAsync(() => {
|
||||
var c = new Control("value");
|
||||
var g = new ControlArray([c], null, asyncValidator("expected"));
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({"async": true});
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
import {
|
||||
ddescribe,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
import {fakeAsync, flushMicrotasks, Log, tick} from '@angular/core/testing';
|
||||
import {ControlGroup, Control, Validators, AbstractControl, ControlArray} from '@angular/common';
|
||||
import {PromiseWrapper} from '../../src/facade/promise';
|
||||
import {EventEmitter, ObservableWrapper, TimerWrapper} from '../../src/facade/async';
|
||||
|
||||
export function main() {
|
||||
function validator(key: string, error: any) {
|
||||
return function(c: AbstractControl) {
|
||||
var r = {};
|
||||
r[key] = error;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
describe("Validators", () => {
|
||||
describe("required", () => {
|
||||
it("should error on an empty string",
|
||||
() => { expect(Validators.required(new Control(""))).toEqual({"required": true}); });
|
||||
|
||||
it("should error on null",
|
||||
() => { expect(Validators.required(new Control(null))).toEqual({"required": true}); });
|
||||
|
||||
it("should not error on a non-empty string",
|
||||
() => { expect(Validators.required(new Control("not empty"))).toEqual(null); });
|
||||
|
||||
it("should accept zero as valid",
|
||||
() => { expect(Validators.required(new Control(0))).toEqual(null); });
|
||||
});
|
||||
|
||||
describe("minLength", () => {
|
||||
it("should not error on an empty string",
|
||||
() => { expect(Validators.minLength(2)(new Control(""))).toEqual(null); });
|
||||
|
||||
it("should not error on null",
|
||||
() => { expect(Validators.minLength(2)(new Control(null))).toEqual(null); });
|
||||
|
||||
it("should not error on valid strings",
|
||||
() => { expect(Validators.minLength(2)(new Control("aa"))).toEqual(null); });
|
||||
|
||||
it("should error on short strings", () => {
|
||||
expect(Validators.minLength(2)(new Control("a")))
|
||||
.toEqual({"minlength": {"requiredLength": 2, "actualLength": 1}});
|
||||
});
|
||||
});
|
||||
|
||||
describe("maxLength", () => {
|
||||
it("should not error on an empty string",
|
||||
() => { expect(Validators.maxLength(2)(new Control(""))).toEqual(null); });
|
||||
|
||||
it("should not error on null",
|
||||
() => { expect(Validators.maxLength(2)(new Control(null))).toEqual(null); });
|
||||
|
||||
it("should not error on valid strings",
|
||||
() => { expect(Validators.maxLength(2)(new Control("aa"))).toEqual(null); });
|
||||
|
||||
it("should error on long strings", () => {
|
||||
expect(Validators.maxLength(2)(new Control("aaa")))
|
||||
.toEqual({"maxlength": {"requiredLength": 2, "actualLength": 3}});
|
||||
});
|
||||
});
|
||||
|
||||
describe("pattern", () => {
|
||||
it("should not error on an empty string",
|
||||
() => { expect(Validators.pattern("[a-zA-Z ]*")(new Control(""))).toEqual(null); });
|
||||
|
||||
it("should not error on null",
|
||||
() => { expect(Validators.pattern("[a-zA-Z ]*")(new Control(null))).toEqual(null); });
|
||||
|
||||
it("should not error on valid strings",
|
||||
() => { expect(Validators.pattern("[a-zA-Z ]*")(new Control("aaAA"))).toEqual(null); });
|
||||
|
||||
it("should error on failure to match string", () => {
|
||||
expect(Validators.pattern("[a-zA-Z ]*")(new Control("aaa0")))
|
||||
.toEqual({"pattern": {"requiredPattern": "^[a-zA-Z ]*$", "actualValue": "aaa0"}});
|
||||
});
|
||||
});
|
||||
|
||||
describe("compose", () => {
|
||||
it("should return null when given null",
|
||||
() => { expect(Validators.compose(null)).toBe(null); });
|
||||
|
||||
it("should collect errors from all the validators", () => {
|
||||
var c = Validators.compose([validator("a", true), validator("b", true)]);
|
||||
expect(c(new Control(""))).toEqual({"a": true, "b": true});
|
||||
});
|
||||
|
||||
it("should run validators left to right", () => {
|
||||
var c = Validators.compose([validator("a", 1), validator("a", 2)]);
|
||||
expect(c(new Control(""))).toEqual({"a": 2});
|
||||
});
|
||||
|
||||
it("should return null when no errors", () => {
|
||||
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
|
||||
expect(c(new Control(""))).toEqual(null);
|
||||
});
|
||||
|
||||
it("should ignore nulls", () => {
|
||||
var c = Validators.compose([null, Validators.required]);
|
||||
expect(c(new Control(""))).toEqual({"required": true});
|
||||
});
|
||||
});
|
||||
|
||||
describe("composeAsync", () => {
|
||||
function asyncValidator(expected, response) {
|
||||
return (c) => {
|
||||
var emitter = new EventEmitter();
|
||||
var res = c.value != expected ? response : null;
|
||||
|
||||
PromiseWrapper.scheduleMicrotask(() => {
|
||||
ObservableWrapper.callEmit(emitter, res);
|
||||
// this is required because of a bug in ObservableWrapper
|
||||
// where callComplete can fire before callEmit
|
||||
// remove this one the bug is fixed
|
||||
TimerWrapper.setTimeout(() => { ObservableWrapper.callComplete(emitter); }, 0);
|
||||
});
|
||||
return emitter;
|
||||
};
|
||||
}
|
||||
|
||||
it("should return null when given null",
|
||||
() => { expect(Validators.composeAsync(null)).toEqual(null); });
|
||||
|
||||
it("should collect errors from all the validators", fakeAsync(() => {
|
||||
var c = Validators.composeAsync([
|
||||
asyncValidator("expected", {"one": true}),
|
||||
asyncValidator("expected", {"two": true})
|
||||
]);
|
||||
|
||||
var value = null;
|
||||
(<Promise<any>>c(new Control("invalid"))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({"one": true, "two": true});
|
||||
}));
|
||||
|
||||
it("should return null when no errors", fakeAsync(() => {
|
||||
var c = Validators.composeAsync([asyncValidator("expected", {"one": true})]);
|
||||
|
||||
var value = null;
|
||||
(<Promise<any>>c(new Control("expected"))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual(null);
|
||||
}));
|
||||
|
||||
it("should ignore nulls", fakeAsync(() => {
|
||||
var c = Validators.composeAsync([asyncValidator("expected", {"one": true}), null]);
|
||||
|
||||
var value = null;
|
||||
(<Promise<any>>c(new Control("invalid"))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({"one": true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user