Angular forRoot方法内调用函数致AOT构建失败的问题及解决
AOT构建报错:Function calls are not supported in decorators(解决方法)
我之前也踩过Angular AOT编译的这个坑!先理清楚你的问题场景和解决思路:
问题场景
你在Angular项目的forRoot配置里引用了environment,而environment是通过调用loadJSON函数加载外部JSON文件生成的:
app.module.ts
import {environment} from '../environments/environment'; ... @NgModule({ imports: [ BrowserModule, MyModule.forRoot({ config: { sentryURL: environment.SENTRY_URL // 这里间接触发了loadJSON函数调用 } }), HttpClientModule, ... ] })
environment.ts
export function loadJSON(filePath) { const json = loadTextFileAjaxSync(filePath, 'application/json'); return JSON.parse(json); } export function loadTextFileAjaxSync(filePath, mimeType) { const xmlhttp = new XMLHttpRequest(); xmlhttp.open('GET', filePath, false); if (mimeType != null) { if (xmlhttp.overrideMimeType) { xmlhttp.overrideMimeType(mimeType); } } xmlhttp.send(); if (xmlhttp.status === 200) { return xmlhttp.responseText; } else { return null; } } export const environment = loadJSON('/assets/config.json');
config.json
{ "production": "false", "SENTRY_URL": "https://...@sentry.com/whatever/1" }
报错信息
执行AOT构建时直接抛出错误:
ERROR in src/app/app.module.ts(41,20): Error during template compile of 'AppModule' Function calls are not supported in decorators but 'loadJSON' was called in 'environment' 'environment' calls 'loadJSON'.
原因分析
Angular的AOT编译要求装饰器(比如@NgModule)里的所有配置必须是静态可分析的常量,不能包含动态函数调用。因为AOT是在构建阶段预编译代码,没法在这个阶段执行loadJSON这类运行时函数来获取值。
最终解决方案
我当时参考了Suren Srapyan的建议,把模块的forRoot方法改成用函数getter延迟获取配置,再通过APP_INITIALIZER在应用初始化时执行函数调用。具体修改如下:
import { InjectionToken, ModuleWithProviders, NgModule, APP_INITIALIZER } from '@angular/core'; import { CommonModule } from '@angular/common'; export const OPTIONS = new InjectionToken<string>('OPTIONS'); // 修改接口,把sentryURL换成getter函数 export interface MyModuleOptions { config: { sentryURLGetter: () => string | Promise<string>; } } // 初始化工厂函数,在应用启动时调用getter export function initialize(options: MyModuleOptions) { console.log('sentryURL', options.config.sentryURLGetter()); // 这里可以把获取到的URL用于后续初始化逻辑(比如Sentry配置) return function () { }; } @NgModule({ imports: [ CommonModule ] }) export class MyModule { static forRoot(options: MyModuleOptions): ModuleWithProviders<MyModule> { return { ngModule: MyModule, providers: [ {provide: OPTIONS, useValue: options}, // 注册APP_INITIALIZER,确保应用启动时执行函数 { provide: APP_INITIALIZER, useFactory: initialize, deps: [OPTIONS], multi: true } ] }; } }
然后在app.module.ts里调用时,直接传入getter函数而不是提前执行:
MyModule.forRoot({ config: { sentryURLGetter: () => environment.SENTRY_URL } }),
这样一来,函数调用就从AOT编译的装饰器阶段,移到了应用运行时的初始化阶段,完美避开了AOT的限制!
内容的提问来源于stack exchange,提问作者ismaestro
相关产品推荐
相关产品推荐

