Search code examples
angularng-packagrangular15

Angular 14 to 15 update results in constructor parameter compatibility error in library


I have authored a library for extracting environment variables that are set from server-side rendering.

This library provides a base class to extend. getEnvironmentValues() has been omitted for brevity:

@Directive()
export class NgxEnvironmentService<T> {

  environment: T;

  constructor(
    @Inject(ENVIRONMENT_CONFIG)
    private readonly environmentConfig: IEnvironmentConfig,

    @Inject(PLATFORM_ID)
    private readonly platformId: string,
  ) {
    if (isPlatformBrowser(this.platformId)) {
      this.environment = this.getEnvironmentValues<T>();
    }
  }

}

Here is the ENVIRONMENT_CONFIG token:

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

import { DEFAULT_CONFIG } from './constants';
import { IEnvironmentConfig } from '../interfaces';

export const ENVIRONMENT_CONFIG = new InjectionToken<IEnvironmentConfig>('environment-config', {
  factory: (): IEnvironmentConfig => DEFAULT_CONFIG,
  providedIn: 'root'
});

This library is transpiled using ng-packagr. After installation and implementation in the target project:

import { Injectable } from '@angular/core';
import { NgxEnvironmentService } from '@labcorp/ngx-environment';

import { IEnvironment } from '../interfaces';

@Injectable({
  providedIn: 'root'
})
export class EnvironmentService extends NgxEnvironmentService<IEnvironment> {}

I receive the following error:

The injectable EnvironmentService inherits its constructor from NgxEnvironmentService, but the latter has a constructor parameter that is not compatible with dependency injection. Either add an explicit constructor to EnvironmentService or change NgxEnvironmentService's constructor to use parameters that are valid for DI.

If I take the source code from the library and copy it into the target project and change the import path, everything works as expected.

This issue: https://stackoverflow.com/questions/60702258/angular-ivy-constructor-is-not-compatible-with-angular-dependency-injection#:~:text=core.js%3A3828%20ERROR%20Error%3A%20This%20constructor%20is%20not%20compatible,of%20this%20class%20is%20missing%20an%20Angular%20decorator is similar, but I'd really like to avoid having to re-implement the constructor in classes that extend NgxEnvironmentService

Has anyone else experienced this error, and how did you fix it?


Solution

  • There were two underlying problems causing this behavior.

    First was this issue: https://github.com/angular/angular/issues/45155 which was resolved with the release of Angular 15.0.1: https://github.com/angular/angular/issues/46419

    Setting strictInjectionParameters to false in my target project fixed the compiler issue.

    Second was that during testing, I was specifying a local install path for the library in my target project. Even though I had not included @angular/core as a dependency in the library's package.json, the target project was apparently confused as to which @angular/core to use, resulting in this error at runtime:

    ERROR Error: NG0203: inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with `EnvironmentInjector#runInContext`. Find more at https://angular.io/errors/NG0203
        at injectInjectorOnly (core.mjs:731:15)
        at Module.ɵɵinject (core.mjs:742:60)
        at NgxEnvironmentService_Factory (ngx-environment.service.ts:11:35)
        at Object.EnvironmentService_Factory [as factory] (environment.service.ts:9:32)
        at R3Injector.hydrate (core.mjs:8780:35)
        at R3Injector.get (core.mjs:8668:33)
        at ChainedInjector.get (core.mjs:13929:36)
        at lookupTokenUsingModuleInjector (core.mjs:3547:39)
        at getOrCreateInjectable (core.mjs:3592:12)
        at Module.ɵɵdirectiveInject (core.mjs:10971:12)
    

    I set a paths value in my tsconfig.json:

    "paths": {
      "@angular/*": ["node_modules/@angular/*"],
    }
    

    which fixed the runtime error, however, after publishing my library to npm and installing from there, I was able to remove the path and the project now runs as expected.