After upgrade Angular to v8: Can't resolve all parameters for Component: (?).
jfcere opened this issue · comments
Hi folks,
I recently upgraded my application to Angular 8 and couldn't get the tests to run successfully with Jest-Preset-Angular resulting in the error below telling me it cannot resolve the constructor dependencies although it works fine using Karma and was working with Angular 7.
Can't resolve all parameters for AppComponent: (?).
I've created a newly generated Angular 8 application to see if it has something to do with my migration and I ended up having the same issue.
Here is the reproduction repository (Jest configuration is in package.json
):
https://github.com/jfcere/jest-preset-angular-issue
Thanks in advance!
TL;DR
tsconfig.spec.json
"compilerOptions": {
+ "emitDecoratorMetadata": true,
"outDir": "./out-tsc/spec",
Issue
The Angular CLI takes over some of the TS -> JS transpilation using transformers, especially when it's about the decorators and reflection. We were not aware of this change, but it appearently came in in Angular v8.1.
Here the full PR: angular/angular-cli#14473
We can try to reuse their transformer (https://github.com/angular/angular-cli/blob/master/packages/ngtools/webpack/src/transformers/ctor-parameters.ts), as @angular/cli
is anyway a dependency in every Angular project.
I will try to experiment with the transformer, we can track the progress in this issue till then.
Workaround
To make ts-jest
not lose the parameter information upon compilation without the AST transformation, you can set "emitDecoratorMetadata": true
in your tsconfig.spec.json
for now. The PR mentions issues with AOT compilation, which is usually not used in testing, so this setting should not create any problems. If you use karma as well and this leads to issues, try to use a separate tsconfig.spec.json
.
@wtho thanks for the quick answer, really appreciated!
I've downgraded the reproduction repository to use Angular 8.0.3 and the error is still happening.
But the workaround for tsconfig.spec.ts
you've provided works! 😄
Ok interesting, I created a project with 8.0.0 once and there emitDecoratorMetadata
was still set, I thought it would not have come in in a patch version update, but for Angular it is just an internal detail.
It's difficult to spot, as I could not find the PR referenced in the angular-cli releases list anywhere.
I created a project with 8.0.0 once and there emitDecoratorMetadata was still set
Oh alright, that could be it, I've only downgraded the angular dependencies manually in package.json
, I didn't regenerate a new project with @angular/cli v8.0.x ... sorry!
So I was able to wrap the transformer, but I am not sure if this is the way to with ts-jest
.
Why?
This example leads to a runtime error in JIT-compiled Angular projects <v7 using emitDecoratorMetadata
, but in Angular projects v8 it works:
@Injectable()
export class BService {
constructor(@Inject(forwardRef(() => AService)) public as: AService) {}
}
@Injectable()
export class AService {
}
Runtime Error with emitDecoratorMetadata
: ReferenceError: Cannot access 'AService' before initialization
.
More to read about it: angular/angular#30106 and microsoft/TypeScript#27519
Angular AOT always handled this, but JIT only since recently (since this PR angular/angular-cli#14473). Basically a test with this scenario would pass in Angular or karma, but not with our preset. This is achieved using the DecoratorDownlevelTransformer.
What does the transformer do
The transformer stores the constructor parameter types as JS values on the object inside a ctorParameters
-property:
export class SomeClass { constructor(v: ClassInject) {} }
becomes
export class SomeClass { constructor(v) { } } SomeClass.ctorParameters = () => [ { type: ClassInject } ]
Discussion
To make the existing angular-cli
transformer work I had to import typescript
on my own and call ts.createProgram
, which seems something ts-jest
tries to avoid, although I am not sure why, i just found this note, where it says that it is slow:
[...] If we don't return
undefined
it results inundefined === "undefined"
and runcreateProgram
again (which is very slow).
On the other hand ts-jest
without isolatedModules
became slower in bigger projects since v25.10, so maybe createProgram
scales better? I am not a TypeScript internals expert, this is just a baseless guess...
ts.createProgram
is required to gain access to the type checker, which is extensively used in the transformer, but is not available in the lightweight compiler ts-jest
offers. Rewriting this transformer without the type checker is a task that is quite complicated, if not impossible (the type checker keeps track of class declarations in the project and can then re-reference them from other files).
I also used @angular/compiler-cli
to parse the project configuration and the transformer itself, which is located inside @ngtools/webpack
.
The wrapper would look something like this:
const { readConfiguration } = require('@angular/compiler-cli')
const { downlevelConstructorParameters } = require('@ngtools/webpack/src/transformers/ctor-parameters.js')
const ts = require('typescript')
function factory(cs) {
const config = readConfiguration(cs.tsJest.tsConfig.value)
const program = ts.createProgram(config.rootNames, config.options || {}, config.compilerHost)
return downlevelConstructorParameters(() => program.getTypeChecker())
}
I would require more feedback from you guys, @thymikee and especially @ahnpnl or someone else from ts-jest
, who is familiar with the TypeScript internals going on in ts-jest
.
Update
I figured out we can create the program with the ts compiler module inside configSet
, which removes the direct dependency to typescript
and @angular/compiler-cli
:
const { downlevelConstructorParameters } = require('@ngtools/webpack/src/transformers/ctor-parameters.js')
function factory(cs) {
const program = cs.compilerModule.createProgram(cs.typescript.fileNames, cs.typescript.options, undefined)
return downlevelConstructorParameters(() => program.getTypeChecker())
}
If the TS Program and Type Checker would be instantiated by ts-jest
and available to the transformer, we could just include the original Angular transformer. See kulshekhar/ts-jest#1146 for the this request.
FWIW - I've updated the Briebug schematic to support Angular 8. It includes some of the changes mentioned above in version 2.0.0 - https://github.com/briebug/jest-schematic
solved my problem,nice
The only thing that worked for me, after trying all the other options here,was to enable the following in the compilerOptions in the tsconfig.json file:
"emitDecoratorMetadata": true,
This option should be enabled if the "experimentalDecorators": true.
I had to enable both in order to make my app work.
Hope that helps someone.
Update
I figured out we can create the program with the ts compiler module inside
configSet
, which removes the direct dependency totypescript
and@angular/compiler-cli
:const { downlevelConstructorParameters } = require('@ngtools/webpack/src/transformers/ctor-parameters.js') function factory(cs) { const program = cs.compilerModule.createProgram(cs.typescript.fileNames, cs.typescript.options, undefined) return downlevelConstructorParameters(() => program.getTypeChecker()) }If the TS Program and Type Checker would be instantiated by
ts-jest
and available to the transformer, we could just include the original Angular transformer. See kulshekhar/ts-jest#1146 for the this request.
@who I think we can start working on this right ?, based on these codes from you + the exposed Program from ts-jest.
Currently isolatedModules mode doesn’t expose Program so I don’t know if it is a must to have or just use compileModule.createProgram as fallback for isolatedModules mode like you did above ?
@ahnpnl yes, I have a working implementation for isolatedModules: false
using the original Angular Transformer. I am not sure what the better alternative would be:
- recommending the workaround for whoever wants to use
isolatedModules: true
- provide a possibly bad performing program in
isolatedModules: true
- discourage from using
isolatedModules: true
Finally the best solution for everybody would be a performant ts-jest
without isolated modules, but it is difficult ot estimate how difficult it is to achieve this, or if it can be achieved at all.
Hmm actually I gonna create a PR to expose Program
also for isolatedModules: true
because the need of support projectReferences
which also requires Program
. So no matter what kind of settings for isolatedModules
, there will be always a Program
instance.
I noticed that the impact to performance is usually noticeable when using Program.emit
, otherwise creating a Program
instance doesn't do anything. I hope I'm right. Do you have similar observation ?
LanguageService
in ts-jest
has been improved quite a lot but not sure what else it can be done. There will be room for improvement but will need some times to figure out and helps from others.
Exposing Program
for isolatedModules: true
can be found here kulshekhar/ts-jest#766 (comment) If you can help to test projectReferences
as well, it would be very nice :)
Update 1: I have tested the tgz file from that ts-jest
issue and I don't see any downgrade in performance for isolatedModules: true
. Seem like my theory is correct that only when using .emit()
will impact the performance
Update 2: Exposing Program
for isolatedModules: true
will be available after this PR kulshekhar/ts-jest#1527
Ok, awesome! This simplifies things for this issue.
Will prepare the PR soon!
The error
This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid.
is fixed using this emitDecoratorMetadata: true
@norato this workaround was mentioned several times before in this thread, but is just a workaround and not more, not a fix for the preset.
If you wonder why this is not closed: The fix in this preset would not require any changes in the tsconfig
, as this preset in a perfect world would already include all required settings.
@wtho I did not found those references mentioned when I wrote this comment. This was just to help the ones that need to "fix" this error until we don't have the solution.
This is a weird one. I'm also getting this error, however, I do have emitDecoratorMetadata: true
in my tsconfig.spec.json. But the problem only seems to appear with a specific provider that doesn't want to work unless I reference it in the file outside of type definitions.
Edit: seems like adding isolatedModules: true
to ts-jest config makes everything work. 🤷♂️
@villelahdenvuo isolatedModules
switches off typechecking and just compiles TS to JS.
Do you have an own file for each injectable class?
@wtho Yeah, but I actually re-export the class through a another file, that might be why it's failing.
@wtho I can confirm that moving the failing class to the file being imported made the provider work again even without isolatedModules.
Hey @Murkhassan86,
you definitely have a different issue, you seem to not have worked much with TypeScript syntax yet.
Try this instead:
constructor(private fb: FormBuilder) {}
Cheers!
Dear wtho,
thank you for the correction.
Why I'm getting this error? "emitDecoratorMetadata": true, didn't work.
import { Component, OnInit, Input, ChangeDetectorRef, Attribute, Output, EventEmitter } from '@angular/core';
import { Store } from '@ngrx/store';
@Component({
selector: 'keyword-list',
templateUrl: './keyword-list.component.html',
styleUrls: ['./keyword-list.component.css']
})
export class KeywordListComponent {
constructor(
private masterDataStore: any,
private changeDetectorRef: ChangeDetectorRef,
@Attribute('isSkills') public isSkills: boolean = false
) {}
}
There is something wrong with constructor here.
@senseihimanshu Most likely due to that you're declaring masterDataStore
as any
. You need to provide a proper (and registered) type in order for Angular's DI system to know what instance to inject
TL;DR
tsconfig.spec.json "compilerOptions": { + "emitDecoratorMetadata": true, "outDir": "./out-tsc/spec",
Issue
The Angular CLI takes over some of the TS -> JS transpilation using transformers, especially when it's about the decorators and reflection. We were not aware of this change, but it appearently came in in Angular v8.1.
Here the full PR: angular/angular-cli#14473
We can try to reuse their transformer (https://github.com/angular/angular-cli/blob/master/packages/ngtools/webpack/src/transformers/ctor-parameters.ts), as
@angular/cli
is anyway a dependency in every Angular project.I will try to experiment with the transformer, we can track the progress in this issue till then.
Workaround
To make
ts-jest
not lose the parameter information upon compilation without the AST transformation, you can set"emitDecoratorMetadata": true
in yourtsconfig.spec.json
for now. The PR mentions issues with AOT compilation, which is usually not used in testing, so this setting should not create any problems. If you use karma as well and this leads to issues, try to use a separatetsconfig.spec.json
.
You are awesome. It solved my issue. Iwas struggling for days and hours.
Hey there! Any news on this bug?
[Edit] Seems to be closed by an old pr, nice!