如何有条件地导入模块或提供服务? (AOT)

时间:2017-11-17 18:33:14

标签: angular typescript dependency-injection service-worker

我的问题是Angular模块之一无法与Safari浏览器一起正常工作。

我有以下代码:

@NgModule({
    bootstrap: [ AppComponent ],
    imports: [
        BrowserAnimationsModule,
        BrowserModule.withServerTransition({
            appId: 'app'
        }),
        AppModule,
        'navigator' in window && 'serviceWorker' in navigator ? ServiceWorkerModule.register('./ngsw-worker.js') : ServiceWorkerModuleMock
    ]
})

如您所见,我想有条件地导入模块。当我使用JIT编译时,这种机制很有效。不幸的是,它无法使用AOT - 无论条件如何都导入ServiceWorkerModuleMock

作为一种解决方案,我尝试使用ngDoBootstrap方法进行一些操作:

@NgModule({
    entryComponents: [ AppComponent ],
    imports: [
        BrowserAnimationsModule,
        BrowserModule.withServerTransition({
            appId: 'app'
        }),
        AppModule,
        ServiceWorkerModule.register('./ngsw-worker.js')
    ]
})
export class BrowserAppModule {
    constructor(private injector: Injector) {}

    public ngDoBootstrap(appRef: ApplicationRef): void {
        // console.log('navigator' in window && 'serviceWorker' in navigator);

        appRef.bootstrap(AppComponent);
    }
}

但到目前为止,我不知道如何更改@NgModule注释中导入的模块。我应该使用哪种类的哪种方法?

也许有可能在Injector中更改给定服务的实例?那个解决方案也让我满意。

2 个答案:

答案 0 :(得分:0)

您可能希望使用其他模块或函数来包装此逻辑。这不是现成的解决方案,需要一些工作,但它仍然足以向您展示这个想法:

@NgModule({
    ...
})
export class MyBootstrapModule {
    static getModule() {
        if ('navigator' in window && 'serviceWorker' in navigator) {
            return ServiceWorkerModule.register('./ngsw-worker.js') 
        } else {
            return ServiceWorkerModuleMock;
        }
    }
}

@NgModule({
    bootstrap: [ AppComponent ],
    imports: [
        BrowserAnimationsModule,
        BrowserModule.withServerTransition({
            appId: 'app'
        }),
        AppModule,
        MyBootstrapModule.getModule()
    ]
})

此外,您还可以查看更复杂的模块设置的其他示例,例如ngrx effects module。这很简单(见下面部分)。请注意他们如何使用ngModule。根据您的确切需要,您可能还需要从模块中重新定义和重新导入/重新导出某些内容。

@NgModule({
  providers: [
    Actions,
    EffectsSubscription,
    {
      provide: APP_BOOTSTRAP_LISTENER,
      multi: true,
      deps: [ Injector, EffectsSubscription ],
      useFactory: runAfterBootstrapEffects
    }
  ]
})
export class EffectsModule {
  static run(type: Type<any>) {
    return {
      ngModule: EffectsModule,
      providers: [
        EffectsSubscription,
        type,
        { provide: effects, useExisting: type, multi: true }
      ]
    };
  }
....

答案 1 :(得分:0)

根据我的研究,当前似乎没有办法克服只能使用编译时构造的AOT编译器的局限性。

尝试克服另一个答案所建议的方法,将无法正常工作,只会将相同的静态评估转移到另一个位置,或者编译失败或无提示编译为无用的错误常量。正确的方法是通过提供程序进行初始化,并且Angular Service Worker实际上已在更高版本中修复了此问题。但是Angular 7没有修复。

但是,如果您可以运行其他的构建后脚本,则以下极其肮脏但可以正常工作的黑客将可以将运行时代码注入到装饰器AOT编译的代码中:

1),而不是使用编译失败或被AOT错误评估的构造,请使用带有“宏”的字符串将脚本注入到AOT编译的装饰器代码中,例如

enabled: '#INJECT_CODE("serviceWorker" in navigator)' as any

编译器将在生成的JS代码中保留此字符串。

请注意,该字符串在默认的build / ng服务中将评估为true。您可以将其与其他静态逻辑结合使用,例如environment.production && '#INJECT_CODE(...否定调试版本(无论如何,服务工作者对ng服务都没用)。

2)在构建后对main。*。js进行后处理,并将'#INJECT_CODE(XX)'替换为   XX 。 如果使用源映射,请使用空格保持表达式长度相同,以免破坏映射。 用Ant进行说明:

<replaceregexp match="&quot;#INJECT_CODE(.*?)&quot;"
               flags="gs"
               preserveLastModified="true"
               replace="             \1 "
>
    <fileset dir="${basedir}">
        <include name="main*.js"/>
    </fileset>
</replaceregexp>

最终结果将是实际代码,而不是宏字符串。对于更复杂的逻辑,为避免污染代码和可能的极端情况,注入可能只是一个window.XX,其中XX是在模块加载之前求值并存储的,或者可以是函数调用。在函数调用的情况下,也应该在某个地方的普通代码中对其进行引用,以避免摇晃树。

我想知道为什么AOT不提供这种内置的东西,或者至少没有保留对窗口对象的运行时访问。