Title: forwardRef breaks at runtime with ES2015 · Issue #30106 · angular/angular · GitHub
Open Graph Title: forwardRef breaks at runtime with ES2015 · Issue #30106 · angular/angular
X Title: forwardRef breaks at runtime with ES2015 · Issue #30106 · angular/angular
Description: 🐞 bug report Affected Package The issue is caused by package @angular/core Is this a regression? No. Description Using forwardRef as described in https://angular.io/api/core/forwardRef while targeting ES2015 results on a Uncaught Referen...
Open Graph Description: 🐞 bug report Affected Package The issue is caused by package @angular/core Is this a regression? No. Description Using forwardRef as described in https://angular.io/api/core/forwardRef while target...
X Description: 🐞 bug report Affected Package The issue is caused by package @angular/core Is this a regression? No. Description Using forwardRef as described in https://angular.io/api/core/forwardRef while target...
Opengraph URL: https://github.com/angular/angular/issues/30106
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"forwardRef breaks at runtime with ES2015","articleBody":"\u003c!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅\r\n\r\nOh hi there! 😄\r\n\r\nTo expedite issue processing please search open and closed issues before submitting a new one.\r\nExisting issues often contain information about workarounds, resolution, or progress updates.\r\n\r\n🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅--\u003e\r\n\r\n\r\n# 🐞 bug report\r\n\r\n### Affected Package\r\n\u003c!-- Can you pin-point one or more @angular/* packages as the source of the bug? --\u003e\r\n\u003c!-- ✍️edit: --\u003e The issue is caused by package @angular/core\r\n\r\n\r\n### Is this a regression?\r\n\r\n\u003c!-- Did this behavior use to work in the previous version? --\u003e\r\n\u003c!-- ✍️--\u003e No.\r\n\r\n\r\n### Description\r\n\r\nUsing `forwardRef` as described in https://angular.io/api/core/forwardRef while targeting ES2015 results on a `Uncaught ReferenceError: Cannot access 'Lock' before initialization` runtime error.\r\n\r\n\r\n## 🔬 Minimal Reproduction\r\n- make a new project `ng new forward-ref-project \u0026\u0026 cd forward-ref-project`\r\n- ensure `tsconfig.json` contains `\"target\": \"es2015\",`\r\n- replace the contents of `src/main.ts` with:\r\n```\r\nimport { Inject, forwardRef, ReflectiveInjector } from '@angular/core';\r\nclass Door {\r\n lock: Lock;\r\n\r\n // Door attempts to inject Lock, despite it not being defined yet.\r\n // forwardRef makes this possible.\r\n constructor(@Inject(forwardRef(() =\u003e Lock)) lock: Lock) { this.lock = lock; }\r\n}\r\n\r\n// Only at this point Lock is defined.\r\nclass Lock { }\r\n\r\nconst injector = ReflectiveInjector.resolveAndCreate([Door, Lock]);\r\nconst door = injector.get(Door);\r\nconsole.log(door instanceof Door);\r\nconsole.log(door.lock instanceof Lock);\r\n```\r\n- `ng serve -o`\r\n\r\n## 🔥 Exception or Error\r\n```\r\nUncaught ReferenceError: Cannot access 'Lock' before initialization\r\n at Module../src/main.ts (main.ts:21)\r\n at __webpack_require__ (bootstrap:78)\r\n at Object.2 (main.ts:30)\r\n at __webpack_require__ (bootstrap:78)\r\n at checkDeferredModules (bootstrap:45)\r\n at Array.webpackJsonpCallback [as push] (bootstrap:32)\r\n at main.js:1\r\n```\r\n\r\n\r\n## 🌍 Your Environment\r\n\r\n**Angular Version:**\r\n\u003cpre\u003e\u003ccode\u003e\r\nAngular CLI: 8.0.0-beta.18\r\nNode: 10.10.0\r\nOS: win32 x64\r\nAngular: 8.0.0-beta.14\r\n... animations, common, compiler, compiler-cli, core, forms\r\n... language-service, platform-browser, platform-browser-dynamic\r\n... router\r\n\r\nPackage Version\r\n-----------------------------------------------------------\r\n@angular-devkit/architect 0.800.0-beta.18\r\n@angular-devkit/build-angular 0.800.0-beta.18\r\n@angular-devkit/build-optimizer 0.800.0-beta.18\r\n@angular-devkit/build-webpack 0.800.0-beta.18\r\n@angular-devkit/core 8.0.0-beta.18\r\n@angular-devkit/schematics 8.0.0-beta.18\r\n@angular/cli 8.0.0-beta.18\r\n@ngtools/webpack 8.0.0-beta.18\r\n@schematics/angular 8.0.0-beta.18\r\n@schematics/update 0.800.0-beta.18\r\nrxjs 6.4.0\r\ntypescript 3.4.5\r\nwebpack 4.30.0\r\n\u003c/code\u003e\u003c/pre\u003e\r\n\r\n**Anything else relevant?**\r\n\r\n`forwardRef` is provided specifically for the purpose of referencing something that isn't defined. This is useful in breaking [circular dependencies](https://angular.io/guide/dependency-injection-in-action#break-circularities-with-a-forward-class-reference-forwardref), and when declaring both services and components in the [same file](https://angular.io/guide/dependency-injection#create-and-register-an-injectable-service).\r\n\r\n`forwardRef` works because it delays the resolution of the reference to a time at which it is already declared through the callback indirection. In the [API example](https://angular.io/api/core/forwardRef), the symbol we want to delay resolution is `Lock`:\r\n```\r\nclass Door {\r\n lock: Lock;\r\n\r\n // Door attempts to inject Lock, despite it not being defined yet.\r\n // forwardRef makes this possible.\r\n constructor(@Inject(forwardRef(() =\u003e Lock)) lock: Lock) { this.lock = lock; }\r\n}\r\n\r\n// Only at this point Lock is defined.\r\nclass Lock { }\r\n```\r\n\r\nBut `Lock` is actually being referenced in more places than just inside `forwardRef`. It is also being used as a TS type in the class property, and in the constructor parameter.\r\n\r\nTypes don't usually have a runtime representation so that shouldn't be a problem. But constructor types are an exception and actually do have a runtime representation. We can see this by looking at the transpiled code:\r\n\r\n```\r\nimport * as tslib_1 from \"tslib\";\r\nimport { Inject, forwardRef, ReflectiveInjector } from '@angular/core';\r\nlet Door = class Door {\r\n // Door attempts to inject Lock, despite it not being defined yet.\r\n // forwardRef makes this possible.\r\n constructor(lock) { this.lock = lock; }\r\n};\r\nDoor = tslib_1.__decorate([\r\n tslib_1.__param(0, Inject(forwardRef(() =\u003e Lock))),\r\n tslib_1.__metadata(\"design:paramtypes\", [Lock])\r\n], Door);\r\n// Only at this point Lock is defined.\r\nclass Lock {\r\n}\r\nconst injector = ReflectiveInjector.resolveAndCreate([Door, Lock]);\r\nconst door = injector.get(Door);\r\nconsole.log(door instanceof Door);\r\nconsole.log(door.lock instanceof Lock);\r\n```\r\n\r\nThe `Lock` type in the for the constructor parameter was transpiled into `tslib_1.__metadata(\"design:paramtypes\", [Lock])`. This reference does not have a delayed resolution like the injected `forwardRef` and is instead immediately resolved, resulting in `Uncaught ReferenceError: Cannot access 'Lock' before initialization`.\r\n\r\nThis error isn't observed when targetting ES5 however. We can understand why by looking at the code when transpiled to ES5 :\r\n```\r\nimport * as tslib_1 from \"tslib\";\r\nimport { Inject, forwardRef, ReflectiveInjector } from '@angular/core';\r\nvar Door = /** @class */ (function () {\r\n // Door attempts to inject Lock, despite it not being defined yet.\r\n // forwardRef makes this possible.\r\n function Door(lock) {\r\n this.lock = lock;\r\n }\r\n Door = tslib_1.__decorate([\r\n tslib_1.__param(0, Inject(forwardRef(function () { return Lock; }))),\r\n tslib_1.__metadata(\"design:paramtypes\", [Lock])\r\n ], Door);\r\n return Door;\r\n}());\r\n// Only at this point Lock is defined.\r\nvar Lock = /** @class */ (function () {\r\n function Lock() {\r\n }\r\n return Lock;\r\n}());\r\nvar injector = ReflectiveInjector.resolveAndCreate([Door, Lock]);\r\nvar door = injector.get(Door);\r\nconsole.log(door instanceof Door);\r\nconsole.log(door.lock instanceof Lock);\r\n```\r\n\r\nIn ES5 there are no `class` declarations, so TS instead uses a `var`. One important different between `var` and `class/let/const` is that the latter are all subject to the [Temporal Dead Zone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone). \r\n\r\nIn practical terms the TDZ means that using a `var` before it is declared resolves to `undefined`, but using a `class/let/const` instead throws a `ReferenceError`. This is the error we are seeing here.\r\n\r\nA possible workaround is to **not** declare the type in the constructor:\r\n```\r\n// Instead of adding the type in the parameter\r\nconstructor(@Inject(forwardRef(() =\u003e Lock)) lock: Lock) { \r\n this.lock = lock;\r\n}\r\n\r\n// Add it as a cast in the constructor body\r\nconstructor(@Inject(forwardRef(() =\u003e Lock)) lock) {\r\n this.lock = lock as Lock;\r\n}\r\n```\r\n\r\nThis will change the transpiled code and remove the reference, avoiding the `ReferenceError`:\r\n```\r\nDoor = tslib_1.__decorate([\r\n tslib_1.__param(0, Inject(forwardRef(() =\u003e Lock))),\r\n tslib_1.__metadata(\"design:paramtypes\", [Object])\r\n ^^^^^^ was Lock before\r\n], Door);\r\n```\r\n\r\nOne important note is that the `ReferenceError` does not come up on Angular CLI projects compiled with AOT. This is because there we actually transform transpiled TS code and remove Angular decorators, so the metadata reference (`tslib_1.__metadata(\"design:paramtypes\", [Lock])`) never reaches the browser and thus there is no `ReferenceError`.\r\n","author":{"url":"https://github.com/filipesilva","@type":"Person","name":"filipesilva"},"datePublished":"2019-04-25T08:56:10.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":19},"url":"https://github.com/30106/angular/issues/30106"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:0c7c250c-53e1-80e0-3d7c-70a9df91d5e7 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | CF8C:2345DC:875515:C474E6:6A54ACBD |
| html-safe-nonce | 4d1033f45174988f7a5c038ddf7967c31d6767806d8b6cd6f22f96a3d328bb57 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDRjhDOjIzNDVEQzo4NzU1MTU6QzQ3NEU2OjZBNTRBQ0JEIiwidmlzaXRvcl9pZCI6IjE4NjI2NTM2NDY0ODUxMDU4NTMiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | b311dd87dbe4fa736020b03a2b5d4074bd1810e284e67cb90c208c34cd0708d4 |
| hovercard-subject-tag | issue:437082804 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/angular/angular/30106/issue_layout |
| twitter:image | https://opengraph.githubassets.com/bfc55adc43891442de378c24585c3d530b45ed59b21cbb4f0354083f9e5ae00d/angular/angular/issues/30106 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/bfc55adc43891442de378c24585c3d530b45ed59b21cbb4f0354083f9e5ae00d/angular/angular/issues/30106 |
| og:image:alt | 🐞 bug report Affected Package The issue is caused by package @angular/core Is this a regression? No. Description Using forwardRef as described in https://angular.io/api/core/forwardRef while target... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | filipesilva |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| go-import | github.com/angular/angular git https://github.com/angular/angular.git |
| octolytics-dimension-user_id | 139426 |
| octolytics-dimension-user_login | angular |
| octolytics-dimension-repository_id | 24195339 |
| octolytics-dimension-repository_nwo | angular/angular |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 24195339 |
| octolytics-dimension-repository_network_root_nwo | angular/angular |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | f8cfff6384f62259baea531c7f58bc7040f8dd92 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width