angular: Function calls are not supported in decorators when fullTemplateTypeCheck is not specified and @dynamic has no effect

I’m submitting a…


[X] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] 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
[ ] Other... Please describe:

Current behavior

When building a library and specifying both skipTemplateCodegen and strictMetadataEmit to true, and you try to call a method inside the ngModule decorate example RouterModule.forChild([]) this will cause a compilation error Function calls are not supported in decorators but 'RouterModule' was called.

When adding fullTemplateTypeCheck to true the error is not emitted.

Also, @dynamic seems not to have any effect in this particular case.

Expected behavior

No error emitted, or at least that it can be suppressed with the @dynamic.

I also expect that setting fullTemplateTypeCheck doesn’t have any effect here as this flag relates more to binding in templates.

Minimal reproduction of the problem with instructions

Create a file example

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

@NgModule({
  imports: [
    RouterModule.forChild([])
  ]
})
export class LibModule { }

Have tsconfig set with the following options;

  "angularCompilerOptions": {
    "skipTemplateCodegen": true,
    "strictMetadataEmit": true
  }

When transpiling this will emit an error;

Error during template compile of 'LibModule' Function calls are not supported in decorators but 'RouterModule' was called.

Note when adding "fullTemplateTypeCheck": true no error is emitted. (Though this is kinda weird as I don’t think this shouldn’t have any effect)

Reproduction repo: https://github.com/alan-agius4/angular-issue-23609

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

No error was emitted in NG 5

Environment


Angular version: 6.0.0-rc-6

Related issues: https://github.com/dherges/ng-packagr/issues/822 https://github.com/dherges/ng-packagr/issues/778 https://github.com/dherges/ng-packagr/issues/727 https://github.com/dherges/ng-packagr/issues/765 https://github.com/dherges/ng-packagr/issues/767 https://github.com/dherges/ng-packagr/issues/885

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 108
  • Comments: 117 (32 by maintainers)

Commits related to this issue

Most upvoted comments

Regarding Ward’s repro: @wardbell

The build will succeed / fail depending on the combination of angularCompilerOptions. Added one example in this repro

Build success

    "skipTemplateCodegen": true,
    "strictMetadataEmit": true,
    "fullTemplateTypeCheck": true
    "skipTemplateCodegen": false,
    "strictMetadataEmit": false
    "skipTemplateCodegen": false,
    "strictMetadataEmit": true,
    // fullTemplateTypeCheck omitted (= default value)

Build failures

    "skipTemplateCodegen": true,
    "strictMetadataEmit": true,
    // fullTemplateTypeCheck omitted (= default value)
    "skipTemplateCodegen": true,
    "strictMetadataEmit": false
    // fullTemplateTypeCheck omitted (= default value)

Observation

In Ward’s example, setting "skipTemplateCodegen": true requires that "fullTemplateTypeCheck": true is also enabled to get a success build from ngc.

Side notes / other thoughts

My experience is that the issue goes down to “everything statically analyzable for AoT” (see “need for static value resoltion” in the compiler docs). Imo, something is broken around the strictMetadataEmit option or my understanding of the option is horribly broken 😆

From my experience I can tell that the issue is often produced in static forRoot(): ModuleWithProviders, sometimes depending on one line of code that becomes or becomes not “statically analyzable”. Of course, I don’t have reliable repros and I also don’t want to speculate on some vague memories of code that I’ve seen working / non-working.

Can ngc improve the error message?

What will help that ngc prints out the line number in the source code that triggers the error.

To the next poor soul who comes across this ridiculously long chain of comments, and historically difficult compile error to resolve. I hope you find this comment, and it helps you.

My library uses a forRoot() static function to provide a configuration. This library compiles fine and had no issues working with an Angular application compiled with 8.2 or lower, but this error appears as soon as I started using 8.3 or higher to compile an application that uses the library.

The error specifically was this:

ERROR in Error during template compile of 'MainModule'
  Function calls are not supported in decorators but 'LoggerModule' was called.
Unexpected value 'undefined' imported by the module 'MainModule in src/app/main/main.module.ts'
Error during template compile of 'MainModule'
  Function calls are not supported in decorators but 'LoggerModule' was called.

The MainModule is in my application, and LoggerModule was in the library. The library was compiled with 8.0 and works fine with apps upto 8.2, but breaks with 8.3 or higher.

Here are the steps that you can try if this happens to you:

Make sure that your tsconfig.lib.json has the following

  "angularCompilerOptions": {
    "annotateForClosureCompiler": true,
    "skipTemplateCodegen": true,
    "strictMetadataEmit": true,
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true,
    "enableResourceInlining": true
  },

Now for the NgModule that has the static function it’s really important to do the following things:

  • you can not have any source code inside the function. It can only contain a return statement that yields a ModuleWithProviders object.
  • you can not use an arrow function with any providers
  • any functions used in any providers should have an export but don’t have to be exposed in your public_api
  • I had to add a // @dynamic above my @NgModule()
  • you can not use any source code to dynamically generate the array of providers
  • I don’t think this helps, but I added a generic type for the return value ModuleWithProviders<LoggerModule>

The above restrictions complicated things fo mer, because I had to figure out how to fix this issue while remaining compatible with everyone who was using the library.

This is what the LoggerModule ended up being, and this works for me:

export const LOGGER_OPTIONS: InjectionToken<LoggerConfig> = new InjectionToken<LoggerConfig>('LOGGER_OPTIONS');

export function LogServiceFactory(
    levels: LOGGER_LEVEL,
    console: ConsoleMethods<void>,
    prefixService: PrefixService,
    loggerConfig: LoggerConfig
) {
    return loggerConfig && loggerConfig.enabled
        ? new LogConsoleService(levels, console, prefixService)
        : new LogNoopService();
}

// @dynamic
@NgModule({})
export class LoggerModule {
    public static forRoot(loggerConfig?: LoggerConfig): ModuleWithProviders<LoggerModule> {
        return {
            ngModule: LoggerModule,
            providers: [
                {provide: LOGGER_LEVELS, useValue: loggerConfig.levels || LOGGER_ALL},
                {provide: LOGGER_TAILS, useValue: loggerConfig.tails || LOGGER_TAILS_DEFAULT},
                {provide: LOGGER_CONSOLE, useValue: loggerConfig.console || console},
                {provide: LOGGER_OPTIONS, useValue: loggerConfig || {}},
                {
                    provide: LogService,
                    useFactory: LogServiceFactory,
                    deps: [LOGGER_LEVELS, LOGGER_CONSOLE, PrefixService, LOGGER_OPTIONS]
                }
            ]
        };
    }
}

The key things to notice is that the forRoot() is just a function that returns a module metadata, and there is no other source code. The other thing is that this limitation forced me to use a factory, and then figure out how to pass the loggerConfig options to the factory method.

As a comparison, I’ll also share below what the original source code was that broke so you can see how dramatically different the fix is from the original. The fact that I got this working is in itself a miracle.

@NgModule()
export class LoggerModule {
    public static forRoot(options?: LoggerConfig): ModuleWithProviders {
        options = Object.assign({
            enabled: true,
            levels: LOGGER_ALL,
            tails: LOGGER_TAILS_DEFAULT,
            console: console
        } as LoggerConfig, options || {});

        const providers: Provider[] = [
            {provide: LOGGER_LEVELS, useValue: options.levels},
            {provide: LOGGER_TAILS, useValue: options.tails},
            {provide: LOGGER_CONSOLE, useValue: options.console}
        ];

        if (options && options.enabled) {
            providers.push({provide: LogService, useClass: LogConsoleService});
        } else {
            providers.push({provide: LogService, useClass: LogNoopService});
        }

        return {ngModule: LoggerModule, providers};
    }
}

This is the second highest commented issue in the past year, second only to the Ivy tracking issue. It’s a fundamental problem with AOT and its status is backlog. Can we “needsTriage” this?

It helps me

import { someCoreModule } from '@myPackage/core';

export const someCoreModuleforRoot = someCoreModule.forRoot(); // Without "export" build will crash

@NgModule({
    imports: [
        someCoreModuleforRoot
    ]
})
export class MyModule {
}
"ng-packagr": "4.2.0"
"@angular/cli": "7.3.8"

Can anyone tell a nub what should I do for now to workaround this issue? ng build --prod I’m using ngx-progressbar and i get : “Function calls are not supported in decorators but ‘NgProgressModule’ was called.”

I found another workaround that isn’t as ugly as my prior workaround. You can see where I found this, here: https://github.com/dschnelldavis/angular2-json-schema-form/issues/273#issuecomment-407184242. So, here is my workaround for the example I gave above with ngrx.


export const storeModuleForRoot: ModuleWithProviders = StoreModule.forRoot(prmCoreActionReducers, { initialState: initialPrmCoreState })
export const effectsModuleForRoot: ModuleWithProviders = EffectsModule.forRoot([ApiEffects, GridEffects])
export const storeDevToolsModuleForRoot: ModuleWithProviders = StoreDevtoolsModule.instrument()

@NgModule({
  imports: [
    effectsModuleForRoot,
    storeModuleForRoot,
    storeDevToolsModuleForRoot,
  ],
})
export class PrmCoreStoreModule { }

Please note that you must declare ModuleWithProviders, otherwise, another error occurs.

I fixed that by replacing import of RouterModule.forChild(routes) with the previously defined constant: export const routerModule = RouterModule.forChild(routes);

I started getting this issue in one of my libraries once I repackaged it with Angular 7.0 (up from 6.x). Tried every applicable suggestion in this thread with no luck. My forRoot is as static as they get.

Library

./misc/injection-tokens.ts

import {InjectionToken} from '@angular/core';
import {NgForageOptions} from '../config/ng-forage-options';

export const DEFAULT_CONFIG = new InjectionToken<NgForageOptions>('Default NgForage config');

NgForage.module.ts

import {ModuleWithProviders, NgModule} from '@angular/core';
import {NgForageOptions} from './config/ng-forage-options';
import {DEFAULT_CONFIG} from './misc/injection-tokens';

/**
 * NgForage core module
 */
@NgModule({})
export class NgForageModule {

  public static forRoot(config: Partial<NgForageOptions>): ModuleWithProviders<NgForageModule> {
    return {
      ngModule: NgForageModule,
      providers: [
        {
          provide: DEFAULT_CONFIG,
          useValue: config
        }
      ]
    };
  }
}

Test app

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ModuleWithProviders } from '@angular/core';
import { NgForageModule } from 'ngforage';
import { AppComponent } from './app.component';

const ngfm: ModuleWithProviders<NgForageModule> = NgForageModule.forRoot({});

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ngfm
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Related bug reports in the library repo:

  • Alorel/ngforage#107
  • Alorel/ngforage#102

For the record, I have this problem with latest angular and ng-packagr (3.0.0) and the three aforementioned parameters are set to true (this is the default when generating a library with angular-cl). So I’m not sure the PR you made @JoostK actually fixes this problem… or something new appeared with latest versions. This is really a pain in the ass because there is no way to understand why something works or not (at one point I made it work, but I am unable to know why or how I can go back to this working state), I second @dherges request to have better error output…

EDIT: I found the source of my problem, it was a wrong paths declaration in tsconfig preventing metadata to be found. Neverttheless, we are really missing something to diagnose those problems…

I’ve managed to fixed this by re-exporting the actual file in public_api.ts

Example of the issue:

File ~/lib/index.ts export * from './sample/data.component';

File ~/public_api.ts export * from './lib';

This would work on ng build --prod --aot=false, but it won’t build to ng build --prod

to fix: File ~/public_api.ts export * from '.lib/sample/data.component;'

I think the answer will always be the same… Will be much easier to fix once ivy is out

Let’s just brace ourselves and wait for ivy to come 🤷🏻‍♂️

This has arguably been the most annoying error that libraries have had to deal with, and I’m glad that this class of issues has been resolved with the Ivy compiler.

With v12 now allowing libraries to be compiled with the Ivy compiler, I am closing this issue as resolved.

Any update ? It’s blocking our migration from angular 5 to 6. Our libraries contains 3 forRoot. I don’t like the solution to copy the forRoot code of the external libraries to our project.

Hi @jpike88, all.

This is driving me insane. How is a line of the problem code not provided with an error like this?

I agree, it’s pretty crazy that no line/column context for the error is provided. Unfortunately it’s not an easy fix - the problem is more systemic than adding some missing info to the error message.

ngc (specifically, the View Engine compiler) begins by extracting “metadata” from the source code. Each .ts file becomes a JSON structure describing it. The rest of the compiler operates on this metadata. Contextual information (line/col numbers) are captured for some structures, but not all, as that would greatly increase the metadata size and hurt compiler performance.

The validation done by strictMetadataEmit happens on this metadata, and so whether or not the line/col context of an error can be displayed depends on the type of error and whether the metadata has captured this information in the first place. The two systems are relatively separate, and so it’s not always guaranteed that contextual information will be available.

That’s also why you see little quality-of-life bugs like this not getting addressed over the last couple years. Rather than try and deal with all of the issues like this individually, we’ve been focused on replacing the metadata design as a whole with a system that doesn’t have these kinds of issues. The Ivy compiler is incredibly good at localizing errors and telling you exactly why something isn’t valid, because that has been one of its design goals since day 1.

So that’s really our fix for this issue - Ivy will eventually make confusing and inconsistent metadata errors like this a thing of the past. With libraries the situation is a little complicated since for backwards compatibility, they still have to publish in View Engine format and thus deal with strictMetadataEmit for a little longer, but that’s going to change sooner rather than later, hopefully.

@rahulsahay19 honestly? You are screwed, this is hell and nobody care 😃 I’ve stopped trying to solve the problem.

The only thing that I found to work is to not use forRoot at all and to duplicate in each module that use your module the content of the providers map of forRoot. But your mileage may vary 😃

Thanks to help from @samherrmann, I have been able to get my code working.

It appears that the AOT-enabled compilation process has problems dealing with import statements that rely on index.ts files inside directories.

Firstly, an explicit export statement for the Angular Modules was necessary within the library public_api.ts file to get rid of the error Function calls are not supported in decorators but 'Module' was called..

But even after that, all directory-based import statements within the library code, which rely on index.ts files within those directories to export the other modules, had to be changed to import directly from the module files themselves.

i.e. change:

import { TestService } from './services';

to

import { TestService } from './services/test.service';

Look at https://github.com/kiranjholla/ng-issue-23609-repro/issues/1#issuecomment-417449088 for details.

@danielmhair Thanks! I actually had tried your fix too and that too had not worked for me.

However, I have now discovered that the problem wasn’t in this piece of code or the fixes that had been suggested here, but rather in a different piece of my code. It was in the way I was deriving the config object that was being passed into this forRoot method when importing the AppSettingsModule.

The Problem

/**
 * main.module.ts
 *
// Angular & Lodash
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { assign } from 'lodash';

// Application Modules
import { coreConfig } from '../modules/core';
import { userConfig } from '../modules/user';

// Components
import { MainComponent } from './components/main.component';

const appConfig = assign({}, coreConfig, userConfig);     // <--- This is where the problem came from!!

@NgModule({
  declarations: [
    MainComponent
  ],
  imports: [
    BrowserModule,
    // Application-specific modules
    AppSettingsModule.forRoot(appConfig)
  ],
  providers: [],
  bootstrap: [MainComponent]
})
export class MainModule { }

The Fix

To fix this, I replaced this with

/**
 * main.module.ts
 *
@NgModule({
  declarations: [
    MainComponent
  ],
  imports: [
    BrowserModule,
    // Application-specific modules
    AppSettingsModule.forRoot(coreConfig, userConfig)
  ],
  providers: [],
  bootstrap: [MainComponent]
})
export class MainModule { }


/**
 * app.settings.module.ts
 *
export const CoreSettingsObject = new InjectionToken('CoreSettingsObject');
export const UserSettingsObject = new InjectionToken('UserSettingsObject');

export function createAppSettingsService(core, user) {
  return new AppSettingsService(assign({}, core, user));
}

@NgModule({
  imports: [
    CommonModule
  ]
})
export class AppSettingsModule {
  static forRoot(core: Object, user: Object): ModuleWithProviders {
    return {
      ngModule: AppSettingsModule,
      providers: [
        { provide: CoreSettingsObject, useValue: core },
        { provide: UserSettingsObject, useValue: user },
        {
          provide: AppSettingsService,
          useFactory: (createAppSettingsService),
          deps: [CoreSettingsObject, UserSettingsObject]
        }
      ]
    };
  }
}

The trick seems to be to ensure that there is absolutely no code-execution necessary before the forRoot is invoked. All executable code should be only within the factory. The forRoot method can only deal with completely static values.

I think I had the same issue. In my case the metadata of the used library was not generated correctly:

"metadata": {}

Then I changed the public_api.ts in the library project:

# from
export * from './lib/auth';
# to
export * from './lib/auth/index';

Now it works fine. Maybe this helps to resolve the issue?

Toggle Ivy in a library

"angularCompilerOptions": {
   "skipTemplateCodegen": true,
   "strictMetadataEmit": true,
   "fullTemplateTypeCheck": true,
   "enableResourceInlining": true,
   "enableIvy": false
 }
 
 "angularCompilerOptions": {
     "skipTemplateCodegen": true,
     "strictMetadataEmit": true,
     "enableResourceInlining": true,
     "enableIvy": true
   }

I wanted to comment again, because I have came into this error for the 5th time while updating all my demo apps, main app and libraries (3 demos, 1 main app, and 3 libraries). I receive these errors in various modules. One was referenced above (formly-js/ngx-formly#996) and then another that I just receive from @ngrx/core, where it says the same error:

Error during template compile of 'CoreStoreModule'
  Function calls are not supported in decorators but 'StoreModule' was called.

The other 3 are from forRoot methods in my own modules. The common theme with all these, is that they all use InjectionTokens. I’m quite convinced that this is only related to modules that use forRoot methods providing a value for an InjectionToken.

This is forcing me to copy all that is in those forRoot methods and paste them into the module I need them in. This is becoming very taxing, especially when those InjectionTokens are from libraries I don’t maintain.

Anyway, I really appreciate all the effort that has gone into resolving this error. If there is anything I can do to help with this, I’m open to help. I’m not really sure what to do, to help with this one. Seems like its pinned down on what is happening. Hopefully it can get resolved soon.

I would suggest my fix. Its temporary, but not overbearing in how bad it looks. Not sure if it will help, but it helped me in about 5 scenarios quite like this.

In MyAppModule, when you call forRoot, have it be exported into a const variable such as:

export const appSettingsModuleForRoot: ModuleWithProviders = AppSettingsModule.forRoot(yourConfig)

...
imports: [
  ...,
  appSettingsModuleForRoot,
],
...

Make sure you import ModuleWithProviders from @angular/core and that you put it as a typing. It might not work if you do not type the appSettingsModuleForRoot variable.

After reading @leonardochaia 's answer I just fixed this error on my lib import… by removing a console.log() statement from the forRoot method declaration

After some struggle it seems that forRoot must be a function with a return statement and nothing more.

This menas that, variables and functions can not be used inside the method.

WORKS

 public static forRoot(config: ITimoneerTab[]): ModuleWithProviders {
  return {
    ngModule: TabsModule,
    providers: [
      [...]
      {
        provide: APPLICATION_TABS,
        useValue: config
      }
    ]
  };
}

DOESNT WORK Storing the object in a variable, or using any function breaks the build.

public static forRoot(config: ITimoneerTab[]): ModuleWithProviders {
  const output = {
    ngModule: TabsModule,
    providers: [
     [...]
      {
        provide: APPLICATION_TABS,
        useValue: config
      }
    ]
  };

  return output;
}

Commit fixing this in Timoneer

ng build --prod
ERROR in Error during template compile of 'AppTabsModule'
  Function calls are not supported in decorators but 'TabsModule' was called.

A good example is the RouterModule

@shairez I wonder if it is that issue. My problem is, due to my mono-repo, I need to link my libraries to my application via npm link. Hopefully there is another solution.

I had a similar error which only happened using yarn link. I fixed it by installing from the source.

+1 For now, it’s fine to leave provide configuration inside the module which is using my lib, but in the future would be great if someone could come and say the status of this fix.

According to official doc, Ivy is still not recommended for libraries:

That is something we should fix in the docs, then, as it’s not the case anymore since v12.

Other than that I think I got beaten by @NiklasPor, who has just given the correct answer 😃

Hey, there; Didn’t downvote 😃 ; you probably mistyped me instead @SergeyMell

@kroeder You, sir, made my day! Thanks!

Does anyone know whether this is documented? And why this happens?

I fixed the error by following https://github.com/angular/angular/issues/23609#issuecomment-561615821

  • you can not have any source code inside the function. It can only contain a return statement that yields a ModuleWithProviders object.

I had to remove this

@NgModule({
    imports: [
        // [...]
    ]
})
export class CoreModule implements OnDestroy {

    private static showUserInfo = true;

    static forRoot(options: CoreOptions = {}): ModuleWithProviders<NextCoreModule> {
        // this line caused the error
        // NextCoreModule.showUserInfo = options.showUserInfo ?? true;

        return {
            ngModule: NextCoreModule,
            providers: [
                {
                    provide: APP_INITIALIZER,
                    useFactory: initialize,
                    deps: [
                        ConfigBuilderService, AppKeyService, TranslateService, Title, coreOptionsToken
                    ],
                    multi: true
                },
                { provide: coreOptionsToken, useValue: options || {} }
            ]
        };
    }

    // [...]
}

every angular version, i have to patch angular compiler to suppress this error.

enable-angular-compiler-annotation.5.2.9.patch
enable-angular-compiler-annotation.5.2.10.patch
enable-angular-compiler-annotation.6.0.1.patch
enable-angular-compiler-annotation.6.0.2.patch
enable-angular-compiler-annotation.6.1.10.patch
enable-angular-compiler-annotation.7.1.0.patch

the patch file almost like this

Index: node_modules/@angular/compiler/bundles/compiler.umd.js
IDEA additional info:
<+>UTF-8
===================================================================
--- node_modules/@angular/compiler/bundles/compiler.umd.js	(date 1543397797000)
+++ node_modules/@angular/compiler/bundles/compiler.umd.js	(date 1543398810000)
@@ -24526,6 +24526,14 @@
             this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), createSelf);
             this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), createSkipSelf);
             this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Optional'), createOptional);
+            this._registerDecoratorOrConstructor(this.findDeclaration('@pkg/transform-proxy', 'TPProxyParam'), makeMetadataFactory('TPProxyParam'));
+            this._registerDecoratorOrConstructor(this.findDeclaration('@pkg/transform-proxy', 'TPProxyRequestBody'), makeMetadataFactory('TPProxyRequestBody'));
+            this._registerDecoratorOrConstructor(this.findDeclaration('@pkg/transform-proxy', 'TPProxyRequest'), makeMetadataFactory('TPProxyRequest'));
         };
...
...
...

sh.exec(patch -p0 -i tools/cli-patches/version/enable-angular-compiler-annotation.${version}.patch);

So I went to apply my changes to a second library I have and I discovered something disturbing and “explains” why my minor change to my setup above started behaving differently. My minor change above was mostly just that I added a different tsconfig.json file for building vs. serving. So for building I added the --ts--config <tsconfig.json> to the build. I ran into a different error now on this other library and discovered in trying to debug it that …

ng build

and

ng build --ts-config tsconfig.json

give different results! What? I’m passing it the default json file and the first works fine and the second gives me my error. What the heck is going on with that?

I had an issue with this and solved it. It killed a whole day (thanks for that…) but it looks like I got around it.

(For the record, no amount of jiggling around angularCompilerOptions, or exported functions, etc, had any effect).

My issue was, that I wanted to import a module, in a module that was imported by a lazy loaded module, based on the environment data fed in from the top level application. The lazy loaded module can be used in any application in the repo, so the environment data needs to always come from whatever app.module is doing the lazy loading (so the environment data can change).

I figured forRoot was the way to go about it, so did my thing (simplified code below). Note that the class containing the forRoot function does NOT have an ngModule deco (since I’m returning the one from the forRoot function, which is complete, believe it or not the approach came from my “working without TS” angular days, where you didn’t have all the fancy decos):

static forRoot ( environment ): ModuleWithProviders {
     @NgModule ( {
        imports: [ environment.production ? ThisModule : ThatModule ],
        providers: [ Provider1, Provider2 ]
      } )
     class ConfiguredMod {}
     return { ngModule: ConfiguredMod }
}

Worked like a charm…until --prod. Then the horrible error we all see here.

After reading everything there is to read on the subject (including the entire Angular Compiler doc), I came up with this, and it worked. Not pretty but was critical, a big part of my “super tooled mono repo” strategy depends on it.

In the module file:

    @NgModule ( {
        imports: [ THISModule ],
        providers: [ Provider1, Provider2 ]
      } )
     export class ConfiguredTHISMod {}

    @NgModule ( {
        imports: [ THATModule ],
        providers: [ Provider1, Provider2 ]
      } )
     export class ConfiguredTHATMod {}
    ....
    static forRoot ( environment ) {
        return { ngModule: ( environment.production ? ConfiguredTHISMod : ConfiguredTHATMod ) };
    }

If I did this:

    static forRoot ( environment ) {
        const mod = environment.production ? ConfiguredTHISMod : ConfiguredTHATMod;
        return { ngModule: mod };
    }

Or any manner of checking that incoming environment object, like this:

    static forRoot ( environment ) {
        environment = ( environment && 'production' in environment ? environment : { production : false } );
       return { ngModule: ( environment.production ? ConfiguredTHISMod : ConfiguredTHATMod ) };
    }

It broke again.

I have some suspicions as to why this works, in a way I think it makes sense based on what I read in the compiler docs, and although it’s not typically done I suspect maybe typing the environment object might have some effect, but I’d have to admit I’d be taking educated guesses at best, so I’ll forego the rationale. At this point I’m just happy I figured it out before having to go into overtime.

Hope that helps somebody out there.

@rahulsahay19 the error is definitely not ideal and this is an issue we should fix; sorry for your frustration. If downgrading to version 6 would be the easiest workaround for you, you can go ahead. We didn’t do major changes between v6 and v7.

OK, I spoke too soon.; this is like peeling an onion! This issue is still not completely fixed.

Context

The AppSettingsModule that I keep referring to in my previous posts in the thread above are actually part of a different Library application within the same Angular CLI workspace.

As I was working on this project, to investigate another issue, I had temporarily updated my tsconfig.json to point to the Library application directly instead of via the dist folder.

Original tsconfig.json settings
{
    ...
    "paths": {
      "my-lib": [
        "dist/my-lib"
      ],
      "my-lib/*": [
        "dist/my-lib/*"
      ]
    }
}
Modified tsconfig.json settings
{
    ...
    "paths": {
      "my-lib": [
        "projects/my-lib"
      ],
      "my-lib/*": [
        "projects/my-lib/*"
      ]
    }
}

With the above modified settings, the build works just fine.

However, if I revert the tsconfig.json to the original settings, the build fails.

I have now created a github repo to illustrate this problem. If you clone that repo and run npm run repro, you will see the error I am referring to.

How should shared modules that use data passed in from the Application context for initialization be handled? I am trying the solution suggested in https://github.com/angular/angular-cli/issues/9358#issuecomment-373053053, but unsuccessfully.

My scenario is as below:

> ng --version
Angular CLI: 6.1.4
Node: 10.4.1
OS: win32 x64
Angular: 6.1.4
... animations, cli, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

Package                            Version
------------------------------------------------------------
@angular-devkit/architect          0.7.4
@angular-devkit/build-angular      0.7.4
@angular-devkit/build-ng-packagr   0.7.5
@angular-devkit/build-optimizer    0.7.4
@angular-devkit/build-webpack      0.7.4
@angular-devkit/core               0.7.4
@angular-devkit/schematics         0.7.4
@angular/cdk                       6.4.6
@angular/flex-layout               6.0.0-beta.17
@angular/material                  6.4.6
@ngtools/json-schema               1.1.0
@ngtools/webpack                   6.1.4
@schematics/angular                0.7.4
@schematics/update                 0.7.4
ng-packagr                         3.0.6
rxjs                               6.2.2
typescript                         2.7.2
webpack                            4.9.2

My Code:

import { NgModule, ModuleWithProviders, InjectionToken } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AppSettingsService } from './services/app-settings.service';

export const AppSettingsObject = new InjectionToken('AppSettingsObject');

export function createAppSettingsService(settings) {
  return new AppSettingsService(settings);
}

@NgModule({
  imports: [
    CommonModule
  ]
})
export class AppSettingsModule {
  static forRoot(config: Object): ModuleWithProviders {
    return {
      ngModule: AppSettingsModule,
      providers: [
        { provide: AppSettingsObject, useValue: config },
        {
          provide: AppSettingsService,
          useFactory: (createAppSettingsService),
          deps: [AppSettingsObject]
        }
      ]
    };
  }
}

Error:

> ng build my-app --prod

Date: 2018-08-29T
Hash: saghsh4ty463f34r4fef
Time: 8585ms
chunk {0} runtime.xxx.js (runtime) 1.05 kB [entry] [rendered]
chunk {1} styles.xxx.css (styles) 102 kB [initial] [rendered]
chunk {2} polyfills.xxx.js (polyfills) 130 bytes [initial] [rendered]
chunk {3} main.xxx.js (main) 128 bytes [initial] [rendered]

ERROR in Error during template compile of 'MyAppModule'
  Function calls are not supported in decorators but 'AppSettingsModule' was called.

Any help?

@leonardochaia that is correct, because it must be a completely static function, including what it returns, which is why it cannot have a const in there.

@dherges what about installing the dependencies of my library in my app?

It will work well to copy the library from dist/my-lib to my-app/node_modules/@my/lib, but what about installing the dependencies of my-lib in my-app/node_modules?