Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

After upgrade Angular to v8: Can't resolve all parameters for Component: (?). #288

Closed
jfcere opened this issue Jul 3, 2019 · 28 comments · Fixed by #562
Closed

After upgrade Angular to v8: Can't resolve all parameters for Component: (?). #288

jfcere opened this issue Jul 3, 2019 · 28 comments · Fixed by #562
Labels
🐛 Bug Confirmed Bug is confirmed

Comments

@jfcere
Copy link

jfcere commented Jul 3, 2019

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!

@wtho
Copy link
Collaborator

wtho commented Jul 3, 2019

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 wtho changed the title Angular 8: Can't resolve all parameters for Component: (?). Angular 8.1: Can't resolve all parameters for Component: (?). Jul 3, 2019
@jfcere
Copy link
Author

jfcere commented Jul 3, 2019

@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! 😄

@wtho
Copy link
Collaborator

wtho commented Jul 3, 2019

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.

@wtho wtho changed the title Angular 8.1: Can't resolve all parameters for Component: (?). Angular 8: Can't resolve all parameters for Component: (?). Jul 3, 2019
@jfcere
Copy link
Author

jfcere commented Jul 3, 2019

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!

@wtho
Copy link
Collaborator

wtho commented Jul 4, 2019

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 in undefined === "undefined" and run createProgram 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.

@schuchard
Copy link

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

@joebnb
Copy link

joebnb commented Feb 14, 2020

solved my problem,nice

joebnb added a commit to joebnb/Angular8WithWebpack4 that referenced this issue Feb 14, 2020
fix inject problem case like
```
export class AppComponent {
  constructor(public translate:TranslateService) {  // error
    translate.addLangs(['en', 'fr']);
    translate.setDefaultLang('en');

    const browserLang = translate.getBrowserLang();
    translate.use(browserLang.match(/en|fr/) ? browserLang : 'en');
  }
}
```
it will throw a error Can't resolve all parameters for AppComponent: (?)

quote:
thymikee/jest-preset-angular#288
@dalitroz
Copy link

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.

@ahnpnl
Copy link
Collaborator

ahnpnl commented Apr 12, 2020

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.

@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 ?

@wtho
Copy link
Collaborator

wtho commented Apr 14, 2020

@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.

@ahnpnl
Copy link
Collaborator

ahnpnl commented Apr 15, 2020

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

@wtho
Copy link
Collaborator

wtho commented Apr 16, 2020

Ok, awesome! This simplifies things for this issue.

Will prepare the PR soon!

@norato
Copy link

norato commented May 25, 2020

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

@wtho
Copy link
Collaborator

wtho commented May 26, 2020

@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.

@norato
Copy link

norato commented May 26, 2020

@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.

@villelahdenvuo
Copy link

villelahdenvuo commented Jun 12, 2020

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. 🤷‍♂️

@wtho
Copy link
Collaborator

wtho commented Jun 12, 2020

@villelahdenvuo isolatedModules switches off typechecking and just compiles TS to JS.

Do you have an own file for each injectable class?

@villelahdenvuo
Copy link

@wtho Yeah, but I actually re-export the class through a another file, that might be why it's failing.

@villelahdenvuo
Copy link

@wtho I can confirm that moving the failing class to the file being imported made the provider work again even without isolatedModules.

@Murkhassan86
Copy link

Murkhassan86 commented Jun 25, 2020

image

I have created component with name 'test' and after simple coding i am facing this type of error. I have also make this true "emitDecoratorMetadata": true,. still i am getting this error.
I am using webstorm framework and recently joined GitHub. kindly help me out.

@Murkhassan86
Copy link

image
this is how my test.component.ts page looks like. In test.component.html i have created simple form by using some input fields only. what is the error in this code. your response will highly be appreciated.

@wtho
Copy link
Collaborator

wtho commented Jun 25, 2020

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!

@wtho wtho changed the title Angular 8: Can't resolve all parameters for Component: (?). After upgrade Angular App to v8: Can't resolve all parameters for Component: (?). Jun 25, 2020
@wtho wtho changed the title After upgrade Angular App to v8: Can't resolve all parameters for Component: (?). After upgrade Angular to v8: Can't resolve all parameters for Component: (?). Jun 25, 2020
@Murkhassan86
Copy link

Dear wtho,
thank you for the correction.

@senseihimanshu
Copy link

senseihimanshu commented Aug 18, 2020

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.

@dannyskoog
Copy link

@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

@systemdice

This comment has been minimized.

@systemdice
Copy link

You are awesome. It solved my issue. Iwas struggling for days and hours.

@ahnpnl ahnpnl added the Confirmed Bug is confirmed label Oct 27, 2020
ahnpnl added a commit that referenced this issue Dec 12, 2020
Closes #108
Closes #288
Closes #322
Closes #353
Closes #622

BREAKING CHANGE:
With the new jest transformer, `jest-preset-angular` now switches to default to use this new transformer and no longer uses `ts-jest` to transform codes.

Users who are currently doing in jest config
```
// jest.config.js
module.exports = {
    // [...]
    transform: {
      '^.+\\.(ts|js|html)$': 'ts-jest',
    },
}
```

should change to
```
// jest.config.js
module.exports = {
    // [...]
    transform: {
      '^.+\\.(ts|js|html)$': 'jest-preset-angular',
    },
}
```

`isolatedModule: true` will still use `ts-jest` to compile `ts` to `js` but you won't get full compatibility with Ivy.
@fexxdev
Copy link

fexxdev commented Jan 16, 2023

Hey there! Any news on this bug?
[Edit] Seems to be closed by an old pr, nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 Bug Confirmed Bug is confirmed
Projects
None yet