├── .editorconfig
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── karma.conf.ts
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── bootstrap.ts
├── component.ts
├── directive.ts
├── element_ref.ts
├── hostListener.ts
├── index.ts
├── injectable.ts
├── input.ts
├── lifecycle_hooks.ts
├── module.ts
├── pipe.ts
├── provider.ts
├── type.ts
├── utils.ts
└── viewChild.ts
├── test
├── mocks.ts
├── ng-module.spec.ts
└── tsconfig.json
├── tsconfig-base.json
├── tsconfig.json
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.{js,ts,json,css,html}]
12 | indent_size = 2
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
17 | [Makefile]
18 | indent_style = tab
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | coverage/
3 | *.log
4 | lib/
5 | build/
6 | dist/
7 | .idea/
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "7"
5 |
6 | sudo: required
7 |
8 | dist: trusty
9 |
10 | branches:
11 | only:
12 | - master
13 | - /^v\d+\.\d+(\.\d+)?(-\S*)?$/
14 | - /^greenkeeper/.*$/
15 |
16 | # install headless chrome
17 | before_install:
18 | - export CHROME_BIN=/usr/bin/google-chrome
19 | - export DISPLAY=:99.0
20 | - sh -e /etc/init.d/xvfb start
21 | - sudo apt-get update
22 | - sudo apt-get install -y libappindicator1 fonts-liberation
23 | - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
24 | - sudo dpkg -i google-chrome*.deb
25 |
26 | install:
27 | - npm install
28 |
29 | before_script:
30 | - npm test
31 |
32 | script:
33 | - npm run build
34 |
35 | before_deploy:
36 | - cd dist
37 |
38 | deploy:
39 | provider: npm
40 | api_key: $NPM_TOKEN
41 | email: vlad.sternbach@gmail.com
42 | skip_cleanup: true
43 | on:
44 | tags: true
45 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ### [3.7.8](https://github.com/vsternbach/angular-ts-decorators/compare/v3.7.7...v3.7.8) (2019-05-21)
6 |
7 |
8 |
9 |
10 | ## [3.7.7](https://github.com/vsternbach/angular-ts-decorators/compare/v3.7.6...v3.7.7) (2018-08-23)
11 |
12 |
13 | ### Bug Fixes
14 |
15 | * **build:** update rollup config according to new format ([c78f180](https://github.com/vsternbach/angular-ts-decorators/commit/c78f180))
16 |
17 |
18 |
19 |
20 | ## [3.7.6](https://github.com/vsternbach/angular-ts-decorators/compare/v3.7.5...v3.7.6) (2018-08-23)
21 |
22 |
23 |
24 |
25 | ## [3.7.5](https://github.com/vsternbach/angular-ts-decorators/compare/v3.7.4...v3.7.5) (2018-05-13)
26 |
27 |
28 | ### Bug Fixes
29 |
30 | * **ngHooks:** leave ngHooks so inherited components could call them ([03732dc](https://github.com/vsternbach/angular-ts-decorators/commit/03732dc)), closes [#70](https://github.com/vsternbach/angular-ts-decorators/issues/70)
31 |
32 |
33 |
34 |
35 | ## [3.7.4](https://github.com/vsternbach/angular-ts-decorators/compare/v3.7.3...v3.7.4) (2018-04-30)
36 |
37 |
38 | ### Bug Fixes
39 |
40 | * **ViewChild:** add support for querying ViewChild by element id ([4c92d69](https://github.com/vsternbach/angular-ts-decorators/commit/4c92d69)), closes [#66](https://github.com/vsternbach/angular-ts-decorators/issues/66)
41 |
42 |
43 |
44 |
45 | ## [3.7.3](https://github.com/vsternbach/angular-ts-decorators/compare/v3.7.2...v3.7.3) (2018-04-23)
46 |
47 |
48 | ### Bug Fixes
49 |
50 | * **Inject:** add support for class methods ([14a6dcb](https://github.com/vsternbach/angular-ts-decorators/commit/14a6dcb)), closes [#65](https://github.com/vsternbach/angular-ts-decorators/issues/65)
51 |
52 |
53 |
54 |
55 | ## [3.7.2](https://github.com/vsternbach/angular-ts-decorators/compare/v3.7.1...v3.7.2) (2018-04-21)
56 |
57 |
58 | ### Bug Fixes
59 |
60 | * **ViewChild:** remove unnecessary dependency on DebugData ([40cccfb](https://github.com/vsternbach/angular-ts-decorators/commit/40cccfb))
61 |
62 |
63 |
64 |
65 | ## [3.7.1](https://github.com/vsternbach/angular-ts-decorators/compare/v3.7.0...v3.7.1) (2018-04-21)
66 |
67 |
68 | ### Bug Fixes
69 |
70 | * **Component:** replace array.from with destructuring ([72bca2b](https://github.com/vsternbach/angular-ts-decorators/commit/72bca2b))
71 | * **Input:** fix test after making input optional ([70df825](https://github.com/vsternbach/angular-ts-decorators/commit/70df825))
72 |
73 |
74 |
75 |
76 | # [3.7.0](https://github.com/vsternbach/angular-ts-decorators/compare/v3.6.1...v3.7.0) (2018-04-20)
77 |
78 |
79 | ### Features
80 |
81 | * **Input:** make input optional by default ([12cacb2](https://github.com/vsternbach/angular-ts-decorators/commit/12cacb2)), closes [#60](https://github.com/vsternbach/angular-ts-decorators/issues/60)
82 |
83 |
84 |
85 |
86 | ## [3.6.1](https://github.com/vsternbach/angular-ts-decorators/compare/v3.6.0...v3.6.1) (2018-04-14)
87 |
88 |
89 | ### Bug Fixes
90 |
91 | * **utils:** fix export util functions ([073eff9](https://github.com/vsternbach/angular-ts-decorators/commit/073eff9))
92 |
93 |
94 |
95 |
96 | # [3.6.0](https://github.com/vsternbach/angular-ts-decorators/compare/v3.5.0...v3.6.0) (2018-04-14)
97 |
98 |
99 | ### Features
100 |
101 | * **helpers:** export util functions as helpers ([60c6be8](https://github.com/vsternbach/angular-ts-decorators/commit/60c6be8))
102 |
103 |
104 |
105 |
106 | # [3.5.0](https://github.com/vsternbach/angular-ts-decorators/compare/v3.4.1...v3.5.0) (2018-04-14)
107 |
108 |
109 | ### Features
110 |
111 | * **utils:** add some utils functions to public api ([1220117](https://github.com/vsternbach/angular-ts-decorators/commit/1220117))
112 |
113 |
114 |
115 |
116 | ## [3.4.1](https://github.com/vsternbach/angular-ts-decorators/compare/v3.4.0...v3.4.1) (2018-04-12)
117 |
118 |
119 | ### Bug Fixes
120 |
121 | * **inject:** change logic to re-assignment instead of splice ([a6519f0](https://github.com/vsternbach/angular-ts-decorators/commit/a6519f0)), closes [#64](https://github.com/vsternbach/angular-ts-decorators/issues/64)
122 |
123 |
124 |
125 |
126 | # [3.4.0](https://github.com/vsternbach/angular-ts-decorators/compare/v3.3.1...v3.4.0) (2018-04-09)
127 |
128 |
129 | ### Bug Fixes
130 |
131 | * **bootstrap:** fix bug when compilerOptions are not provided ([3920e0b](https://github.com/vsternbach/angular-ts-decorators/commit/3920e0b))
132 | * **component:** update children binding in $onChanges ([3e81205](https://github.com/vsternbach/angular-ts-decorators/commit/3e81205)), closes [#62](https://github.com/vsternbach/angular-ts-decorators/issues/62)
133 |
134 |
135 | ### Features
136 |
137 | * **bootstrap:** refactor bootstrap logic ([ecc5ac9](https://github.com/vsternbach/angular-ts-decorators/commit/ecc5ac9))
138 |
139 |
140 |
141 |
142 | ## [3.3.1](https://github.com/vsternbach/angular-ts-decorators/compare/v3.3.0...v3.3.1) (2018-03-04)
143 |
144 |
145 | ### Bug Fixes
146 |
147 | * **types:** fix exports in utils.d.ts ([871a747](https://github.com/vsternbach/angular-ts-decorators/commit/871a747)), closes [#61](https://github.com/vsternbach/angular-ts-decorators/issues/61)
148 |
149 |
150 |
151 |
152 | # [3.3.0](https://github.com/vsternbach/angular-ts-decorators/compare/v3.2.1...v3.3.0) (2018-02-26)
153 |
154 |
155 | ### Features
156 |
157 | * **ViewParent:** add ViewParent property decorator ([69a1214](https://github.com/vsternbach/angular-ts-decorators/commit/69a1214))
158 |
159 |
160 |
161 |
162 | ## [3.2.1](https://github.com/vsternbach/angular-ts-decorators/compare/v3.2.0...v3.2.1) (2018-02-25)
163 |
164 |
165 |
166 |
167 | # [3.2.0](https://github.com/vsternbach/angular-ts-decorators/compare/v3.1.0...v3.2.0) (2018-02-25)
168 |
169 |
170 | ### Bug Fixes
171 |
172 | * **bootstrap:** fix bootstraping on body tag when no jQuery present ([2d45dbc](https://github.com/vsternbach/angular-ts-decorators/commit/2d45dbc))
173 | * **view child:** refactor to use type as selector ([73e3056](https://github.com/vsternbach/angular-ts-decorators/commit/73e3056))
174 | * **ViewChild:** finish ViewChild implementation and bugfixes ([ea40c33](https://github.com/vsternbach/angular-ts-decorators/commit/ea40c33))
175 |
176 |
177 | ### Features
178 |
179 | * **query:** add ViewChild and ViewChildren ([d2b0975](https://github.com/vsternbach/angular-ts-decorators/commit/d2b0975))
180 | * add ViewChild and ViewChildren decorators ([53a7ddd](https://github.com/vsternbach/angular-ts-decorators/commit/53a7ddd))
181 |
182 |
183 |
184 |
185 | # [3.1.0](https://github.com/vsternbach/angular-ts-decorators/compare/v3.0.5...v3.1.0) (2018-02-18)
186 |
187 |
188 | ### Features
189 |
190 | * **Inject:** add support for Inject decorator ([167dce0](https://github.com/vsternbach/angular-ts-decorators/commit/167dce0)), closes [#57](https://github.com/vsternbach/angular-ts-decorators/issues/57)
191 |
192 |
193 |
194 |
195 | ## [3.0.5](https://github.com/vsternbach/angular-ts-decorators/compare/v3.0.4...v3.0.5) (2018-01-30)
196 |
197 |
198 | ### Bug Fixes
199 |
200 | * **deps:** downgrade reflect-metadata because of broken sourcemap ([a7fe07d](https://github.com/vsternbach/angular-ts-decorators/commit/a7fe07d))
201 |
202 |
203 |
204 |
205 | ## [3.0.4](https://github.com/vsternbach/angular-ts-decorators/compare/v3.0.3...v3.0.4) (2018-01-29)
206 |
207 |
208 | ### Bug Fixes
209 |
210 | * **HostListener:** fix deregistering host listeners ([e8e4a14](https://github.com/vsternbach/angular-ts-decorators/commit/e8e4a14)), closes [#52](https://github.com/vsternbach/angular-ts-decorators/issues/52)
211 |
212 |
213 |
214 |
215 | ## [3.0.3](https://github.com/vsternbach/angular-ts-decorators/compare/v3.0.2...v3.0.3) (2018-01-29)
216 |
217 |
218 |
219 |
220 | ## [3.0.2](https://github.com/vsternbach/angular-ts-decorators/compare/v3.0.1...v3.0.2) (2018-01-29)
221 |
222 |
223 | ### Bug Fixes
224 |
225 | * **Pipe:** fixed $inject bug ([1a84056](https://github.com/vsternbach/angular-ts-decorators/commit/1a84056))
226 |
227 |
228 |
229 |
230 | ## [3.0.1](https://github.com/vsternbach/angular-ts-decorators/compare/v3.0.0...v3.0.1) (2018-01-15)
231 |
232 |
233 | ### Bug Fixes
234 |
235 | * update test suite after removing implicit annotations ([0046928](https://github.com/vsternbach/angular-ts-decorators/commit/0046928))
236 |
237 |
238 |
239 |
240 | # [3.0.0](https://github.com/vsternbach/angular-ts-decorators/compare/v2.1.0...v3.0.0) (2018-01-15)
241 |
242 |
243 | ### Features
244 |
245 | * Remove support for implicit annotations ([19f8ad9](https://github.com/vsternbach/angular-ts-decorators/commit/19f8ad9))
246 | * **NgModule:** Add support for bootstrap elements in NgModule
247 |
248 | ### Bug Fixes
249 |
250 | * use explicit imports from angular definitions instead of namespaced
251 | * **build:** update typescript
252 |
253 | ### BREAKING CHANGES
254 |
255 | * Implicit annotations were error prone and didn't work
256 | correctly with uglified code, so this feature is removed completely now
257 | and it's user's responsibility to take care of angular annotations,
258 | either by explicitly providing them or by using tools like ng-annotate.
259 |
260 |
261 |
262 | # [2.1.0](https://github.com/vsternbach/angular-ts-decorators/compare/v2.0.0...v2.1.0) (2017-11-29)
263 |
264 |
265 | ### Features
266 |
267 | * add missing exports for Type, Provider and ModuleConfig ([addadc2](https://github.com/vsternbach/angular-ts-decorators/commit/addadc2))
268 |
269 |
270 |
271 |
272 | # [2.0.0](https://github.com/vsternbach/angular-ts-decorators/compare/v1.3.1...v2.0.0) (2017-11-29)
273 |
274 |
275 | ### Bug Fixes
276 |
277 | * handle use case of service registration without explicit name ([667e921](https://github.com/vsternbach/angular-ts-decorators/commit/667e921)), closes [#39](https://github.com/vsternbach/angular-ts-decorators/issues/39)
278 |
279 |
280 | ### Features
281 |
282 | * add utility function to get Type name ([390bc80](https://github.com/vsternbach/angular-ts-decorators/commit/390bc80))
283 | * Remove support for link, compile and $provider ([f92351d](https://github.com/vsternbach/angular-ts-decorators/commit/f92351d))
284 |
285 |
286 | ### BREAKING CHANGES
287 |
288 | * remove support for directive's link and compile, so you
289 | if you want to migrate directives with link or compile, you need to
290 | register them the old angularjs way.
291 | * remove support for classes registered as $providers, so
292 | if you have custom providers you need to register them the old way.
293 |
294 |
295 |
296 |
297 | ## [1.3.1](https://github.com/vsternbach/angular-ts-decorators/compare/v1.3.0...v1.3.1) (2017-11-14)
298 |
299 |
300 |
301 |
302 | # [1.3.0](https://github.com/vsternbach/angular-ts-decorators/compare/v1.2.5...v1.3.0) (2017-11-06)
303 |
304 |
305 | ### Bug Fixes
306 |
307 | * **providers:** Support deps for useFactory provider registration ([f51eea9](https://github.com/vsternbach/angular-ts-decorators/commit/f51eea9)), closes [#45](https://github.com/vsternbach/angular-ts-decorators/issues/45)
308 |
309 |
310 | ### Features
311 |
312 | * **lifecycle_hooks:** Support generic SimpleChange for use in ngOnChanges ([c730215](https://github.com/vsternbach/angular-ts-decorators/commit/c730215)), closes [#46](https://github.com/vsternbach/angular-ts-decorators/issues/46)
313 |
314 |
315 |
316 |
317 | ## [1.2.5](https://github.com/vsternbach/angular-ts-decorators/compare/v1.2.4...v1.2.5) (2017-07-03)
318 |
319 |
320 | ### Bug Fixes
321 |
322 | * **directive:** add support for controller and angular hooks ([2eef743](https://github.com/vsternbach/angular-ts-decorators/commit/2eef743)), closes [#31](https://github.com/vsternbach/angular-ts-decorators/issues/31) [#32](https://github.com/vsternbach/angular-ts-decorators/issues/32)
323 |
324 |
325 |
326 |
327 | ## [1.2.4](https://github.com/vsternbach/angular-ts-decorators/compare/v1.2.3...v1.2.4) (2017-06-28)
328 |
329 |
330 | ### Bug Fixes
331 |
332 | * **component:** add support for automatic injection ([3e53658](https://github.com/vsternbach/angular-ts-decorators/commit/3e53658)), closes [#29](https://github.com/vsternbach/angular-ts-decorators/issues/29)
333 |
334 |
335 |
336 |
337 | ## [1.2.3](https://github.com/vsternbach/angular-ts-decorators/compare/v1.2.2...v1.2.3) (2017-06-27)
338 |
339 |
340 | ### Bug Fixes
341 |
342 | * **NgModule:** Support for TypeScript 2.4, which requires a weak ([293e783](https://github.com/vsternbach/angular-ts-decorators/commit/293e783))
343 |
344 |
345 |
346 |
347 | ## [1.2.2](https://github.com/vsternbach/angular-ts-decorators/compare/v1.2.1...v1.2.2) (2017-06-27)
348 |
349 |
350 | ### Bug Fixes
351 |
352 | * **build:** rename package-lock to npm-shrinkwrap ([853a8d6](https://github.com/vsternbach/angular-ts-decorators/commit/853a8d6))
353 |
354 |
355 |
356 |
357 | ## [1.2.1](https://github.com/vsternbach/angular-ts-decorators/compare/v1.2.0...v1.2.1) (2017-06-27)
358 |
359 |
360 | ### Bug Fixes
361 |
362 | * **build:** add package-lock and upgrade to typescript 2.4.1 ([fd7b43e](https://github.com/vsternbach/angular-ts-decorators/commit/fd7b43e))
363 |
364 |
365 |
366 |
367 | # [1.2.0](https://github.com/vsternbach/angular-ts-decorators/compare/v1.1.2...v1.2.0) (2017-06-27)
368 |
369 |
370 | ### Bug Fixes
371 |
372 | * **NgModule:** make declarations optional ([6c510d1](https://github.com/vsternbach/angular-ts-decorators/commit/6c510d1))
373 | * add Type and Provider from angular source code ([2174cec](https://github.com/vsternbach/angular-ts-decorators/commit/2174cec))
374 |
375 |
376 | ### Features
377 |
378 | * **bootstrap:** add bootstrap abstraction as in angular 2 ([84356ca](https://github.com/vsternbach/angular-ts-decorators/commit/84356ca))
379 | * **directive:** add [@HostListener](https://github.com/HostListener) support ([b34250f](https://github.com/vsternbach/angular-ts-decorators/commit/b34250f))
380 |
381 |
382 |
383 |
384 | ## [1.1.2](https://github.com/vsternbach/angular-ts-decorators/compare/v1.1.1...v1.1.2) (2017-06-21)
385 |
386 |
387 | ### Features
388 |
389 | * **@Component:** add support for styles using webpack require ([f0703ed](https://github.com/vsternbach/angular-ts-decorators/commit/f0703ed)), closes [#25](https://github.com/vsternbach/angular-ts-decorators/issues/25)
390 |
391 |
392 |
393 |
394 | ## [1.1.1](https://github.com/vsternbach/angular-ts-decorators/compare/v1.1.0...v1.1.1) (2017-05-19)
395 |
396 |
397 | ### Bug Fixes
398 |
399 | * **@Component:** fix injection error when original controller has no dependencies ([5b5b6a9](https://github.com/vsternbach/angular-ts-decorators/commit/5b5b6a9))
400 |
401 |
402 |
403 |
404 | # [1.1.0](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.15...v1.1.0) (2017-05-17)
405 |
406 |
407 | ### Bug Fixes
408 |
409 | * **@NgModule:** add backward compatibility for deprecated NgModuleDecorated ([97f7fd5](https://github.com/vsternbach/angular-ts-decorators/commit/97f7fd5))
410 |
411 |
412 | ### Features
413 |
414 | * **@Component:** add [@HostListener](https://github.com/HostListener) ([26f5094](https://github.com/vsternbach/angular-ts-decorators/commit/26f5094)), closes [#13](https://github.com/vsternbach/angular-ts-decorators/issues/13)
415 | * **@Component:** add angular 2+ style lifecycle hooks support ([b2fd1bf](https://github.com/vsternbach/angular-ts-decorators/commit/b2fd1bf))
416 |
417 |
418 |
419 |
420 | ## [1.0.15](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.14...v1.0.15) (2017-05-15)
421 |
422 |
423 | ### Bug Fixes
424 |
425 | * **build:** add minified build ([10db0e0](https://github.com/vsternbach/angular-ts-decorators/commit/10db0e0))
426 | * **deploy:** change string substitution to POSIX format ([0eea0d9](https://github.com/vsternbach/angular-ts-decorators/commit/0eea0d9))
427 |
428 |
429 |
430 |
431 | ## [1.0.14](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.13...v1.0.14) (2017-05-14)
432 |
433 |
434 | ### Bug Fixes
435 |
436 | * **build:** fix interfaces export issue ([bdf6cfe](https://github.com/vsternbach/angular-ts-decorators/commit/bdf6cfe))
437 |
438 |
439 |
440 |
441 | ## [1.0.13](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.12...v1.0.13) (2017-04-27)
442 |
443 |
444 | ### Bug Fixes
445 |
446 | * **NgModule:** change warnings ([a23b78e](https://github.com/vsternbach/angular-ts-decorators/commit/a23b78e))
447 |
448 |
449 |
450 |
451 | ## [1.0.12](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.11...v1.0.12) (2017-04-27)
452 |
453 |
454 |
455 |
456 | ## [1.0.11](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.10...v1.0.11) (2017-04-27)
457 |
458 |
459 |
460 |
461 | ## [1.0.10](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.9...v1.0.10) (2017-04-27)
462 |
463 |
464 |
465 |
466 | ## [1.0.9](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.8...v1.0.9) (2017-04-27)
467 |
468 |
469 |
470 |
471 | ## [1.0.8](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.7...v1.0.8) (2017-04-27)
472 |
473 |
474 | ### Bug Fixes
475 |
476 | * **build:** fix build script ([f48d2ad](https://github.com/vsternbach/angular-ts-decorators/commit/f48d2ad))
477 |
478 |
479 |
480 |
481 | ## [1.0.7](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.6...v1.0.7) (2017-04-27)
482 |
483 |
484 |
485 |
486 | ## [1.0.6](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.5...v1.0.6) (2017-04-27)
487 |
488 |
489 |
490 |
491 | ## [1.0.5](https://github.com/vsternbach/angular-ts-decorators/compare/v1.0.4...v1.0.5) (2017-04-27)
492 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Vlad Sternbach
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-ts-decorators
2 |
3 | A collection of angular 2 style decorators for angularjs 1.5.x projects written in typescript.
4 |
5 | [](https://travis-ci.org/vsternbach/angular-ts-decorators)
6 | [](https://coveralls.io/github/vsternbach/angular-ts-decorators?branch=master)
7 | [](https://greenkeeper.io/)
8 |
9 | [](https://nodei.co/npm/angular-ts-decorators/)
10 |
11 | See example of usage [here](https://github.com/vsternbach/angularjs-typescript-webpack)
12 |
13 | ## Prerequisites
14 | `angular-ts-decorators` tries to mimic [angular 2 style](https://angular.io/docs/ts/latest/guide/style-guide.html) decorators as closely as possible.
15 |
16 | Some of the decorator interfaces (@Component and @Directive) were heavily inspired by this excellent [Angular 1.x styleguide (ES2015)](https://github.com/toddmotto/angular-styleguide).
17 |
18 | > Behind the scenes it uses [Metadata Reflection API](https://www.npmjs.com/package/reflect-metadata) to add metadata to the classes.
19 |
20 | ## Installation
21 |
22 | `npm i -S angular-ts-decorators`
23 |
24 | Dependencies: `tslib` and `reflect-metadata`
25 | Peer dependencies: `"angular": ">=1.5.0"`
26 |
27 | ## Available decorators
28 |
29 | | Decorator | angularjs analog | Details |
30 | |:------------- |:------------------------------------------|:----------|
31 | | @NgModule | angular.module | |
32 | | @Injectable | angular.service | |
33 | | @Inject | --- | see [@Inject](#inject) for details |
34 | | @Component | angular.component | |
35 | | @Input | angular.component options binding ('<') | can be used only inside @Component decorator
default input binding value can be overridden by passing parameter to the decorator |
36 | | @Output | angular.component options binding ('&') | can be used only inside @Component decorator |
37 | | @ViewParent | angular.component options require | pass controller name with syntax according to angularjs require spec |
38 | | @HostListener | --- | see [@HostListener](#hostlistener) for details |
39 | | @ViewChild(ren) | --- | see [@ViewChild](#viewchild) for details |
40 | | @Directive | angular.directive | |
41 | | @Pipe | angular.filter | |
42 |
43 | ## Usage with examples
44 |
45 | Let's say we have a todo-form component from classical todo example with the following template
46 | ```html
47 | /* ----- todo/todo-form/todo-form.html ----- */
48 |
52 | ```
53 | If we were writing in plain es6/typescript without decorators we'd define this component like this:
54 | ```js
55 | /* ----- todo/todo-form/todo-form.component.js ----- */
56 | const templateUrl = require('./todo-form.html');
57 |
58 | export const TodoFormComponent = {
59 | bindings: {
60 | todo: '<',
61 | onAddTodo: '&'
62 | },
63 | templateUrl,
64 | controller: class TodoFormComponent {
65 | todo;
66 | onAddTodo;
67 |
68 | $onChanges(changes) {
69 | if (changes.todo) {
70 | this.todo = Object.assign({}, this.todo);
71 | }
72 | }
73 | onSubmit() {
74 | if (!this.todo.title) return;
75 | this.onAddTodo({
76 | $event: {
77 | todo: this.todo
78 | }
79 | });
80 | }
81 | }
82 | };
83 | ```
84 | And then we'll register our component with angular like so:
85 | ```js
86 | import angular from 'angular';
87 | import { TodoFormComponent } from './todo-form.component';
88 |
89 | export const TodoFormModule = angular
90 | .module('todo.form', [])
91 | .component('todoForm', TodoFormComponent)
92 | .name;
93 | ```
94 |
95 | Using `angular-ts-decorators` decorators in typescript the component code will look like this
96 | ```js
97 | /* ----- todo/todo-form/todo-form.component.ts ----- */
98 | import { Component, Input, Output } from 'angular-ts-decorators';
99 |
100 | const templateUrl = require('./todo-form.html');
101 |
102 | @Component({
103 | selector: 'todoForm',
104 | templateUrl
105 | })
106 | export class TodoFormComponent implements OnChanges {
107 | @Input() todo;
108 | @Output() onAddTodo;
109 |
110 | ngOnChanges(changes) {
111 | if (changes.todo) {
112 | this.todo = {...this.todo};
113 | }
114 | }
115 | onSubmit() {
116 | if (!this.todo.title) return;
117 | this.onAddTodo({
118 | $event: {
119 | todo: this.todo
120 | }
121 | });
122 | }
123 | }
124 | ```
125 | > Notice how @Input and @Output decorators replace bindings of the
126 | component, by default @Input correlates to '<' value of the binding
127 | and @Output - to the '&' value, you can override bindings values
128 | only in @Input decorator by passing '=' or '@' if you need to.
129 |
130 | And we'll register it with angular like so:
131 | ```js
132 | /* ----- todo/todo-form/todo-form.module.ts ----- */
133 | import { NgModule } from 'angular-ts-decorators';
134 | import { TodoFormComponent } from './todo-form.component';
135 |
136 | @NgModule({
137 | declarations: [TodoFormComponent]
138 | })
139 | export class TodoFormModule {}
140 | ```
141 | > You should declare all of the components (@Component), directives (@Directive) and filters (@Pipe)
142 | you want to register with some module in `declarations`
143 | of @NgModule decorator, all of the services (@Injectable) and providers (also @Injectable with $get method) you
144 | should declare as `providers` of @NgModule decorator, and all of the modules your
145 | module depends on in `imports`. Name of the class decorated
146 | with @NgModule is the name of the module you should provide in
147 | `imports` of other module declaration that depends on this module.
148 | In addition you can define config and run blocks for your module
149 | by adding config and run methods to your module class declaration.
150 |
151 | Here's an example of service using @Injectable decorator
152 | ```js
153 | /* ----- greeting/greeting.service.ts ----- */
154 | import { Injectable } from 'angular-ts-decorators';
155 |
156 | @Injectable()
157 | export class GreetingService {
158 | private greeting = 'Hello World!';
159 |
160 | // Configuration function
161 | public setGreeting(greeting: string) {
162 | this.greeting = greeting;
163 | }
164 | }
165 | ```
166 |
167 | Services, factories and constants can be registered using Angular 2 syntax by providing an array of provider objects. The provider object has a ```provide``` property (string token), and a ```useClass```, ```useFactory```, or ```useValue``` property to use as the provided value.
168 |
169 | This is how angular filter looks like using angular 2 style @Pipe decorator:
170 | ```js
171 | /* ----- greeting/uppercase.filter.ts ----- */
172 | import { Pipe, PipeTransform } from 'angular-ts-decorators';
173 |
174 | @Pipe({name: 'uppercase'})
175 | export class UppercasePipe implements PipeTransform {
176 | public transform(item: string) {
177 | return item.toUpperCase();
178 | }
179 | }
180 | ```
181 | >Please note, that using @Pipe decorator you can register only stateless filters, for stateful filters you need to fallback to original angularjs filter declaration
182 |
183 | And here's an example of provider registration with @NgModule decorator, its configuration in config method of module class and it's usage in run method:
184 | ```js
185 | import { NgModule } from 'angular-ts-decorators';
186 | import { TodoFormModule } from 'todo/todo-form/todo-form.module';
187 | import { GreetingService } from 'greeting/greeting.service';
188 | import { UppercasePipe } from 'greeting/uppercase.filter';
189 |
190 | @NgModule({
191 | id: 'AppModule',
192 | imports: [
193 | TodoFormModule
194 | ],
195 | declarations: [UppercasePipe],
196 | providers: [
197 | GreetingService, // you can register this way only if you provide class name to @Injectable decorator
198 | {provide: 'GreetingService', useClass: GreetingService},
199 | {provide: 'GreetingServiceFactory', useFactory: () => new GreetingService()}
200 | ]
201 | })
202 | export class AppModule {
203 | static config($compileProvider: ng.ICompileProvider) {
204 | 'ngInject';
205 | $compileProvider.debugInfoEnabled(false);
206 | }
207 |
208 | static run(GreetingService: GreetingService) {
209 | 'ngInject';
210 | console.log(GreetingService.getGreeting());
211 | }
212 | }
213 | ```
214 | >Please notice, that you can't define constructor and $inject
215 | anything into it, instead you need to specify all of the injections you
216 | want to provide for your module config and run blocks using 'ngInject' comment inside those static methods respectively.
217 |
218 | ## HostListener
219 |
220 | @HostListener is a special method decorator introduced in angular 2, see [official docs](https://angular.io/docs/ts/latest/guide/style-guide.html#!#directives)
221 | >Please notice, that this feature is kind of experimental, because the way it's implemented is kind of hacky: classes that have @HostListener methods are replaced with a new class that extends the original class. It works with basic use cases, but there could be some implications in some edge cases, so be aware.
222 |
223 | Usage:
224 | ```js
225 | import { HostListener } from 'angular-ts-decorators';
226 |
227 | export class MyDirective {
228 | @HostListener('click mouseover')
229 | onClick() {
230 | console.log('click');
231 | }
232 | }
233 | ```
234 | The implementation of it in angularjs as follows, it injects $element into component constructor and attaches method decorated with @HostListener as event handler on $element in $postLink and dettaches it in $onDestroy:
235 | ```js
236 |
237 | export class MyDirective {
238 | constructor(private $element: ng.IAugmentedJQuery) {}
239 |
240 | $postLink() {
241 | this.$element.on('click mouseover', this.onClick.bind(this));
242 | }
243 |
244 | $onDestroy() {
245 | this.$element.off('click mouseover', this.onClick);
246 | }
247 |
248 | onClick() {
249 | console.log('click');
250 | }
251 | }
252 | ```
253 |
254 | ## ViewChild
255 |
256 | @ViewChild and @ViewChildren are property decorators introduced in angular 2, see [official docs](https://angular.io/api/core/ViewChild)
257 |
258 | Usage is more or less the same as in official docs, but it doesn't support template variables obviously (cause they don't exist in angularjs).
259 | When provided selector is Component/Directive's type or selector, it's controller class is returned, if other css selector is provided - jqlite object is returned.
260 |
261 | >Please notice, that this feature is kind of experimental, because the way it's implemented is kind of hacky: classes that have @ViewChild properties are replaced with a new class that extends the original class. It works with basic use cases, but there could be some implications in some edge cases, so be aware.
262 |
263 | ## Inject
264 |
265 | @Inject decorator allows to inject providers under a different name, for example if you have a provider like this:
266 | ```js
267 | @Injectable('My.Service')
268 | export class MyService {}
269 | ```
270 | You can use it like this:
271 | ```js
272 | export class MyController {
273 | constructor(@Inject('My.Service') service: MyService) {}
274 | }
275 | ```
276 | >Please notice that this decorator relies on explicit annotations either using static $inject property or using tools like ngAnnotate
277 |
278 | ## Bootstraping angularjs application the angular way
279 |
280 | In angularjs the manual boostrap would look like this
281 | ```
282 | angular.element(document).ready(() => {
283 | angular.bootstrap(document, ['AppModule']);
284 | });
285 | ```
286 | With `angular-ts-decorators` you can bootstrap your application using angular syntax
287 |
288 | If your main module is a class decorated with NgModule metadata, you can bootstrap it like so:
289 | ```
290 | platformBrowserDynamic().bootstrapModule(AppModule);
291 | ```
292 | If your main module is registered using angularjs syntax exporting the module itself like so:
293 | ```
294 | export const appModule = angular.module('AppModule', [(SomeModule as NgModule).module.name]);
295 | ```
296 | or exporting only module name like so:
297 | ```
298 | export const appModule = angular.module('AppModule', [(SomeModule as NgModule).module.name]).name;
299 | ```
300 | Then you can bootstrap it by name like so:
301 | ```
302 | platformBrowserDynamic().bootstrapModule(appModule);
303 | ```
304 | or like so
305 | ```
306 | platformBrowserDynamic().bootstrapModule('AppModule');
307 | ```
308 | >By default angularjs adds automatic function annotation for the application, you can override it by passing `{ strictDi: true }` as the second argument to bootstrapModule
309 |
--------------------------------------------------------------------------------
/karma.conf.ts:
--------------------------------------------------------------------------------
1 | module.exports = (config) => {
2 | config.set({
3 | frameworks: ['jasmine', 'karma-typescript'],
4 | customLaunchers: {
5 | chromeTravisCI: {
6 | base: 'Chrome',
7 | flags: ['--no-sandbox']
8 | }
9 | },
10 | files: [
11 | 'src/**/*.ts',
12 | 'test/**/*.ts',
13 | ],
14 | preprocessors: {
15 | 'src/**/*.ts': ['karma-typescript', 'coverage'],
16 | 'test/**/*.ts': ['karma-typescript'],
17 | },
18 | reporters: ['dots', 'coverage'],
19 | coverageReporter: {
20 | reporters: [
21 | // { type: 'html', subdir: 'report-html' },
22 | { type: 'lcovonly', subdir: '.', file: 'lcov.info' }
23 | ]
24 | },
25 | colors: true,
26 | browsers: ['Chrome'],
27 | logLevel: config.INFO
28 | });
29 |
30 | if (process.env.TRAVIS) {
31 | config.browsers = ['chromeTravisCI'];
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ts-decorators",
3 | "version": "3.7.8",
4 | "license": "MIT",
5 | "author": {
6 | "name": "Vlad Sternbach",
7 | "email": "vlad.sternbach@gmail.com",
8 | "url": "https://github.com/vsternbach"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/vsternbach/angular-ts-decorators.git"
13 | },
14 | "homepage": "https://github.com/vsternbach/angular-ts-decorators",
15 | "keywords": [
16 | "angular decorators",
17 | "angular-decorators",
18 | "typescript decorators",
19 | "typescript-decorators",
20 | "decorators",
21 | "angular ts decorators",
22 | "angular-ts-decorators",
23 | "angular typescript decorators",
24 | "angular-typescript-decorators",
25 | "anguarjs decorators",
26 | "anguarjs-decorators"
27 | ],
28 | "main": "angular-ts-decorators.umd.js",
29 | "module": "angular-ts-decorators.js",
30 | "jsnext:main": "angular-ts-decorators.js",
31 | "types": "types/index.d.ts",
32 | "dependencies": {
33 | "reflect-metadata": "0.1.13",
34 | "tslib": "1.10.0"
35 | },
36 | "devDependencies": {
37 | "@types/angular": "^1.6.54",
38 | "@types/jasmine": "^3.3.12",
39 | "@types/node": "^12.0.2",
40 | "angular": "^1.7.8",
41 | "angular-mocks": "^1.7.8",
42 | "copyfiles": "^2.1.0",
43 | "coveralls": "^3.0.3",
44 | "jasmine-core": "^3.4.0",
45 | "karma": "^4.1.0",
46 | "karma-chrome-launcher": "^2.2.0",
47 | "karma-coverage": "^1.1.2",
48 | "karma-jasmine": "^2.0.1",
49 | "karma-typescript": "^4.0.0",
50 | "rimraf": "^2.6.3",
51 | "rollup": "^1.12.3",
52 | "standard-version": "^6.0.1",
53 | "tslint": "^5.16.0",
54 | "typescript": "^3.4.5",
55 | "uglify-es": "^3.3.9"
56 | },
57 | "peerDependencies": {
58 | "angular": ">=1.5.0",
59 | "typescript": ">=2.2.0"
60 | },
61 | "scripts": {
62 | "clean": "rimraf dist coverage",
63 | "test": "karma start --single-run",
64 | "test:dev": "karma start",
65 | "posttest": "cat ./coverage/lcov.info | coveralls",
66 | "build": "npm run clean && tsc && rollup -c && npm run uglify && copyfiles package.json README.md CHANGELOG.md LICENSE dist/ && rimraf dist/temp",
67 | "uglify": "for f in dist/*.js; do ./node_modules/uglify-es/bin/uglifyjs $f --compress drop_console --mangle --output ${f%.js}.min.js; done",
68 | "release": "standard-version",
69 | "postrelease": "git push origin master --follow-tags"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | const pkg = require('./package.json');
2 | const external = [...Object.keys(pkg.dependencies), ...Object.keys(pkg.peerDependencies)];
3 |
4 | export default {
5 | input: 'dist/temp/index.js',
6 | output: [
7 | {
8 | file: 'dist/' + pkg.main,
9 | format: 'umd',
10 | exports: 'named',
11 | name: pkg.name,
12 | sourceMap: true
13 | },
14 | {
15 | file: 'dist/' + pkg.module,
16 | format: 'es',
17 | sourceMap: true
18 | }
19 | ],
20 | globals: {
21 | angular: 'angular',
22 | tslib: 'tslib'
23 | },
24 | external,
25 | plugins: []
26 | };
27 |
--------------------------------------------------------------------------------
/src/bootstrap.ts:
--------------------------------------------------------------------------------
1 | import { bootstrap, element, IModule } from 'angular';
2 | import { NgModule } from './module';
3 |
4 | export interface CompilerOptions {
5 | strictDi?: boolean;
6 | }
7 |
8 | export const platformBrowserDynamic = () => PlatformRef;
9 |
10 | export class PlatformRef {
11 | static bootstrapModule(moduleType: NgModule|IModule|string, compilerOptions: CompilerOptions = { strictDi: false }) {
12 | let moduleName;
13 | switch (typeof moduleType) {
14 | case 'string': // module name string
15 | moduleName = moduleType;
16 | break;
17 | case 'object': // angular.module object
18 | moduleName = (moduleType as IModule).name;
19 | break;
20 | case 'function': // NgModule class
21 | default:
22 | const module = (moduleType as NgModule).module;
23 | if (!module) {
24 | throw Error('Argument moduleType should be NgModule class, angular.module object or module name string');
25 | }
26 | moduleName = module.name;
27 | }
28 | const strictDi = (compilerOptions.strictDi === true);
29 | element(document).ready(() => {
30 | bootstrap(document.body, [moduleName], { strictDi });
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/component.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import { ElementRef } from './element_ref';
3 | import {
4 | camelToKebab,
5 | Declaration, defineMetadata, getAttributeName, getMetadata, getTypeDeclaration, getTypeName, isAttributeSelector,
6 | kebabToCamel,
7 | metadataKeys
8 | } from './utils';
9 | import { IHostListeners } from './hostListener';
10 | import { IViewChildren } from './viewChild';
11 | import { ngLifecycleHooksMap } from './lifecycle_hooks';
12 | import { isFunction, IControllerConstructor, IDirective, IModule, IComponentController,
13 | IComponentOptions } from 'angular';
14 |
15 | export interface ComponentOptionsDecorated extends IComponentOptions {
16 | selector: string;
17 | styles?: any[];
18 | restrict?: string;
19 | replace?: boolean;
20 | }
21 |
22 | export function Component({selector, ...options}: ComponentOptionsDecorated) {
23 | return (ctrl: IControllerConstructor) => {
24 | options.controller = ctrl;
25 | const isAttrSelector = isAttributeSelector(selector);
26 | const bindings = getMetadata(metadataKeys.bindings, ctrl);
27 | if (bindings) {
28 | if (isAttrSelector) {
29 | options['bindToController'] = bindings;
30 | options['controllerAs'] = options['controllerAs'] || '$ctrl';
31 | }
32 | else options['bindings'] = bindings;
33 | }
34 |
35 | const require = getMetadata(metadataKeys.require, ctrl);
36 | if (require) {
37 | options.require = require;
38 | }
39 |
40 | if (isAttrSelector) {
41 | (options as IDirective).restrict = 'A';
42 | }
43 |
44 | replaceLifecycleHooks(ctrl);
45 |
46 | const selectorName = isAttrSelector ? getAttributeName(selector) : selector;
47 | defineMetadata(metadataKeys.name, kebabToCamel(selectorName), ctrl);
48 | defineMetadata(metadataKeys.declaration, isAttrSelector ? Declaration.Directive : Declaration.Component, ctrl);
49 | defineMetadata(metadataKeys.options, options, ctrl);
50 | };
51 | }
52 |
53 | /** @internal */
54 | export function registerComponent(module: IModule, component: IComponentController) {
55 | const name = getMetadata(metadataKeys.name, component);
56 | const options = getMetadata(metadataKeys.options, component);
57 | const listeners: IHostListeners = getMetadata(metadataKeys.listeners, options.controller);
58 | const viewChildren: IViewChildren = getMetadata(metadataKeys.viewChildren, component);
59 | if (listeners || viewChildren) {
60 | options.controller = extendWithHostListenersAndChildren(options.controller, listeners, viewChildren);
61 | }
62 | module.component(name, options);
63 | }
64 |
65 | /** @internal */
66 | export function extendWithHostListenersAndChildren(ctrl: {new(...args: any[])},
67 | listeners: IHostListeners = {},
68 | viewChildren: IViewChildren = {}) {
69 | const handlers = Object.keys(listeners);
70 | const namespace = '.HostListener';
71 | const properties = Object.keys(viewChildren);
72 |
73 | class NewCtrl extends ctrl {
74 | constructor(private $element, ...args: any[]) {
75 | super(...args);
76 | }
77 | private _updateViewChildren() {
78 | properties.forEach(property => {
79 | const child = viewChildren[property];
80 | let selector: string;
81 | if (typeof child.selector !== 'string') {
82 | const type = getTypeDeclaration(child.selector);
83 | if (type !== Declaration.Component && type !== Declaration.Directive) {
84 | console.error(`No valid selector was provided for ViewChild${child.first ? '' :
85 | 'ren'} decorator, it should be type or selector of component/directive`);
86 | return;
87 | }
88 | selector = camelToKebab(getTypeName(child.selector));
89 | } else selector = `#${child.selector}`;
90 |
91 | const viewChildEls = Array.prototype.slice.call(this.$element[0].querySelectorAll(selector))
92 | .map((viewChild: Element) => {
93 | // if ViewChild selector is type use selector derived from type
94 | // otherwise (i.e. id of the element), get it's element name (localName)
95 | const componentName = typeof child.selector === 'string' ? viewChild.localName : selector;
96 | const el = angular.element(viewChild);
97 | const $ctrl = el && el.controller(kebabToCamel(componentName));
98 | return child.read ? new ElementRef(el) : ($ctrl || new ElementRef(el));
99 | })
100 | .filter(el => !!el);
101 |
102 | if (viewChildEls.length) {
103 | this[property] = child.first ? viewChildEls[0] : viewChildEls;
104 | }
105 | else {
106 | this[property] = undefined;
107 | }
108 | });
109 | }
110 | $postLink() {
111 | if (super.$postLink) {
112 | super.$postLink();
113 | }
114 | handlers.forEach(handler => {
115 | const { eventName } = listeners[handler];
116 | this.$element.on(eventName + namespace, this[handler].bind(this));
117 | });
118 | this._updateViewChildren();
119 | }
120 | $onChanges(changes) {
121 | if (super.$onChanges) {
122 | super.$onChanges(changes);
123 | }
124 | this._updateViewChildren();
125 | }
126 | $onDestroy() {
127 | if (super.$onDestroy) {
128 | super.$onDestroy();
129 | }
130 | if (handlers.length) {
131 | this.$element.off(namespace);
132 | }
133 | }
134 | }
135 | NewCtrl.$inject = ['$element', ...ctrl.$inject || []];
136 | return NewCtrl;
137 | }
138 |
139 | /** @internal */
140 | export function replaceLifecycleHooks(ctrl: IControllerConstructor) {
141 | const ctrlClass = ctrl.prototype;
142 | const ngHooksFound = getHooksOnCtrlClass(ctrlClass);
143 |
144 | ngHooksFound.forEach((ngHook: string) => {
145 | const angularJsHook: string = ngLifecycleHooksMap[ngHook];
146 | ctrlClass[angularJsHook] = ctrlClass[ngHook];
147 | });
148 | }
149 |
150 | /** @internal */
151 | function getHooksOnCtrlClass(ctrlClass: any): string[] {
152 | return Object.keys(ngLifecycleHooksMap)
153 | .filter((hook: string) => isFunction(ctrlClass[hook]));
154 | }
155 |
--------------------------------------------------------------------------------
/src/directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Declaration, defineMetadata, getAttributeName, getMetadata, isAttributeSelector, kebabToCamel,
3 | metadataKeys
4 | } from './utils';
5 | import { IHostListeners } from './hostListener';
6 | import { IViewChildren } from './viewChild';
7 | import { extendWithHostListenersAndChildren, replaceLifecycleHooks } from './component';
8 | import { IController, IDirective, IModule } from 'angular';
9 |
10 | export interface DirectiveOptionsDecorated extends IDirective {
11 | selector: string;
12 | }
13 |
14 | export interface DirectiveControllerConstructor {
15 | new (...args: any[]): IController;
16 | }
17 |
18 | export function Directive({selector, ...options}: DirectiveOptionsDecorated) {
19 | return (ctrl: DirectiveControllerConstructor) => {
20 | const bindings = getMetadata(metadataKeys.bindings, ctrl);
21 | if (bindings) {
22 | options.bindToController = bindings;
23 | }
24 | const require = getMetadata(metadataKeys.require, ctrl);
25 | if (require) {
26 | options.require = require;
27 | if (!options.bindToController) options.bindToController = true;
28 | }
29 | options.restrict = options.restrict || 'A';
30 |
31 | const selectorName = isAttributeSelector(selector) ? getAttributeName(selector) : selector;
32 | defineMetadata(metadataKeys.name, kebabToCamel(selectorName), ctrl);
33 | defineMetadata(metadataKeys.declaration, Declaration.Directive, ctrl);
34 | defineMetadata(metadataKeys.options, options, ctrl);
35 | };
36 | }
37 |
38 | /** @internal */
39 | export function registerDirective(module: IModule, ctrl: DirectiveControllerConstructor) {
40 | let directiveFunc;
41 | const name = getMetadata(metadataKeys.name, ctrl);
42 | const options = getMetadata(metadataKeys.options, ctrl);
43 | replaceLifecycleHooks(ctrl);
44 | const listeners: IHostListeners = getMetadata(metadataKeys.listeners, ctrl);
45 | const viewChildren: IViewChildren = getMetadata(metadataKeys.viewChildren, ctrl);
46 | options.controller = listeners || viewChildren ?
47 | extendWithHostListenersAndChildren(ctrl, listeners, viewChildren) : ctrl;
48 | directiveFunc = () => options;
49 | module.directive(name, directiveFunc);
50 | }
51 |
--------------------------------------------------------------------------------
/src/element_ref.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright Google Inc. All Rights Reserved.
4 | *
5 | * Use of this source code is governed by an MIT-style license that can be
6 | * found in the LICENSE file at https://angular.io/license
7 | */
8 |
9 | /**
10 | * A wrapper around a native element inside of a View.
11 | * @stable
12 | */
13 | export class ElementRef {
14 |
15 | public nativeElement: HTMLElement;
16 |
17 | constructor($element: JQuery) {
18 | $element['nativeElement'] = $element[0];
19 | return $element as ElementRef;
20 | }
21 | }
22 |
23 | export interface ElementRef extends JQuery {
24 | nativeElement: HTMLElement;
25 | }
26 |
--------------------------------------------------------------------------------
/src/hostListener.ts:
--------------------------------------------------------------------------------
1 | import { defineMetadata, getMetadata, metadataKeys } from './utils';
2 |
3 | /** @internal */
4 | export interface IHostListeners {
5 | [handler: string]: {
6 | eventName: string;
7 | args: string[];
8 | };
9 | }
10 |
11 | export function HostListener(eventName?: string, args?: string[]) {
12 | return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
13 | const listener = descriptor.value;
14 |
15 | if (typeof listener !== 'function') {
16 | throw new Error(`@HostListener decorator can only be applied to methods not: ${typeof listener}`);
17 | }
18 |
19 | const targetConstructor = target.constructor;
20 | /**
21 | * listeners = { onMouseEnter: { eventName: 'mouseenter mouseover', args: [] } }
22 | */
23 | const listeners: IHostListeners = getMetadata(metadataKeys.listeners, targetConstructor) || {};
24 | listeners[propertyKey] = { eventName, args };
25 | defineMetadata(metadataKeys.listeners, listeners, targetConstructor);
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { platformBrowserDynamic } from './bootstrap';
2 | export { Component } from './component';
3 | export { Directive } from './directive';
4 | export { Injectable, Inject } from './injectable';
5 | export { Pipe, PipeTransform } from './pipe';
6 | export { Input, Output, ViewParent } from './input';
7 | export { NgModule, ModuleConfig } from './module';
8 | export { HostListener } from './hostListener';
9 | export { Provider } from './provider';
10 | export { ViewChild, ViewChildren } from './viewChild';
11 | export { ElementRef } from './element_ref';
12 | export * from './lifecycle_hooks';
13 | export * from './type';
14 | export * from './utils';
15 |
--------------------------------------------------------------------------------
/src/injectable.ts:
--------------------------------------------------------------------------------
1 | import { defineMetadata, getMetadata, metadataKeys } from './utils';
2 | import { Provider } from './provider';
3 | import { IModule } from 'angular';
4 |
5 | export function Injectable(name?: string) {
6 | return (Class: any) => {
7 | if (name) {
8 | defineMetadata(metadataKeys.name, name, Class);
9 | }
10 | };
11 | }
12 |
13 | export function Inject(name: string) {
14 | return (target: any, propertyKey: string, parameterIndex: number) => {
15 | // if @Inject decorator is on target's method
16 | if (propertyKey && Array.isArray(target[propertyKey])) {
17 | target[propertyKey][parameterIndex] = name;
18 | return; // exit, don't change injection on target's constructor
19 | }
20 | // if @Inject decorator is on target's constructor
21 | if (target.$inject) {
22 | target.$inject[parameterIndex] = name;
23 | } else {
24 | console.error(`Annotations should be provided as static $inject property in order to use @Inject decorator`);
25 | }
26 | };
27 | }
28 |
29 | /** @internal */
30 | export function registerProviders(module: IModule, providers: Provider[]) {
31 | providers.forEach((provider: any) => {
32 | // providers registered using { provide, useClass/useFactory/useValue } syntax
33 | if (provider.provide) {
34 | const name = provider.provide;
35 | if (provider.useClass && provider.useClass instanceof Function) {
36 | module.service(name, provider.useClass);
37 | }
38 | else if (provider.useFactory && provider.useFactory instanceof Function) {
39 | provider.useFactory.$inject = provider.deps || provider.useFactory.$inject;
40 | module.factory(name, provider.useFactory);
41 | }
42 | else if (provider.useValue) {
43 | module.constant(name, provider.useValue);
44 | }
45 | }
46 | // providers registered as classes
47 | else {
48 | const name = getMetadata(metadataKeys.name, provider);
49 | if (!name) {
50 | console.error(`${provider.name} was not registered as angular service:
51 | Provide explicit name in @Injectable when using class syntax or register it using object provider syntax:
52 | { provide: '${provider.name}', useClass: ${provider.name} }`);
53 | } else {
54 | module.service(name, provider);
55 | }
56 | }
57 | });
58 | }
59 |
--------------------------------------------------------------------------------
/src/input.ts:
--------------------------------------------------------------------------------
1 | import { defineMetadata, getMetadata, metadataKeys } from './utils';
2 |
3 | export function Input(alias?: string) {
4 | return (target: any, key: string) => addBindingToMetadata(target, key, '', alias);
5 | }
6 |
7 | export function Output(alias?: string) {
8 | return (target: any, key: string) => addBindingToMetadata(target, key, '&', alias);
9 | }
10 |
11 | export function ViewParent(controller: string) {
12 | return (target: any, key: string) => addRequireToMetadata(target, key, controller);
13 | }
14 |
15 | /** @internal */
16 | function addBindingToMetadata(target: any, key: string, direction: string, alias?: string) {
17 | const targetConstructor = target.constructor;
18 | const bindings = getMetadata(metadataKeys.bindings, targetConstructor) || {};
19 | bindings[key] = alias || direction;
20 | defineMetadata(metadataKeys.bindings, bindings, targetConstructor);
21 | }
22 |
23 | /** @internal */
24 | function addRequireToMetadata(target: any, key: string, controller: string) {
25 | const targetConstructor = target.constructor;
26 | const require = getMetadata(metadataKeys.require, targetConstructor) || {};
27 | require[key] = controller;
28 | defineMetadata(metadataKeys.require, require, targetConstructor);
29 | }
30 |
--------------------------------------------------------------------------------
/src/lifecycle_hooks.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @internal
3 | * @desc Mapping between angular and angularjs LifecycleHooks
4 | */
5 | export const ngLifecycleHooksMap: object = {
6 | ngOnInit: '$onInit',
7 | ngOnDestroy: '$onDestroy',
8 | ngDoCheck: '$doCheck',
9 | ngOnChanges: '$onChanges',
10 | ngAfterViewInit: '$postLink'
11 | };
12 |
13 | /**
14 | * Represents a basic change from a previous to a new value using generic type
15 | * @stable
16 | */
17 | export interface SimpleChange {
18 | previousValue: T;
19 | currentValue: T;
20 | isFirstChange(): boolean;
21 | }
22 |
23 | /**
24 | * A `changes` object whose keys are property names and
25 | * values are instances of {@link SimpleChange}. See {@link OnChanges}
26 | * taken from angular: https://github.com/angular/angular/issues/17560#issuecomment-314678911
27 | * @stable
28 | */
29 | export type SimpleChanges = {
30 | [P in keyof C]: SimpleChange;
31 | };
32 |
33 | /**
34 | * @whatItDoes Lifecycle hook that is called when any data-bound property of a directive changes.
35 | * @howToUse
36 | * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='OnChanges'}
37 | *
38 | * @description
39 | * `ngOnChanges` is called right after the data-bound properties have been checked and before view
40 | * and content children are checked if at least one of them has changed.
41 | * The `changes` parameter contains the changed properties.
42 | *
43 | * See {@linkDocs guide/lifecycle-hooks#onchanges "Lifecycle Hooks Guide"}.
44 | *
45 | * @stable
46 | */
47 | export interface OnChanges { ngOnChanges(changes: SimpleChanges): void; }
48 |
49 | /**
50 | * @whatItDoes Lifecycle hook that is called after data-bound properties of a directive are
51 | * initialized.
52 | * @howToUse
53 | * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='OnInit'}
54 | *
55 | * @description
56 | * `ngOnInit` is called right after the directive's data-bound properties have been checked for the
57 | * first time, and before any of its children have been checked. It is invoked only once when the
58 | * directive is instantiated.
59 | *
60 | * See {@linkDocs guide/lifecycle-hooks "Lifecycle Hooks Guide"}.
61 | *
62 | * @stable
63 | */
64 | export interface OnInit { ngOnInit(): void; }
65 |
66 | /**
67 | * @whatItDoes Lifecycle hook that is called when Angular dirty checks a directive.
68 | * @howToUse
69 | * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='DoCheck'}
70 | *
71 | * @description
72 | * `ngDoCheck` gets called to check the changes in the directives in addition to the default
73 | * algorithm. The default change detection algorithm looks for differences by comparing
74 | * bound-property values by reference across change detection runs.
75 | *
76 | * Note that a directive typically should not use both `DoCheck` and {@link OnChanges} to respond to
77 | * changes on the same input, as `ngOnChanges` will continue to be called when the default change
78 | * detector detects changes.
79 | *
80 | * See {@link KeyValueDiffers} and {@link IterableDiffers} for implementing custom dirty checking
81 | * for collections.
82 | *
83 | * See {@linkDocs guide/lifecycle-hooks#docheck "Lifecycle Hooks Guide"}.
84 | *
85 | * @stable
86 | */
87 | export interface DoCheck { ngDoCheck(): void; }
88 |
89 | /**
90 | * @whatItDoes Lifecycle hook that is called when a directive, pipe or service is destroyed.
91 | * @howToUse
92 | * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='OnDestroy'}
93 | *
94 | * @description
95 | * `ngOnDestroy` callback is typically used for any custom cleanup that needs to occur when the
96 | * instance is destroyed.
97 | *
98 | * See {@linkDocs guide/lifecycle-hooks "Lifecycle Hooks Guide"}.
99 | *
100 | * @stable
101 | */
102 | export interface OnDestroy { ngOnDestroy(): void; }
103 |
104 | /**
105 | * @whatItDoes Lifecycle hook that is called after a component's view has been fully
106 | * initialized.
107 | * @howToUse
108 | * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='AfterViewInit'}
109 | *
110 | * @description
111 | * See {@linkDocs guide/lifecycle-hooks#afterview "Lifecycle Hooks Guide"}.
112 | *
113 | * @stable
114 | */
115 | export interface AfterViewInit { ngAfterViewInit(): void; }
116 |
--------------------------------------------------------------------------------
/src/module.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import { PipeTransform, registerPipe } from './pipe';
3 | import { registerProviders } from './injectable';
4 | import { camelToKebab, Declaration, getMetadata, getTypeName, metadataKeys } from './utils';
5 | import { registerComponent } from './component';
6 | import { registerDirective } from './directive';
7 | import { Provider } from './provider';
8 | import { IComponentController, IDirectiveFactory, IModule, Injectable } from 'angular';
9 |
10 | export interface ModuleConfig {
11 | id?: string;
12 | declarations?: Array | PipeTransform>;
13 | imports?: Array;
14 | exports?: Function[];
15 | providers?: Provider[];
16 | bootstrap?: IComponentController[];
17 | }
18 |
19 | export interface NgModule {
20 | module?: IModule;
21 | config?(...args: any[]): any;
22 | run?(...args: any[]): any;
23 | [p: string]: any;
24 | }
25 |
26 | export function NgModule({ id, bootstrap = [], declarations = [], imports = [], providers = [] }: ModuleConfig) {
27 | return (Class: NgModule) => {
28 | // module registration
29 | const deps = imports.map(mod => typeof mod === 'string' ? mod : mod.module.name);
30 | if (!id) {
31 | console.warn('You are not providing ngModule id, be careful this code won\'t work when uglified.');
32 | id = (Class as any).name;
33 | }
34 | const module = angular.module(id, deps);
35 |
36 | // components, directives and filters registration
37 | declarations.forEach((declaration: any) => {
38 | const declarationType = getMetadata(metadataKeys.declaration, declaration);
39 | switch (declarationType) {
40 | case Declaration.Component:
41 | registerComponent(module, declaration);
42 | break;
43 | case Declaration.Directive:
44 | registerDirective(module, declaration);
45 | break;
46 | case Declaration.Pipe:
47 | registerPipe(module, declaration);
48 | break;
49 | default:
50 | console.error(
51 | `Can't find type metadata on ${declaration.name} declaration, did you forget to decorate it?
52 | Decorate your declarations using @Component, @Directive or @Pipe decorator.`
53 | );
54 | }
55 | });
56 |
57 | // services registration
58 | if (providers) {
59 | registerProviders(module, providers);
60 | }
61 | // config and run blocks registration
62 | const { config, run } = Class;
63 | if (config) {
64 | module.config(config);
65 | }
66 | if (run) {
67 | module.run(run);
68 | }
69 |
70 | // expose angular module as static property
71 | Class.module = module;
72 | };
73 | }
74 |
--------------------------------------------------------------------------------
/src/pipe.ts:
--------------------------------------------------------------------------------
1 | import { Declaration, defineMetadata, getMetadata, metadataKeys } from './utils';
2 | import { IModule } from 'angular';
3 |
4 | export interface PipeTransformConstructor {
5 | new(...args: any[]): PipeTransform;
6 | }
7 |
8 | export interface PipeTransform {
9 | transform(...args: any[]): any;
10 | }
11 |
12 | export function Pipe(options: {name: string}) {
13 | return (Class: PipeTransformConstructor) => {
14 | defineMetadata(metadataKeys.name, options.name, Class);
15 | defineMetadata(metadataKeys.declaration, Declaration.Pipe, Class);
16 | };
17 | }
18 |
19 | /** @internal */
20 | export function registerPipe(module: IModule, filter: PipeTransformConstructor) {
21 | const name = getMetadata(metadataKeys.name, filter);
22 | const filterFactory = (...args: any[]) => {
23 | const injector = args[0]; // reference to $injector
24 | const instance = injector.instantiate(filter);
25 | return instance.transform.bind(instance);
26 | };
27 | filterFactory.$inject = ['$injector', ...filter.$inject || []];
28 | module.filter(name, filterFactory);
29 | }
30 |
--------------------------------------------------------------------------------
/src/provider.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright Google Inc. All Rights Reserved.
4 | *
5 | * Use of this source code is governed by an MIT-style license that can be
6 | * found in the LICENSE file at https://angular.io/license
7 | */
8 |
9 | import {Type} from './type';
10 |
11 | /**
12 | * @whatItDoes Configures the {@link Injector} to return an instance of `Type` when `Type' is used
13 | * as token.
14 | * @howToUse
15 | * ```
16 | * @Injectable()
17 | * class MyService {}
18 | *
19 | * const provider: TypeProvider = MyService;
20 | * ```
21 | *
22 | * @description
23 | *
24 | * Create an instance by invoking the `new` operator and supplying additional arguments.
25 | * This form is a short form of `TypeProvider`;
26 | *
27 | * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
28 | *
29 | * ### Example
30 | *
31 | * {@example core/di/ts/provider_spec.ts region='TypeProvider'}
32 | *
33 | * @stable
34 | */
35 | export interface TypeProvider extends Type {}
36 |
37 | /**
38 | * @whatItDoes Configures the {@link Injector} to return a value for a token.
39 | * @howToUse
40 | * ```
41 | * const provider: ValueProvider = {provide: 'someToken', useValue: 'someValue'};
42 | * ```
43 | *
44 | * @description
45 | * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
46 | *
47 | * ### Example
48 | *
49 | * {@example core/di/ts/provider_spec.ts region='ValueProvider'}
50 | *
51 | * @stable
52 | */
53 | export interface ValueProvider {
54 | /**
55 | * An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
56 | */
57 | provide: any;
58 |
59 | /**
60 | * The value to inject.
61 | */
62 | useValue: any;
63 |
64 | /**
65 | * If true, then injector returns an array of instances. This is useful to allow multiple
66 | * providers spread across many files to provide configuration information to a common token.
67 | *
68 | * ### Example
69 | *
70 | * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
71 | */
72 | multi?: boolean;
73 | }
74 |
75 | /**
76 | * @whatItDoes Configures the {@link Injector} to return an instance of `useClass` for a token.
77 | * @howToUse
78 | * ```
79 | * @Injectable()
80 | * class MyService {}
81 | *
82 | * const provider: ClassProvider = {provide: 'someToken', useClass: MyService};
83 | * ```
84 | *
85 | * @description
86 | * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
87 | *
88 | * ### Example
89 | *
90 | * {@example core/di/ts/provider_spec.ts region='ClassProvider'}
91 | *
92 | * Note that following two providers are not equal:
93 | * {@example core/di/ts/provider_spec.ts region='ClassProviderDifference'}
94 | *
95 | * @stable
96 | */
97 | export interface ClassProvider {
98 | /**
99 | * An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
100 | */
101 | provide: any;
102 |
103 | /**
104 | * Class to instantiate for the `token`.
105 | */
106 | useClass: Type;
107 |
108 | /**
109 | * If true, then injector returns an array of instances. This is useful to allow multiple
110 | * providers spread across many files to provide configuration information to a common token.
111 | *
112 | * ### Example
113 | *
114 | * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
115 | */
116 | multi?: boolean;
117 | }
118 |
119 | /**
120 | * @whatItDoes Configures the {@link Injector} to return a value of another `useExisting` token.
121 | * @howToUse
122 | * ```
123 | * const provider: ExistingProvider = {provide: 'someToken', useExisting: 'someOtherToken'};
124 | * ```
125 | *
126 | * @description
127 | * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
128 | *
129 | * ### Example
130 | *
131 | * {@example core/di/ts/provider_spec.ts region='ExistingProvider'}
132 | *
133 | * @stable
134 | */
135 | export interface ExistingProvider {
136 | /**
137 | * An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
138 | */
139 | provide: any;
140 |
141 | /**
142 | * Existing `token` to return. (equivalent to `injector.get(useExisting)`)
143 | */
144 | useExisting: any;
145 |
146 | /**
147 | * If true, then injector returns an array of instances. This is useful to allow multiple
148 | * providers spread across many files to provide configuration information to a common token.
149 | *
150 | * ### Example
151 | *
152 | * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
153 | */
154 | multi?: boolean;
155 | }
156 |
157 | /**
158 | * @whatItDoes Configures the {@link Injector} to return a value by invoking a `useFactory`
159 | * function.
160 | * @howToUse
161 | * ```
162 | * function serviceFactory() { ... }
163 | *
164 | * const provider: FactoryProvider = {provide: 'someToken', useFactory: serviceFactory, deps: []};
165 | * ```
166 | *
167 | * @description
168 | * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
169 | *
170 | * ### Example
171 | *
172 | * {@example core/di/ts/provider_spec.ts region='FactoryProvider'}
173 | *
174 | * Dependencies can also be marked as optional:
175 | * {@example core/di/ts/provider_spec.ts region='FactoryProviderOptionalDeps'}
176 | *
177 | * @stable
178 | */
179 | export interface FactoryProvider {
180 | /**
181 | * An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
182 | */
183 | provide: any;
184 |
185 | /**
186 | * A function to invoke to create a value for this `token`. The function is invoked with
187 | * resolved values of `token`s in the `deps` field.
188 | */
189 | useFactory: Function;
190 |
191 | /**
192 | * A list of `token`s which need to be resolved by the injector. The list of values is then
193 | * used as arguments to the `useFactory` function.
194 | */
195 | deps?: any[];
196 |
197 | /**
198 | * If true, then injector returns an array of instances. This is useful to allow multiple
199 | * providers spread across many files to provide configuration information to a common token.
200 | *
201 | * ### Example
202 | *
203 | * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
204 | */
205 | multi?: boolean;
206 | }
207 |
208 | /**
209 | * @whatItDoes Describes how the {@link Injector} should be configured.
210 | * @howToUse
211 | * See {@link TypeProvider}, {@link ValueProvider}, {@link ClassProvider}, {@link ExistingProvider},
212 | * {@link FactoryProvider}.
213 | *
214 | * @description
215 | * For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
216 | *
217 | * @stable
218 | */
219 | export type Provider =
220 | TypeProvider | ValueProvider | ClassProvider | ExistingProvider | FactoryProvider | any[];
221 |
--------------------------------------------------------------------------------
/src/type.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright Google Inc. All Rights Reserved.
4 | *
5 | * Use of this source code is governed by an MIT-style license that can be
6 | * found in the LICENSE file at https://angular.io/license
7 | */
8 |
9 | /**
10 | * @whatItDoes Represents a type that a Component or other object is instances of.
11 | *
12 | * @description
13 | *
14 | * An example of a `Type` is `MyCustomComponent` class, which in JavaScript is be represented by
15 | * the `MyCustomComponent` constructor function.
16 | *
17 | * @stable
18 | */
19 | export const Type = Function;
20 |
21 | export function isType(v: any): v is Type {
22 | return typeof v === 'function';
23 | }
24 |
25 | export interface Type extends Function { new (...args: any[]): T; }
26 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 |
3 | export enum Declaration { Component = 'Component', Directive = 'Directive', Pipe = 'Pipe' }
4 |
5 | /** @internal */
6 | export const metadataKeys = {
7 | declaration: 'custom:declaration',
8 | name: 'custom:name',
9 | bindings: 'custom:bindings',
10 | require: 'custom:require',
11 | options: 'custom:options',
12 | listeners: 'custom:listeners',
13 | viewChildren: 'custom:viewChildren',
14 | };
15 |
16 | export function kebabToCamel(input: string) {
17 | return input.replace(/(-\w)/g, (m) => m[1].toUpperCase());
18 | }
19 |
20 | export function camelToKebab(str: string) {
21 | return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
22 | }
23 |
24 | /** @internal */
25 | export function getAttributeName(selector: string) {
26 | return selector.substr(1, selector.length - 2);
27 | }
28 |
29 | /** @internal */
30 | export function isAttributeSelector(selector: string) {
31 | return /^[\[].*[\]]$/g.test(selector);
32 | }
33 |
34 | /** @internal */
35 | export function getMetadata(metadataKey: any, target: any): any {
36 | return Reflect.getMetadata(metadataKey, target);
37 | }
38 |
39 | /** @internal */
40 | export function defineMetadata(metadataKey: any, metadataValue: any, target: any): void {
41 | Reflect.defineMetadata(metadataKey, metadataValue, target);
42 | }
43 |
44 | export function getTypeName(target: any): string {
45 | return getMetadata(metadataKeys.name, target);
46 | }
47 |
48 | export function getTypeDeclaration(target: any): Declaration {
49 | return getMetadata(metadataKeys.declaration, target);
50 | }
51 |
--------------------------------------------------------------------------------
/src/viewChild.ts:
--------------------------------------------------------------------------------
1 | import { ElementRef } from './element_ref';
2 | import { defineMetadata, getMetadata, metadataKeys } from './utils';
3 | import { Type } from './type';
4 |
5 | /** @internal */
6 | export interface IViewChildren {
7 | [property: string]: {
8 | first: boolean;
9 | selector: any;
10 | read?: typeof ElementRef;
11 | };
12 | }
13 |
14 | export function ViewChild(selector: Type|Function|string, opts: {read?: typeof ElementRef} = {}): any {
15 | return (target: any, key: string) => addBindingToMetadata(target, key, selector, opts.read, true);
16 | }
17 |
18 | export function ViewChildren(selector: Type|Function|string, opts: {read?: typeof ElementRef} = {}): any {
19 | return (target: any, key: string) => addBindingToMetadata(target, key, selector, opts.read, false);
20 | }
21 |
22 | /** @internal */
23 | function addBindingToMetadata(target: any,
24 | key: string,
25 | selector: Type|Function|string,
26 | read: typeof ElementRef,
27 | first: boolean) {
28 | const targetConstructor = target.constructor;
29 | const viewChildren: IViewChildren = getMetadata(metadataKeys.viewChildren, targetConstructor) || {};
30 | viewChildren[key] = { first, selector, read };
31 | defineMetadata(metadataKeys.viewChildren, viewChildren, targetConstructor);
32 | }
33 |
--------------------------------------------------------------------------------
/test/mocks.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '../src/injectable';
2 | import { Directive } from '../src/directive';
3 | import { Component } from '../src/component';
4 | import { NgModule } from '../src/module';
5 | import { Input, Output } from '../src/input';
6 | import { AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit, SimpleChanges } from '../src/lifecycle_hooks';
7 | import { HostListener } from '../src/hostListener';
8 | import { ViewChild, ViewChildren } from '../src/viewChild';
9 |
10 | export const serviceName = 'TestService';
11 |
12 | @Injectable(serviceName)
13 | export class TestService {
14 | private someProp = 'This is private property';
15 |
16 | static someStaticMethod() {
17 | return 'This is static method';
18 | }
19 |
20 | constructor(private $http: any) {}
21 |
22 | someMethod(): string {
23 | return this.someProp;
24 | }
25 | }
26 |
27 | export function directive(selector: string) {
28 | @Component({
29 | selector: 'child'
30 | })
31 | class ChildComponent {}
32 |
33 | @Directive({
34 | selector,
35 | scope: true
36 | })
37 | class MyDirective {
38 | @Input() testInput;
39 | @Output() testOutput;
40 | @ViewChild(ChildComponent) child;
41 |
42 | constructor(private $log: ng.ILogService,
43 | private $parse: ng.IParseService) { }
44 | $onInit() {
45 | console.log(this.$log, this.$parse);
46 | }
47 |
48 | @HostListener('click')
49 | onClick() {
50 | console.log('click');
51 | }
52 | }
53 | return MyDirective;
54 | }
55 |
56 | export function component(selector: string) {
57 | @Component({
58 | selector: 'child'
59 | })
60 | class ChildComponent {}
61 |
62 | @Component({
63 | selector
64 | })
65 | class MyComponent implements OnInit, OnChanges, DoCheck, OnDestroy, AfterViewInit {
66 | @Input() testInput;
67 | @Output() testOutput;
68 | @ViewChild(ChildComponent) child;
69 |
70 | constructor(private $log: ng.ILogService,
71 | private $parse: ng.IParseService) { }
72 | ngOnInit() {
73 | console.log(this.$log, this.$parse);
74 | }
75 |
76 | ngOnChanges(changes: SimpleChanges) {
77 | console.log(this.$log, this.$parse);
78 | }
79 |
80 | ngDoCheck() {
81 | console.log(this.$log, this.$parse);
82 | }
83 |
84 | ngOnDestroy() {}
85 |
86 | ngAfterViewInit() {}
87 |
88 | @HostListener('click')
89 | onClick() {
90 | console.log('click');
91 | }
92 | }
93 | return MyComponent;
94 | }
95 |
96 | export const registerNgModule = (name: string = '',
97 | imports: any[] = [],
98 | declarations: any[] = [],
99 | providers: any[] = []): any => {
100 |
101 | @NgModule({
102 | id: name,
103 | imports,
104 | declarations,
105 | providers,
106 | })
107 | class TestModule {
108 |
109 | static config($httpProvider: ng.IHttpProvider) {}
110 |
111 | static run($rootScope: ng.IRootScopeService) {}
112 | }
113 |
114 | return TestModule;
115 | };
116 |
--------------------------------------------------------------------------------
/test/ng-module.spec.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import { component, directive, registerNgModule, TestService } from './mocks';
3 | import { Pipe, PipeTransform } from '../src';
4 |
5 | describe('NgModule', () => {
6 | const moduleName = 'TestModule';
7 |
8 | describe('has run and config methods', () => {
9 | it('module should have run and config blocks', () => {
10 | const NgModuleClass = registerNgModule(moduleName, [], [], []);
11 | expect(NgModuleClass.module.name).toBe(moduleName);
12 | expect(angular.module(moduleName)['_runBlocks'].length).toBe(1);
13 | expect(angular.module(moduleName)['_configBlocks'].length).toBe(1);
14 | expect(angular.module(moduleName)['_configBlocks'][0].length).toBe(3);
15 |
16 | expect(angular.module(moduleName)['_runBlocks'][0]).toBe(NgModuleClass.run);
17 | expect(angular.module(moduleName)['_configBlocks'][0][2][0]).toBe(NgModuleClass.config);
18 | });
19 | });
20 |
21 | describe('imports', () => {
22 | it('should define required module as dependency', () => {
23 | const importedModuleName = 'ImportedModule';
24 | const importedModule = registerNgModule(importedModuleName, [], [], []);
25 | registerNgModule(moduleName, [importedModule], [], []);
26 | expect(angular.module(moduleName).requires).toEqual([importedModuleName]);
27 | });
28 | });
29 |
30 | describe('declarations', () => {
31 | describe('@Component:', () => {
32 | it('registers as component or directive', () => {
33 | registerNgModule(moduleName, [], [
34 | component('camelCaseName'), // registers as component
35 | component('camel-case-name'), // registers as component
36 | component('[camelCaseName]'), // registers as directive
37 | component('[camel-case-name]'), // registers as directive
38 | ]);
39 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
40 | expect(invokeQueue.length).toEqual(4);
41 | invokeQueue.forEach((value: any, index: number) => {
42 | expect(value[0]).toEqual('$compileProvider');
43 | if (index < 2) expect(value[1]).toEqual('component');
44 | else expect(value[1]).toEqual('directive');
45 | expect(value[2][0]).toEqual('camelCaseName');
46 | });
47 | });
48 |
49 | describe('@Input and @Output', () => {
50 | it('assigns properties to @Component options bindings' , () => {
51 | registerNgModule(moduleName, [], [
52 | component('camelCaseName')
53 | ]);
54 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
55 | const bindings = invokeQueue[0][2][1].bindings;
56 | expect(bindings).toBeDefined();
57 | expect(bindings).toEqual({
58 | testInput: '',
59 | testOutput: '&'
60 | });
61 | });
62 | });
63 |
64 | describe('@HostListener', () => {
65 | it('injects $element and adds $postLink and $onDestroy lifecycle hooks' , () => {
66 | registerNgModule(moduleName, [], [
67 | component('camelCaseName')
68 | ]);
69 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
70 | const ctrlProto = invokeQueue[0][2][1].controller.prototype;
71 | const inject = ctrlProto['constructor']['$inject'];
72 |
73 | inject.forEach(dependency => expect(typeof dependency).toBe('string'));
74 | expect(inject[0]).toEqual('$element');
75 | expect(ctrlProto['$postLink']).toBeDefined();
76 | expect(ctrlProto['$onDestroy']).toBeDefined();
77 | });
78 | });
79 |
80 | describe('@ViewChild', () => {
81 | it('injects $element and adds $postLink and $onChanges lifecycle hooks' , () => {
82 | registerNgModule(moduleName, [], [
83 | component('camelCaseName')
84 | ]);
85 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
86 | const ctrlProto = invokeQueue[0][2][1].controller.prototype;
87 | const inject = ctrlProto['constructor']['$inject'];
88 |
89 | inject.forEach(dependency => expect(typeof dependency).toBe('string'));
90 | expect(inject[0]).toEqual('$element');
91 | expect(ctrlProto['$postLink']).toBeDefined();
92 | expect(ctrlProto['$onChanges']).toBeDefined();
93 | });
94 | });
95 |
96 | describe('lifecycle hooks', () => {
97 | it('replaces angular lifecycle hooks to angularjs lifecycle hooks' , () => {
98 | registerNgModule(moduleName, [], [
99 | component('camelCaseName')
100 | ]);
101 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
102 | const ctrlProto = invokeQueue[0][2][1].controller.prototype;
103 | expect(ctrlProto['$onInit']).toBeDefined();
104 | expect(ctrlProto['$postLink']).toBeDefined();
105 | expect(ctrlProto['$onChanges']).toBeDefined();
106 | expect(ctrlProto['$doCheck']).toBeDefined();
107 | expect(ctrlProto['$onDestroy']).toBeDefined();
108 | });
109 | });
110 | });
111 |
112 | describe('@Directive:', () => {
113 | it('registers as directive', () => {
114 | registerNgModule(moduleName, [], [
115 | directive('camelCaseName'),
116 | directive('camel-case-name'),
117 | directive('[camelCaseName]'),
118 | directive('[camel-case-name]'),
119 | ]);
120 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
121 | expect(invokeQueue.length).toEqual(4);
122 | invokeQueue.forEach((value: any) => {
123 | expect(value[0]).toEqual('$compileProvider');
124 | expect(value[1]).toEqual('directive');
125 | expect(value[2][0]).toEqual('camelCaseName');
126 | });
127 | });
128 |
129 | describe('@Input and @Output', () => {
130 | it('assigns properties to @Directive bindToController bindings' , () => {
131 | const myDirective = directive('camelCaseName');
132 | registerNgModule(moduleName, [], [
133 | myDirective
134 | ]);
135 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
136 | const directiveObject = invokeQueue[0][2][1]();
137 | expect(directiveObject).toBeDefined();
138 | expect(directiveObject.bindToController).toEqual({
139 | testInput: '',
140 | testOutput: '&',
141 | });
142 | });
143 | });
144 |
145 | describe('@HostListener', () => {
146 | it('injects $element and adds $postLink and $onDestroy lifecycle hooks' , () => {
147 | registerNgModule(moduleName, [], [
148 | directive('[camel-case-name]')
149 | ]);
150 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
151 | const ctrlProto = invokeQueue[0][2][1]().controller.prototype;
152 | const inject = ctrlProto['constructor']['$inject'];
153 |
154 | inject.forEach(dependency => expect(typeof dependency).toBe('string'));
155 | expect(inject[0]).toEqual('$element');
156 | expect(ctrlProto['$postLink']).toBeDefined();
157 | expect(ctrlProto['$onDestroy']).toBeDefined();
158 | });
159 | });
160 |
161 | describe('@ViewChild', () => {
162 | it('injects $element and adds $postLink and $onChanges lifecycle hooks' , () => {
163 | registerNgModule(moduleName, [], [
164 | directive('[camel-case-name]')
165 | ]);
166 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
167 | const ctrlProto = invokeQueue[0][2][1]().controller.prototype;
168 | const inject = ctrlProto['constructor']['$inject'];
169 |
170 | inject.forEach(dependency => expect(typeof dependency).toBe('string'));
171 | expect(inject[0]).toEqual('$element');
172 | expect(ctrlProto['$postLink']).toBeDefined();
173 | expect(ctrlProto['$onChanges']).toBeDefined();
174 | });
175 | });
176 | });
177 | });
178 |
179 | describe('providers', () => {
180 | describe('provided as array of classes', () => {
181 | it('registers provider using class type', () => {
182 | const providers = [TestService];
183 | registerNgModule(moduleName, [], [], providers);
184 |
185 | expect(angular.module(moduleName)['_invokeQueue'].length).toEqual(providers.length);
186 | angular.module(moduleName)['_invokeQueue'].forEach((value: any, index: number) => {
187 | // expect(value[2][0]).toEqual(serviceName);
188 | expect(value[2][1]).toEqual(TestService);
189 | });
190 | });
191 | });
192 |
193 | describe('provided using useClass syntax', () => {
194 | it('registers provider using provide token', () => {
195 | const providers = [{provide: 'useClassTestService', useClass: TestService}];
196 | registerNgModule(moduleName, [], [], providers);
197 |
198 | // const $injector = angular.injector([moduleName]);
199 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
200 | expect(invokeQueue.length).toEqual(providers.length);
201 | invokeQueue.forEach((value: any, index: number) => {
202 | expect(value[0]).toEqual('$provide');
203 | expect(value[1]).toEqual('service');
204 | expect(value[2][0]).toEqual(providers[index].provide);
205 | expect(value[2][1]).toEqual(providers[index].useClass);
206 | expect(TestService).toEqual(providers[index].useClass);
207 | });
208 | // expect($injector.get(anotherServiceName)).toEqual($injector.get(serviceName));
209 | });
210 | });
211 |
212 | describe('useFactory', () => {
213 |
214 | it('registers provider using string token', () => {
215 | const providers = [{provide: 'useFactoryTestService', useFactory: (...args) => new TestService(args)}];
216 | registerNgModule(moduleName, [], [], providers);
217 |
218 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
219 | expect(invokeQueue.length).toEqual(providers.length);
220 | invokeQueue.forEach((value: any, index: number) => {
221 | expect(value[0]).toEqual('$provide');
222 | expect(value[1]).toEqual('factory');
223 | expect(value[2][0]).toEqual(providers[index].provide);
224 | expect(value[2][1]).toBe(providers[index].useFactory);
225 | });
226 | });
227 | });
228 |
229 | describe('useValue', () => {
230 |
231 | it('registers provider using string token', () => {
232 | const providers = [{provide: 'useValueTestService', useValue: (...args) => new TestService(args)}];
233 | registerNgModule(moduleName, [], [], providers);
234 |
235 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
236 | expect(invokeQueue.length).toEqual(providers.length);
237 | invokeQueue.forEach((value: any, index: number) => {
238 | expect(value[0]).toEqual('$provide');
239 | expect(value[1]).toEqual('constant');
240 | expect(value[2][0]).toEqual(providers[index].provide);
241 | expect(value[2][1]).toEqual(providers[index].useValue);
242 | });
243 | });
244 | });
245 | });
246 |
247 | describe('@Pipe', () => {
248 | const name = 'formatDateTime';
249 | describe('without injection', () => {
250 | @Pipe({ name })
251 | class FormatDateTimeFilter implements PipeTransform {
252 | public transform(input: number): string {
253 | return new Date(input).toLocaleString();
254 | }
255 | }
256 | it('registers as filter', () => {
257 | registerNgModule(moduleName, [], [
258 | FormatDateTimeFilter
259 | ]);
260 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
261 | expect(invokeQueue.length).toEqual(1);
262 | expect(invokeQueue[0][0]).toEqual('$filterProvider');
263 | expect(invokeQueue[0][1]).toEqual('register');
264 | expect(invokeQueue[0][2][0]).toEqual(name);
265 | expect(invokeQueue[0][2][1].$inject).toEqual(['$injector']);
266 | });
267 | });
268 |
269 | describe('with injection', () => {
270 | @Pipe({ name })
271 | class FormatDateTimeFilter implements PipeTransform {
272 | constructor($timeout: any) {}
273 | public transform(input: number): string {
274 | return new Date(input).toLocaleString();
275 | }
276 | }
277 | FormatDateTimeFilter.$inject = ['$timeout'];
278 | it('registers as filter', () => {
279 | registerNgModule(moduleName, [], [
280 | FormatDateTimeFilter
281 | ]);
282 | const invokeQueue = angular.module(moduleName)['_invokeQueue'];
283 | expect(invokeQueue.length).toEqual(1);
284 | expect(invokeQueue[0][0]).toEqual('$filterProvider');
285 | expect(invokeQueue[0][1]).toEqual('register');
286 | expect(invokeQueue[0][2][0]).toEqual(name);
287 | expect(invokeQueue[0][2][1].$inject).toEqual(['$injector', '$timeout']);
288 | });
289 | });
290 | });
291 | });
292 |
--------------------------------------------------------------------------------
/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig-base.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "target": "es5",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig-base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "inlineSources": true,
5 | "pretty": true
6 | },
7 | "exclude": [
8 | "dist",
9 | "node_modules"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig-base",
3 | "compilerOptions": {
4 | "module": "es2015",
5 | "target": "es5",
6 | "moduleResolution": "node",
7 | "declaration": true,
8 | "declarationDir": "dist/types",
9 | "stripInternal": true,
10 | "outDir": "dist/temp",
11 | "importHelpers": true,
12 | "lib": ["dom", "es2015"]
13 | },
14 | "files": [
15 | "src/index.ts"
16 | ],
17 | "exclude": [
18 | "dist"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rules": {
4 | "curly": false,
5 | "arrow-parens": false,
6 | "array-type": [
7 | true,
8 | "array-simple"
9 | ],
10 | "interface-name": false,
11 | "ordered-imports": false,
12 | "no-unsafe-finally": true,
13 | "object-literal-sort-keys": false,
14 | "no-var-requires": false,
15 | "no-string-literal": false,
16 | "no-console": false,
17 | "no-namespace": false,
18 | "no-consecutive-blank-lines": [
19 | true,
20 | 2
21 | ],
22 | "trailing-comma": [
23 | true,
24 | {
25 | "singleline": "never"
26 | }
27 | ],
28 | "quotemark": [
29 | true,
30 | "single",
31 | "avoid-escape"
32 | ],
33 | "variable-name": [
34 | true,
35 | "ban-keywords",
36 | "check-format",
37 | "allow-pascal-case",
38 | "allow-leading-underscore"
39 | ],
40 | "linebreak-style": [
41 | true,
42 | "LF"
43 | ],
44 | "whitespace": [
45 | true,
46 | "check-branch",
47 | "check-decl",
48 | "check-operator",
49 | "check-separator",
50 | "check-module",
51 | "check-type"
52 | ],
53 | "member-access": false,
54 | "member-ordering": [
55 | true,
56 | "static-before-instance",
57 | "variables-before-functions"
58 | ],
59 | "one-line": [
60 | true,
61 | "check-catch",
62 | "check-finally",
63 | "check-open-brace",
64 | "check-whitespace"
65 | ],
66 | "no-empty": false,
67 | "no-empty-interface": false,
68 | "ban-types": [
69 | true,
70 | "Function"
71 | ]
72 | },
73 | "jsRules": {
74 | "quotemark": [
75 | "single",
76 | "avoid-escape"
77 | ],
78 | "object-literal-sort-keys": false,
79 | "trailing-comma": false,
80 | "no-trailing-whitespace": true
81 | }
82 | }
83 |
--------------------------------------------------------------------------------