【问题标题】:Angular, Lazy Loading, Inter-Module Dependencies, best practiceAngular,延迟加载,模块间依赖,最佳实践
【发布时间】:2018-03-13 07:27:06
【问题描述】:

我有以下示例应用程序布局:

app.module
 |
 |-- dashboard.module                 (lazy loaded)
 |-- dashboard-routing.module         (imported by dashboard.module)
 |-- dashboard.service                (provided by dashboard.module)
 |-- dashboard.component              (depends on dashboard.service)
 |
 |-- notifications.module             (lazy loaded)
 |-- notifications-routing.module     (imported by notifications.module)
 |-- notifications.service            (provided by notifications.module)
 |-- notifications.component          (depends on notifications.service)
 |
 |-- my-notifications-dashboard-widget.component <- this should be added
 |
 |-- ...

每个模块(dashboard、notifications、...)都在延迟加载。

现在我想添加一些MyNotificationsDashboardWidgetComponent,这取决于notifications.service。然后这个小部件应该被注入到dashboard.service。

只要提供的 WidgetComponent 位于 dashboard.module 内,这样就可以正常工作。但是如果我把组件放在懒加载的notifications.module里面,当然不行。

现在我想知道,如何以一种好的、面向模块的方式解决这个问题。

我的要求:

  • 应该只有一个notifications.service,notifications.component和my-notifications-dashboard-widget.component应该使用。

  • 应该延迟加载大模块(dashboard、notifications)。

  • dashboard.module 不应该对notifications.module 和/或my-notifications-dashboard-widget.component 有任何了解。


想法:

我想,我需要一些预先加载的模块,其中包含my-notifications-dashboard-widget.component。因此,该组件及其提供者在dashboard.module 被加载之前是已知的。

但是我猜我也必须将notifications.service 放在这个模块中?!


当前(非工作)方法的一些代码sn-ps:

dashboard.service:

@Injectable()
export class DashboardService {
    constructor(@Inject(DASHBOARD_WIDGET) widgets) {
        // inject all provided widgets
        widgets.forEach(w => console.log(w.type));
    }
}

notifications.module:

@NgModule({
    imports: [ NotificationsRoutingModule ],
    declarations: [ NotificationsComponent ],
    providers: [
        NotificationsService,
        // this is currently not working, because of lazy loading
        {
            provide: DASHBOARD_WIDGET,
            useValue: {
                type: 'my-notifications'
                component: MyNotificationsDashboardWidgetComponent
            },
            multi: true
        }
    ],
    entryComponents: [ MyNotificationsDashboardWidgetComponent ]
})
export class NotificationsModule { }

notifications.component:

@Component({ ... })
export class NotificationsComponent {
    constructor(service: NotificationsService) { ... }
}

my-notifications-dashboard-widget.component:

@Component({ ... })
export class MyNotificationsDashboardWidgetComponent {
    constructor(service: NotificationsService) { ... }
}

【问题讨论】:

    标签: angular angular2-routing lazy-loading angular2-services angular-router


    【解决方案1】:

    请记住,NgModule 级别的提供程序总是向根注入器注册。只有一个根注入器,并且向根注入器注册的任何提供程序都可以在应用程序范围内使用。此规则的例外是延迟加载模块 - 延迟加载模块创建自己的作用域注入器。换句话说,任何注册到延迟加载模块的提供者都不会注册到根注入器。

    为了向根注入器注册提供程序,无论它们是否延迟加载,您都应该遵循forRoot 约定。这个想法是 forRoot 方法返回带有提供程序的模块,以便它可以被 AppModule 导入。

    @NgModule({
        providers: []
    })
    export class MyModule {
         static forRoot(): ModuleWithProviders {
             return {
                 NgModule: MyModule,
                 providers: [
                     MyProvider
                 ]
             }
         }
    }
    

    注意:您还可以实现 forChild 方法,该方法返回没有提供程序的模块。此方法的目的是让您可以重用模块中的组件(包括延迟加载的模块),而无需实际注册任何提供程序。

    【讨论】:

    • 这听起来很有希望。你能详细说明一下吗?如果我在AppModule 中包含DashboardModule.forRoot(),那么我的整个仪表板模块就会被加载。但我想,我只需要注入提供者的服务。然后可以稍后延迟加载仪表板模块的其余部分(组件等)
    • 这只是一个约定。如果您担心不必要地注册组件(因为它只需要从延迟加载的模块中)forRoot 可以返回您喜欢的任何模块,包括只有提供程序的空模块。存在forRoot/forChild 约定,因此您可以使用根注入器注册提供程序,即使模块是延迟加载的。
    • 谢谢。我了解forRoot/forChild。但是:我应该如何注册MyNotificationsDashboardWidgetComponent?我不能从nofications.module 调用forChild,因为这个模块是延迟加载的。或者我可以吗?
    • 当然可以。您可以从任何模块导入 forChild,并利用其组件(只需确保 forChild 不返回任何旨在成为应用程序范围单例的提供程序)
    • 我会重新开始:notifications.module 是延迟加载的。 dashboard.module 将使用 forRoot 进行加载。当我现在在延迟加载的notifications.module 中有import: [ DashboardModule.forChild(...) ] 时,只有当我点击/notifications 路由时它才会执行forChild。为时已晚。我必须在DashboardModule 加载之前 加载forChild,因此仪表板知道所有可用的小部件类型(由其他(延迟加载)模块提供,即notifications.module)跨度>
    【解决方案2】:

    在这种情况下,您只创建一个小部件,将来可能需要添加更多小部件。

    解决方案是,制作一个共享模块并在该模块中创建这样的组件。新的共享模块将独立于其他模块和其中的所有组件。

    在其他模块中导入共享模块,随意使用。

    【讨论】:

    • 是的,当然它适用于多个小部件。我只是不想用另外 500 行来污染这个问题。 ... 服务呢?也许您可以添加一些布局示例?
    【解决方案3】:

    感谢您的启发。我现在已经创建了一个可行的解决方案。对我来说它仍然看起来有点奇怪,有些事情对我来说是个谜,但至少它有效。

    如果您可以发表评论并提供一些其他信息,那就太好了

    app.module:

    @NgModule({
        imports: [
            AppRoutingModule,
            NotificationModule.forRoot()
        ]
    })
    export class AppModule { }
    

    app-routing.module:

    @NgModule({
        imports: [
            RouterModule.forRoot([
                {
                    path: '/dashboard',
                    loadChildren: 'app/dashboard/dashboard.module#DashboardModule'
                },
                {
                    path: '/notifications',
                    loadChildren: 'app/notification/notification.module#NotificationModule'
                }
            ])
        ]
    })
    export class AppRoutingModule { }
    

    dashboard.module:

    @NgModule({
        providers: [ DashboardService ]
    })
    export class DashboardModule {
        provideWidget(type: string, component: Type<any>) {
            return [
                {
                    provide: ANALYZE_FOR_ENTRY_COMPONENTS,
                    useValue: component,
                    multi: true
                },
                {
                    provide: DASHBOARD_WIDGET,
                    useValue: {
                        type: type,
                        component: component
                    },
                    multi: true
                }
            ];
        }
    }
    

    通知模块:

    @NgModule({
        imports: [
            NotificationRoutingModule
        ],
        declarations: [
            NotificationComponent
        ]
    })
    export class NotificationModule {
        static forRoot(): ModuleWithProviders {
            return {
                ngModule: NotificationRootModule,
                providers: [ ]
            }
        }
    }
    

    通知-root.module:

    @NgModule({
        declarations: [
            TestWidgetComponent
        ],
        providers: [
            NotificationService,
            DashboardModule.provideWidget('TEST', TestWidgetComponent)
        ]
    })
    export class NotificationRootModule { }
    

    问题:

    我想我还没有完全理解forRoot 和ModuleWithProviders。我不明白为什么我必须创建一个单独的NotificationRootModule 才能正常工作。

    现在TestWidgetComponent 和NotificationService 被编译到应用模块中。尽管一切都按预期进行。

    但是:如果我在NotificationModule.forRoot() 中使用ngModule: NotificationModule,那么它会将整个 模块(包括NotificationRoutingModule 和NotificationComponent)放入应用程序模块。 (我可以看到浏览器在打开/notifications URL时并没有延迟加载任何东西。)

    现在我不知道providers 属于哪里。我可以将它们放在NotificationRootModule 的@NgModule({ providers }) 内或forRoot() { return { providers } } 内。好像没什么区别。

    希望大家多多指教

    谢谢!

    【讨论】:

    • 您应该实现 forRoot 的原因是您可以拥有一个在应用程序范围内共享的单例服务。如果您不这样做,那么您的延迟加载模块将创建自己的服务副本。这可能不是你想要的......或者它可能是。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    • 1970-01-01
    • 1970-01-01
    • 2020-02-04
    • 2018-10-22
    相关资源
    最近更新 更多