23

In my Angular 9 app, I have an abstract class:

export abstract class MyAbstractComponent {
  constructor(
    protected readonly cd: ChangeDetectorRef,
  ) {
    super();
  }

  // ...
}

and a Component extending it:

@Component({
  // ...
})
export class MyConcreteComponent extends MyAbstractComponent {
  // ...
}

Everything works fine except the tests, where I get the following error:

Error: This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid. This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.

Please check that 1) the type for the parameter at index 0 is correct and 2) the correct Angular decorators are defined for this class and its ancestors.

Francesco Borzi
  • 56,083
  • 47
  • 179
  • 252

13 Answers13

22

Took me a while, but after creating a new application with angular 10.2.3 my base tsconfig.json did not have the

"emitDecoratorMetadata": true,

in the compilerOptions!

After adding that again to the tsconfig.json, DI worked as expected.

Eydrian
  • 10,258
  • 3
  • 19
  • 29
16

We faced the same issue when migrating to version 9. At the end we found out we forgot to add decorators to some abstract components. As of v9 all classes that uses Angular DI must have an Angular class-level decorator.

Example from Ivy compatibility examples:

Before:

export class DataService {
  constructor(@Inject('CONFIG') public config: DataConfig) {}
}

@Injectable()
export class AppService extends DataService {...}

After:

@Injectable() // <--- THIS
export class DataService {
  constructor(@Inject('CONFIG') public config: DataConfig) {}
}

@Injectable()
export class AppService extends DataService {...}
metodribic
  • 1,561
  • 17
  • 27
  • hello im new to angular 9. im running into this issue but the examples arent clear to me. how do i use this in my component? – hello world May 07 '20 at 08:07
  • Look for any component/service/directive/... in your project which are extending other component/service/directive/... and check if all of those elements has Angular class-level decorators on top of it (`@Component`, `@Injectable`, `@Directive`, ...) – metodribic May 08 '20 at 04:22
  • Is there any source about whether it's correct to add decorators to abstract components vs adding constructors to each extending component / service? It seems strange to me that both solutions works, although I wouldn't be surprised. – briosheje May 18 '20 at 09:07
  • I didn't find calling `super` on child class documented anywhere so I prefer decorators since this solution is documented as part of (advanced) migration guide – metodribic May 19 '20 at 11:02
  • Anybody who winds up at this answer may be interested in the discussion on [this related issue](https://github.com/angular/angular/issues/37769) on the Angular tracker. – Coderer Aug 06 '21 at 14:05
7

I've resolved my issue by adding the constructor in MyConcreteComponent and calling the super(...) constructor:

@Component({
  // ...
})
export class MyConcreteComponent extends MyAbstractComponent {

  // adding this block fixed my issue
  constructor(
    protected readonly cd: ChangeDetectorRef,
  ) {
    super(cd);
  }

  // ...
}
Francesco Borzi
  • 56,083
  • 47
  • 179
  • 252
7

In my case simple restarting npm fixed this error.

Trolejbus
  • 115
  • 4
  • Same issue for me, I was really confused as to why it wasn't working, stopped npm and re-ran the `ng serve` command and it worked. – EM-Creations May 06 '21 at 16:31
5

Today I had to face this exact error while performing tests in my app using Jest. Running Angular 10.2, I had another spec file that was performing the exact same test without throwing any error. Every class had the decorators. Took me an hour to find out the problem: I had a circular dependency in my test file, and I didn't notice it since tests were not displaying usual Angular warning.

So just to point out to somebody else that stumble in the same problem, check your imports!

Dario Vogogna
  • 618
  • 6
  • 7
  • 1
    I faced the same issue : moved some code from main project to a my-library. The imports where importing from the library using `@my-library` which works with `ng serve` but fails in tests. The imports must be using file path when importing same library objects. – ibenjelloun Jul 06 '21 at 08:17
  • 1
    Thank you for that. I spent some time digging, and it was challenging to find the missing import that was causing the circular dependency, but you pointed me in the right direction. Thanks! – Michael Norgren Apr 27 '23 at 19:40
4

In my case it happened because I was in git repository A and moved to another git repository (B) and some services which existed in repository A was no longer on B. so, by simple restarting npm this error disappeared. Hope it works for others.

Mo Asghari
  • 251
  • 2
  • 11
2

Just to add additional info to this question, this error occurred for me similar to the following example.

import * as fromOtherLib from 'fromOtherLib';

@Component({
...
})
class ExampleComponent {
  constructor(private exampleService: fromOtherLib.ExampleService){}
}

So I removed the * as fromOtherLib part and I replaced it with a destructured import like import { exampleService } from 'fromOtherLib';

Andrew Gremlich
  • 353
  • 4
  • 7
  • I can confirm having similar error around `import * as Apollo from 'apollo-angular'` - switching to explicit `import { Apollo, Mutation, Query } from 'apollo-angular';` solved my issue as well. – ciekawy Mar 14 '23 at 19:16
1

I got something close to this error message ("Error: This constructor was not compatible with Dependency Injection.") when I inadvertently changed an import statement to an import type statement:

WRONG

import type { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore'

@Injectable({
  providedIn: 'root'
})
export class CommentsApiService {

  constructor(
    private firestore: AngularFirestore
  ) { }

  // ...
}

CORRECT

import { AngularFirestore } from '@angular/fire/firestore'
import type { AngularFirestoreDocument } from '@angular/fire/firestore'

@Injectable({
  providedIn: 'root'
})
export class CommentsApiService {

  constructor(
    private firestore: AngularFirestore
  ) { }

  // ...
}
Trevor
  • 13,085
  • 13
  • 76
  • 99
  • 1
    Came down here to give this answer after discovering it for myself. I found an interesting edge case: normally, the Angular Language Service (in VSCode) flags the use of `import type` for DI tokens as an error. BUT, if you convert a non-injected class into an injected service *without* restarting the Angular language service, it seems to fail to notice that your constructor is now using DI. – Coderer Aug 06 '21 at 14:12
0

in my exact case(in angular9), By adding private accessor to the constructor parameter..

Simas Joneliunas
  • 2,890
  • 20
  • 28
  • 35
0

In my case I had a constructor argument that the di container didn't recognize. I marked it optional which was good enough for it to ignore it:

import { Optional } from '@angular/core';    

constructor(protected coolService: CoolService, @Optional() private name: string) {...
jcroll
  • 6,875
  • 9
  • 52
  • 66
0

This cryptic error can be caused by wrong order of multiple dependent services within the same file.

e.g.

@Injectable()
export class ServiceB {
  constructor(serviceA: ServiceA) {}
}

@Injectable()
export class ServiceA {
  constructor() {}
}

Reversing the order fixes the issue:

@Injectable()
export class ServiceA {
  constructor() {}
}

@Injectable()
export class ServiceB {
  constructor(serviceA: ServiceA) {}
}

That said, you should rather keep to one-service-per-file pattern to avoid such issues.

Vedran
  • 10,369
  • 5
  • 50
  • 57
0

Using ng-mocks and jest, encountered same error. Checked for circular dependencies as @Dario Vogogna suggested. I had paths in tsconfig modified, and files used them in relative and absolute manner, so I changed related imports to these exact paths, and changed order in constructor. It could be cache, but I removed everything except source files twice.

TL;DR: Reorder injections in constructor, rename your imports to follow paths in tsconfig accordingly in every imported file.

Maxime Lyakhov
  • 140
  • 1
  • 11
0

Big PAIN in the rear.

I forgot the @Inject(ElementRef) attribute in my Directive constructor parameter and it started giving me this error.

Wrong

constructor (private ElementRef : element)

Correct

constructor (@Inject(ElementRef) private ElementRef : element)
fahadash
  • 3,133
  • 1
  • 30
  • 59