angular: Dependency injection and class inheritance doesn't work in Angular 8 library test

🐞 bug report

Affected Package

The issue is caused by package @angular/core@^8

Is this a regression?

Yes, the previous version in which this bug was not present was: @angular/core@7

Description

The problem occurs when:

  1. The base class has a constructor with an injected service. (Injector from the example)
@Injectable()
export class BaseService {
  // "Injector" is just for the sake of example. Any injectable would have the same problem.
  constructor(public injector: Injector) {
  }
}
  1. Child class has no constructor (i.e., it relies on base class constructor) AND at least one property with the default value.
@Injectable()
export class MyLibService extends BaseService {

  public foo = 'bar';

}
  1. (dont ask me how I have found this, but:) In angular.json [lib name].test.options.codeCoverage should be true.

PROBLEM:

injector is undefined in the instance of MyLibService.


🚩 Removing property from the child class (2) AND/OR opting out codeCoverage config in angular.json (3) doesn’t brake test.

As a result, I can’t have a class inheritance and code coverage at the same time.


I could put a constructor in the child class and call super by passing all required arguments manually, but it’s the last option since the real-life scenario is much more complicated than the example provided here.

🔬 Minimal Reproduction

git repo: https://github.com/ddramone/ng-8-library-inheritance-di-test-bug

reproduce steps:

  1. Install angular 8 cli
  2. Clone git clone https://github.com/ddramone/ng-8-library-inheritance-di-test-bug.git
  3. npm i
  4. run Test ng test my-lib

Expected behavior

Test should pass = Injector service should be available in the service instance.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 32
  • Comments: 17 (4 by maintainers)

Commits related to this issue

Most upvoted comments

I have looked into why DI doesn’t work when coverage reporting is enabled, and it can be traced back to how Angular detects whether a constructor was present in a class, or if a constructor is generated synthetically, e.g. to initialize class fields. Enabling coverage inserts coverage instrumentation statements into the generated constructors:

class MyLibService extends BaseService {
  constructor() {
    cov_8nt6qq5zt().f[2]++;
    cov_8nt6qq5zt().s[6]++;
    super(...arguments);
    cov_8nt6qq5zt().s[7]++;
    this.foo = 'bar';
  }
}

These prevent Angular from recognizing the constructor as synthesized, causing MyLibService to be instantiated with 0 arguments. The proper behavior would be to delegate to the parent constructor, allowing its dependencies to be injected.

I opened #37811 as a proposal to workaround this issue. It’s not done yet and I can’t give an ETA yet, unfortunately.

tested on angular 8.2.11 with cli 8.3.12 and it is still not fixed, now I have to add tons of workarounds 😦

we are seeing the same issue when using differential loading with v8 and this is breaking in es5 script bundles in IE 11 for specific instances where we have multiple class inheritances without constructor defined in some of the classes.

ex: ` export class A { constructor(public dep1: SomeType){} }

export class B extends A { }

export class C extends B { constructor(dep1: SomeType, dep2: AnotherType){ super(dep1); }

ngOnInit(){ //this.dep1 is undefined here //this.dep2 is defined } } `

I got the same problem. It appears only if tests are running with code coverage: ng test --code-coverage The usual ng test or any type of build (JIT or AOT) gives no errors.

For now the problem can be solved by injecting services on child level and passing them to parent component.