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

Constructor parameter in service is not compatible with dependency injection after build #2487

Closed
alsoicode opened this issue Nov 24, 2022 · 17 comments

Comments

@alsoicode
Copy link

Type of Issue

[ X] Bug Report
[ ] Feature Request

Description

A bug: please describe the error that you encountered

In Angular 15 / ng-pckagr 15, after building and installing my library into another project, a service provided in my library reports:

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.(-992016)

However, when using the same service in test code within the library, everything works as expected

A feature: please describe your use case and motivation

I do not want developers leveraging my library to have to provide constructor arguments when extending this service class if they are not doing additional work in the constructor.

How To Reproduce

This is a private, scoped package, but I am more than happy to provide all source code.

To reproduce the issue, I build the library and install it into another Angular application using a local file path in package.json

Expected Behaviour

In all prior versions of this library, from Angular 11 - 14, everything works as expected. Updating from Angular/ng-packagr 14 to 15 builds and tests fine, but fails when implemented in another app.

Version Information

$ node_modules/.bin/ng-packagr --version
ng-packagr: 15.0.1
@angular/*: 15.0.0
typescript: 4.8.2
rxjs: 7.5.7
node: 18.12.0
npm/yarn: 8.19.2

Please include all version numbers that might be relevant, e.g. third-party libraries

@JoostK
Copy link
Member

JoostK commented Nov 24, 2022

This was a bug that will be fixed in the next Angular FW release.

@alan-agius4
Copy link
Member

Closing as per above.

@alan-agius4 alan-agius4 closed this as not planned Won't fix, can't repro, duplicate, stale Nov 24, 2022
@alsoicode
Copy link
Author

Does that mean Angular 15.x? or 16? :)

@JoostK
Copy link
Member

JoostK commented Nov 24, 2022

The next patch of v15.

@JoostK
Copy link
Member

JoostK commented Nov 24, 2022

If you're curious: angular/angular#48156

@alsoicode
Copy link
Author

After changing the tsconfig to set strictInjectionParameters to false, the project will build and run, but now results in a runtime error of:

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

I'm only leveraging the @Inject() decorator in the constructor of my service:

@Injectable({
  providedIn: 'root'
})
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>();
    }
  }

  private getEnvironmentValues = <T>(): T => {
    ...
  };

}

Any insight on this? https://angular.io/errors/NG0203 isn't particularly helpful

@JoostK
Copy link
Member

JoostK commented Nov 24, 2022

Is there a stacktrace you can share? The runtime semantics didn't change when the compiler became more strict.

@alsoicode
Copy link
Author

alsoicode commented Nov 24, 2022

Here's the stacktrace:

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)
handleError @ core.mjs:9229
(anonymous) @ core.mjs:27407
invoke @ zone.js:375
run @ zone.js:134
runOutsideAngular @ core.mjs:26496
(anonymous) @ core.mjs:27407
invoke @ zone.js:375
onInvoke @ core.mjs:26597
invoke @ zone.js:374
run @ zone.js:134
(anonymous) @ zone.js:1278
invokeTask @ zone.js:409
onInvokeTask @ core.mjs:26279
invokeTask @ zone.js:408
onInvokeTask @ core.mjs:26584
invokeTask @ zone.js:408
runTask @ zone.js:178
drainMicroTaskQueue @ zone.js:588
Promise.then (async)
nativeScheduleMicroTask @ zone.js:564
scheduleMicroTask @ zone.js:575
scheduleTask @ zone.js:399
scheduleTask @ zone.js:221
scheduleMicroTask @ zone.js:241
scheduleResolveOrReject @ zone.js:1268
then @ zone.js:1464
bootstrapModule @ core.mjs:27321
4431 @ main.ts:6
__webpack_require__ @ bootstrap:19
__webpack_exec__ @ ngx-environment.ts:3
(anonymous) @ ngx-environment.ts:3
__webpack_require__.O @ chunk loaded:23
(anonymous) @ ngx-environment.ts:3
webpackJsonpCallback @ jsonp chunk loading:34
(anonymous) @ main.js:2

The implementation of this library-provided service is:

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

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

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

Nothing too fancy. I'm just extending a service class and providing the type of the value returned by the getEnvironmentValues() method.

I've seen some posts like: angular/angular#46419, however my library's package.json only specifies peerDependencies:

{
  "name": "ngx-environment",
  "version": "15.0.1",
  "peerDependencies": {
    "@angular/common": ">=15.0.0",
    "@angular/core": ">=15.0.0",
    "rxjs": ">=7.5.6",
    "tslib": ">=2.4.0"
  }
}

@JoostK
Copy link
Member

JoostK commented Nov 24, 2022

The stack trace looks normal, but I suspect that you're seeing the effect of having two copies of @angular/core in your application. Only one of the runtimes has the correct state setup, but then calls into the other runtime fail because its state will not have been setup.

@alsoicode
Copy link
Author

That certainly explains why the source code works as expected when used directly in a project, but why/how would that happen if my package.json for the library only contains peer dependencies?

The resulting pacakge.json for the built library is:

{
  "name": "ngx-environment",
  "version": "15.0.1",
  "peerDependencies": {
    "@angular/common": ">=15.0.0",
    "@angular/core": ">=15.0.0",
    "rxjs": ">=7.5.6"
  },
  "module": "fesm2015/ngx-environment.mjs",
  "es2020": "fesm2020/ngx-environment.mjs",
  "esm2020": "esm2020/ngx-environment.mjs",
  "fesm2020": "fesm2020/ngx-environment.mjs",
  "fesm2015": "fesm2015/ngx-environment.mjs",
  "typings": "index.d.ts",
  "exports": {
    "./package.json": {
      "default": "./package.json"
    },
    ".": {
      "types": "./index.d.ts",
      "esm2020": "./esm2020/ngx-environment.mjs",
      "es2020": "./fesm2020/ngx-environment.mjs",
      "es2015": "./fesm2015/ngx-environment.mjs",
      "node": "./fesm2015/ngx-environment.mjs",
      "default": "./fesm2020/ngx-environment.mjs"
    }
  },
  "sideEffects": false,
  "dependencies": {
    "tslib": ">=2.4.0"
  }
}

@JoostK
Copy link
Member

JoostK commented Nov 24, 2022

It typically happens when you (sym)link the library into another application, where the link has access to a different node_modules directory.

@alsoicode
Copy link
Author

Hmm. For testing, I have specified the instal location as:

"ngx-environment": "/Users/btaylor/work/angular-apps/clinical-authoring-common/ngx-environment/angular-15/dist/ngx-environment",

but that doesn't automatically symlink to the best of my knowledge... please correct me if I'm wrong

@alsoicode
Copy link
Author

alsoicode commented Nov 24, 2022

I must be wrong, because when I added a path to my tsconfig.json:

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

and now the runtime error is gone.

@alsoicode
Copy link
Author

Looks like the local install path was the root cause of all of the issues. Once I published the package to npm and installed from there, everything works as expected. Do you know how I can avoid this in the future?

Thank you very much for the replies and helping debug this issue, I greatly appreciate it.

@JoostK
Copy link
Member

JoostK commented Nov 24, 2022

Hmm. For testing, I have specified the instal location as:

"ngx-environment": "/Users/btaylor/work/angular-apps/clinical-authoring-common/ngx-environment/angular-15/dist/ngx-environment",

but that doesn't automatically symlink to the best of my knowledge... please correct me if I'm wrong

This way there is no symlink indeed, but every source file in the /Users/btaylor/work/angular-apps/clinical-authoring-common/ngx-environment/angular-15/dist/ngx-environment directory will perform module resolution from within that path. Node module resolution algorithm will traverse the directory tree upwards within /Users/btaylor/work/angular-apps/clinical-authoring-common/ngx-environment/angular-15/dist/ngx-environment, and therefore also arrive at the node_modules directory that is reachable from there. This is likely a different installation than the app you're consuming it in, hence the duplicate module.

@alsoicode
Copy link
Author

Interesting. I will certainly remember that in the future. Thanks again for all your help.

@github-actions
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

This action has been performed automatically by a bot.

@github-actions github-actions bot locked and limited conversation to collaborators Dec 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

No branches or pull requests

3 participants