Angular-cli: AOT build breaks with InjectionToken used cross library boundaries

Created on 18 Oct 2017  路  14Comments  路  Source: angular/angular-cli

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x ] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

I get the following error when using "ng build --prod"

ERROR in Error: Internal error: unknown identifier undefined
    at Object.importExpr$$1 [as importExpr] (C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:24097:23)
    at tokenExpr (C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:18447:39)
    at providerDef (C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:18349:20)
    at C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:18574:77
    at Array.map (<anonymous>)
    at NgModuleCompiler.compile (C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:18574:44)
    at AotCompiler._compileModule (C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:24030:32)
    at C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:23942:66
    at Array.forEach (<anonymous>)
    at AotCompiler._compileImplFile (C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:23942:19)
    at C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:23855:87
    at Array.map (<anonymous>)
    at AotCompiler.emitAllImpls (C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler\bundles\compiler.umd.js:23855:52)
    at CodeGenerator.emit (C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler-cli\src\codegen.js:42:46)
    at C:\d\ContentHub\samples-wch-sdk\navigation\node_modules\@angular\compiler-cli\src\codegen.js:33:61
    at <anonymous>

As far as I can tell, the undefined identifier is the value of the provider field in a ClassProvider. Doing a console log for the AST node yields the following result:

providerAST {"token":{"identifier":{"reference":{"filePath":"C:/d/ContentHub/samples-wch-sdk/navigation/node_modules/@angular/common/common.d.ts","name":"NgLocalization","members":[]}}}
,"multiProvider":false,"eager":false,"providers":[{"token":{"identifier":{"reference":{"filePath":"C:/d/ContentHub/samples-wch-sdk/navigation/node_modules/@angular/common/common.d.ts","
name":"NgLocalization","members":[]}}},"useClass":{"reference":{"filePath":"C:/d/ContentHub/samples-wch-sdk/navigation/node_modules/@angular/common/common.d.ts","name":"NgLocaleLocaliza
tion","members":[]},"diDeps":[{"isAttribute":false,"isHost":false,"isSelf":false,"isSkipSelf":false,"isOptional":false,"token":{"identifier":{"reference":{"filePath":"C:/d/ContentHub/sa
mples-wch-sdk/navigation/node_modules/@angular/core/core.d.ts","name":"LOCALE_ID","members":[]}}}}],"lifecycleHooks":[]},"useFactory":null,"deps":[{"isAttribute":false,"isHost":false,"i
sSelf":false,"isSkipSelf":false,"isOptional":false,"token":{"identifier":{"reference":{"filePath":"C:/d/ContentHub/samples-wch-sdk/navigation/node_modules/@angular/core/core.d.ts","name
":"LOCALE_ID","members":[]}}}}],"multi":false}],"providerType":0,"lifecycleHooks":[],"sourceSpan":{"start":{"file":{"content":"","url":"in NgModule WchNgLoggingModule in C:/d/ContentHub
/samples-wch-sdk/navigation/node_modules/ibm-wch-sdk-ng-logger/ibm-wch-sdk-ng-logger.d.ts"},"offset":-1,"line":-1,"col":-1},"end":{"file":{"content":"","url":"in NgModule WchNgLoggingMo
dule in C:/d/ContentHub/samples-wch-sdk/navigation/node_modules/ibm-wch-sdk-ng-logger/ibm-wch-sdk-ng-logger.d.ts"},"offset":-1,"line":-1,"col":-1},"details":null}}
providerAST {"token":{"identifier":{}},"multiProvider":false,"eager":false,"providers":[{"token":{"identifier":{}},"useClass":{"reference":{"filePath":"C:/d/ContentHub/samples-wch-sdk/n
avigation/node_modules/ibm-wch-sdk-ng-logger/ibm-wch-sdk-ng-logger.d.ts","name":"傻b","members":[],"test":"Carsten"},"diDeps":[],"lifecycleHooks":[]},"useFactory":null,"deps":[],"multi":
false}],"providerType":0,"lifecycleHooks":[],"sourceSpan":{"start":{"file":{"content":"","url":"in NgModule WchNgLoggingModule in C:/d/ContentHub/samples-wch-sdk/navigation/node_modules
/ibm-wch-sdk-ng-logger/ibm-wch-sdk-ng-logger.d.ts"},"offset":-1,"line":-1,"col":-1},"end":{"file":{"content":"","url":"in NgModule WchNgLoggingModule in C:/d/ContentHub/samples-wch-sdk/
navigation/node_modules/ibm-wch-sdk-ng-logger/ibm-wch-sdk-ng-logger.d.ts"},"offset":-1,"line":-1,"col":-1},"details":null}}
tokenMeta {"identifier":{}} {"statements":[],"genFilePath":"C:/d/ContentHub/samples-wch-sdk/navigation/node_modules/ibm-wch-sdk-ng-logger/ibm-wch-sdk-ng-logger.ngfactory.ts"}```

The scenario is this:

  • I have an ng4 CLI application using the standard CLI build
  • this application uses two libraries A and B, both ng4 libraries built against the ng4 module spec
  • A defines an InjectionToken that allows to inject services
  • B provides a service using the Injection token provided by A
    B'm module looks like this (in TS)
// the provider
export const PROVIDER: InjectionToken<LoggerFactory> = WchLoggerFactory;

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [
  ],
  providers: [{ provide: PROVIDER, useClass: Ng2LoggerFactory }],
  exports: [
  ],
  entryComponents: [
  ]
})
export class WchNgLoggingModule {

Where 'WchLoggerFactory' is imported from A.

The problem only occurs if I use modules, if I build everything in one large monolithic application, the build works.

Expected behavior

I expect that InjectionTokens can be used across library boundaries to provide services.

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?

Environment


Angular CLI version: 1.4.7

For Tooling issues:
- Node version: v8.6.0
- Platform:  Windows 7

Others:

angulacli low investigation broken bufix

Most helpful comment

I'm having this same issue with the exact same error, it builds find in dev but fails with this error with --prod. Any idea how to fix it?

All 14 comments

This sounds like the build might be using several Angular packages instead of the single one (for the main app). This can happen depending on how you install your other libraries.

Have you looked at the wiki page for this sort of thing? You can find it in https://github.com/angular/angular-cli/wiki/stories-linked-library. If you can provide me with a reproduction of the behaviour you're seeing now I can have a look as well.

I'm having this same issue with the exact same error, it builds find in dev but fails with this error with --prod. Any idea how to fix it?

You can use https://github.com/CarstenLeue/angular-build-problem as a test repository.

None of the npm packages is linked (at least not explicitly), the dependencies are just installed via npm install

+1

+1

Attached is a minimal project in a zip file that reproduces the problem. Under node_modules/@abc/def is a library that exports an InjectionToken.

export const AbcServices = {
    'HelpService': new InjectionToken('IAbcHelpService'),
};

In app.module, the InjectionToken from the library is used in the Provides clause:

  { provide: AbcServices.HelpService, useClass: HelpService }

This project runs correctly with "ng serve", and "ng build", but "ng build --prod" and "ng build --aot" fail with "Error: Internal error: unknown identifier".

To run it you will need to run npm install to get the other dependencies, while preserving the node_modules/@abc/def library.

InjectionTokenErrorSample.zip

For the above zip file reproduction, the following syntax fixes the issue and appears to run in aot and production build mode:

export const HELP_SERVICE = new InjectionToken('IAbcHelpService');

export const AbcServices = {
    'HelpService': HELP_SERVICE
};

This fixes the problem that I was encountering.

I Have similar problem...

I use @nrwl/nx, tokens are defined in @framework library and used in app.module to provide instances. In non-production build everything works as expected.

In production build, everything is built without any error, however instanced are NOT provided.

I had a similar problem:
I provided a Mock service in Module A, and at app-module I provided the real service and import module A.

With AOT set to false everything is good. but if I set AOT to true it seems like module A is taking precedence with the mock service, which is unexpected behavior.

If I'm dropping the provider from Module A - the page won't load at all (only when AOT = true) with an error: NullInjectorError: No provider for InjectionToken MyInjectionToken.

My Solution

I found a solution for this by using MyModule.forRoot(...) as described here at the end of the page (which is actually the same as RouterModule approach).

If someone can suggest a better solution I would like to hear.

Looks like a problem with old version of the CLI. Let us close the issue for now. If it's still relevant, please open a new ticket with steps to reproduce and a minimal demo. Thanks!

@mgechev my problem was (is) that you cannot use .map() to map Array to "provide array"

i mean:

forRoot(objects: any[]) {
return {
ngModule: ModuleA,
providers: [ objects.map(x => ({ provide... })) ]
};
}

it works in DEV mode, but NOTHING is provided in AOT mode (without any error with some conditions...)

Propably can be fixed with (no reason to do that) "instance creator"

forRoot(objects: any[]) {
return {
ngModule: ModuleA,
providers: creator(objects)
};
}

export function creator(...) {...}

@montella1507 that's a constraint set by ngc (the Angular compiler). We may relax these constraints in the future, but until then you should extract the mapping logic elsewhere. To allow Angular to optimize the dependency injection, we perform compilation of the providers which set constraints on the expressions you can use.

thanks for explain.

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

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings