├── .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 | [![Build Status](https://travis-ci.org/vsternbach/angular-ts-decorators.svg?branch=master)](https://travis-ci.org/vsternbach/angular-ts-decorators) 6 | [![Coverage Status](https://coveralls.io/repos/github/vsternbach/angular-ts-decorators/badge.svg?branch=master)](https://coveralls.io/github/vsternbach/angular-ts-decorators?branch=master) 7 | [![Greenkeeper badge](https://badges.greenkeeper.io/vsternbach/angular-ts-decorators.svg)](https://greenkeeper.io/) 8 | 9 | [![NPM](https://nodei.co/npm/angular-ts-decorators.png?downloads=true)](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 |
49 | 50 | 51 |
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, ' 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: ' { 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: ' { 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 | --------------------------------------------------------------------------------