RouterModule.forRoot(ROUTES) 和 RouterModule.forChild(ROUTES) 的区别

167
这两者有何区别,以及它们各自的使用场景是什么? 文档并没有提供太多帮助:
- forRoot创建一个模块,其中包含所有指令、给定的路由以及路由服务本身。 - forChild创建一个模块,其中包含所有指令和给定的路由,但不包括路由服务。
我初步猜测其中一个是用于“主”模块,另一个则用于任何导入的模块(因为它们已经可以从主模块中获取到该服务),但我无法想出具体的使用场景。

1
你能更具体地说明你不理解什么吗?你所引用的语句字面上就告诉了你它们之间的区别。 - jonrsharpe
3
我不明白使用.forChild()的意义是什么。在什么情况下,我想要指令和路由,但不需要服务?同时,请回答您从帖子中删除的问题... - VSO
40
一个Angular2应用只应该有一个RouterService。调用forRoot会初始化这个服务,并将它和路由配置一起注册到DI中,而调用forChild则只会注册额外的路由配置,并告诉Angular2重用forRoot创建的RouterService - Harry Ninh
1
请查看 Angular 路由器作者 Victor 的此视频:https://www.youtube.com/watch?v=QLns6s02O48。 - nikhil mehta
有一个视频可以看。https://www.youtube.com/watch?v=puscOjMyouc - Hello
显示剩余3条评论
6个回答

170

我强烈建议阅读这篇文章:

提供者的模块

当你导入一个模块时,通常使用模块类的引用:

@NgModule({
    providers: [AService]
})
export class A {}

-----------------------------------

@NgModule({
    imports: [A]
})
export class B

通过这种方式,在模块 A 上注册的所有提供程序都将添加到根注入器中,并可用于整个应用程序。

但是还有另一种注册具有提供程序的模块的方式:

@NgModule({
    providers: [AService]
})
class A {}

export const moduleWithProviders = {
    ngModule: A,
    providers: [AService]
};

----------------------

@NgModule({
    imports: [moduleWithProviders]
})
export class B

这与前一个示例具有相同的含义。

您可能已经知道,惰性加载模块有自己的注入器。因此,假设您想要注册AService以便整个应用程序都可以使用,但是某些BService仅可供惰性加载模块使用。您可以像这样重新设计您的模块:

@NgModule({
    providers: [AService]
})
class A {}

export const moduleWithProvidersForRoot = {
    ngModule: A,
    providers: [AService]
};

export const moduleWithProvidersForChild = {
    ngModule: A,
    providers: [BService]
};

------------------------------------------

@NgModule({
    imports: [moduleWithProvidersForRoot]
})
export class B
    
// lazy loaded module    
@NgModule({
    imports: [moduleWithProvidersForChild]
})
export class C

现在,BService仅适用于子惰性加载模块,而AService可用于整个应用程序。

您可以将上述内容重写为导出的模块,如下所示:

@NgModule({
    providers: [AService]
})
class A {
    forRoot() {
        return {
            ngModule: A,
            providers: [AService]
        }
    }

    forChild() {
        return {
            ngModule: A,
            providers: [BService]
        }
    }
}

--------------------------------------

@NgModule({
    imports: [A.forRoot()]
})
export class B

// lazy loaded module
@NgModule({
    imports: [A.forChild()]
})
export class C

###这与RouterModule有什么关系呢? 假设它们都使用相同的令牌进行访问:

export const moduleWithProvidersForRoot = {
    ngModule: A,
    providers: [{provide: token, useClass: AService}]
};

export const moduleWithProvidersForChild = {
    ngModule: A,
    providers: [{provide: token, useClass: BService}]
};

当您从惰性加载的模块请求token时,将获得BService,就像计划的那样,因为它具有单独的配置。

RouterModule使用ROUTES令牌获取特定模块的所有路由。由于它希望懒加载子模块的路由在该模块内可用(类似于我们的BService),因此它使用不同的配置。

static forChild(routes: Routes): ModuleWithProviders {
    return {
        ngModule: RouterModule, 
        providers: [{provide: ROUTES, multi: true, useValue: routes}]
    };
}

7
换句话说,我们应该在特性模块中调用forChild(),因为forRoot()已经在根模块中调用并添加了所有必要的服务。再次调用forRoot()会导致不可预测的状态。 - Wachburn
11
在懒加载模块中调用forRoot会在该模块注入器中创建所有全局服务的新实例。是的,这将导致不可预测的结果。同时阅读此文章避免Angular中常见的模块混淆 - Max Koretskyi
1
@Willwsharp,为什么? - Max Koretskyi
2
我完全理解这个问题。但是RouterModule.forRoot和RouterModule.forChild之间有什么区别呢?forRoot和forChild只是静态方法,根据传递的值返回对象。那么在应用程序模块中,为什么我不能使用forChild?为什么会抛出错误?为什么我不能使用多个forRoot? - Subhadeep
8
有时候,直截了当地回答问题是很好的选择。信息过载往往会导致更多的混乱。 - Adépòjù Olúwáségun
显示剩余9条评论

43

我认为答案是正确的,但是我认为有些东西缺失了。
缺失的是“为什么以及它解决了什么问题?”
好的,让我们开始吧。

首先,让我们提供一些信息:

所有模块都可以访问根服务。
因此,即使是惰性加载的模块也可以使用在app.module中提供的服务。
如果一个惰性加载的模块将自己提供给应用程序模块已经提供的服务,会发生什么?就会有2个实例。
这不是问题,但有时候会是问题。
我们如何解决它?简单地说,不要在惰性加载的模块中导入具有该提供程序的模块。

故事结束。

这^只是为了说明惰性加载的模块有它们自己的注入点(与非惰性加载的模块相对)。

但是,当一个共享(!)模块声明了providers,并且该模块被惰性加载和app.module导入时会发生什么?同样,像我们所说的那样,会有两个实例。

那么,我们如何从共享模块的角度解决这个问题?我们需要一种方法使用providers:[]!为什么?因为它们将自动导入到消费惰性和app.module中,我们不希望这样,因为我们看到每个实例都有一个不同的实例。

好吧,事实证明我们可以声明一个没有providers:[]但仍然提供提供程序的共享模块(抱歉:))

怎么做?像这样:

enter image description here

注意,没有提供程序。

但是

  • 现在,当app.module以服务的POV导入共享模块时会发生什么?什么也不会发生。

  • 当一个懒加载模块从服务的角度导入共享模块时,现在会发生什么?什么都不会发生。

通过约定进入手动机制:

您会注意到图片中的提供程序具有service1service2

这使我们可以为惰性加载的模块导入service2,而为非惰性模块导入service1。(咳咳...路由器...咳咳)

顺便说一句,没人阻止您在惰性加载模块中调用forRoot。但是,由于app.module也应该这样做,因此您将拥有2个实例,所以不要在惰性模块中这样做。

此外 - 如果app.module调用forRoot(没有人调用forchild)-那就没问题了,但根注入器只有service1。(对所有应用程序可用)

那么我们为什么需要它呢?我会说:

它允许共享模块通过forRootforChild约定将其不同的提供程序拆分为可用于急切模块和惰性模块。我重复一遍:约定

就这样。

等等!!完全没有单例的提到??那么我为什么到处都看到单例?

好吧 - 它隐藏在上面的句子中 ^

它允许共享模块,通过forRootforChild将其不同的提供程序拆分为可用于急切模块和惰性模块

约定(!!!)允许它成为单例-更确切地说,如果不遵循约定-将不会得到单例。
因此,如果您只在app.module中加载forRoot,则只会得到一个实例,因为您只应该在app.module中调用forRoot
BTW - 在这一点上,您可以忘记forChild。延迟加载的模块不应/不会调用forRoot-因此,在单例POV方面是安全的。

forRootforChild不是一个不可分割的包-只是没有必要调用明显仅在app.module中加载的forRoot,而不给予延迟加载的模块拥有自己的服务的能力,而无需创建应该是单例的新服务。

这个约定给了你一个称为forChild的好功能-以消耗“仅适用于延迟加载模块”的服务。

这是一个演示,根提供程序产生正数,延迟加载的模块产生负数。


1
  1. 这里的POV是什么?
  2. Stackblitz不再起作用了。
- Nicholas K
@NicholasK 这个 StackBlitz 一段时间后总是出错。我会尝试上传新版本。 - Royi Namir
@RoyiNamir 正如您所提到的,延迟加载模块也可以访问根模块的提供者,那么为什么我们需要在延迟加载模块中再次添加它们呢?即使在共享模块的情况下,如果我们已经在根模块中添加了它,为什么我还要在延迟加载模块中再次添加共享模块呢?任何在根模块中不存在但在延迟加载模块中需要的提供者,我都可以单独将它们添加到延迟加载模块中。对于这个问题我仍然感到困惑,不确定它们的使用方式。 - Aman jaura
@NicholasK POV = 视角(https://i.imgur.com/EDtF1qA.jpg) - Royi Namir

30

文档清晰地说明了此处区分的目的:https://angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root

只在根应用程序模块 AppModule 中调用 forRoot。在任何其他模块(尤其是延迟加载模块)中调用它,都与意图相反,并可能导致运行时错误。

记得导入结果;不要将其添加到任何其他 @NgModule 列表中。

每个应用程序都有且仅有一个起始点(根),其中主路由服务应该使用 forRoot 进行初始化,而特定“子”功能的路由应该使用 forChild 进行注册。这对于子模块和延迟加载模块非常有用,它们不必在应用程序启动时加载,并且如@Harry Ninh所说,它们被告知重用 RouterService 而不是注册新服务,这可能会导致运行时错误。


2
这些文档似乎已经被移动了,https://v2.angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root - PeterS

5

forRoot静态方法:

RouterModule.forRoot(routes)

forRoot静态方法是配置应用程序的根路由模块的方法。当您调用RouterModule.forRoot(routes)时,您正在请求Angular全局实例化Router类的一个实例。就像Angular会创建一个新的基本AppModule来导入所有功能模块一样,它还提供了AppRoutingModule来导入所有子路由。

在您通过Angular CLI创建的新应用程序中,forRoot方法实际上已经在app-routing.module.ts中被使用了。在您的应用程序中,您只需要使用forRoot方法一次。这是因为此方法在幕后告诉Angular实例化一个Router类的实例,而在您的应用程序中只能有一个路由器。 forRoot静态方法是确保您使用单例类的模式的一部分。

子路由:

RouterModule.forChild(routes)

当您使用forChild静态方法时,基本上是在告诉Angular:“应用程序中已经有一个路由器实例可用,请仅将所有这些路由注册到该实例中。” forChild方法是您在整个应用程序中调用以注册路由的方法,并且您将在创建的子路由模块中使用它。
forChild静态方法很有用,因为它通过允许您在应用程序中保持关注点分离来发挥Angular模块功能的核心作用。
假设您想为应用程序中的用户设置创建一个新的功能模块,并且该功能将包含几个路由。您可以使用forChild方法在应用程序中保持关注点分离,而不是直接将这些路由添加到AppRoutingModule中,这将随着应用程序增长而变得不可行。首先,创建一个新的UserSettingsRoutingModule。
import { NgModule } from  '@angular/core';
import { Routes, RouterModule } from  '@angular/router';

import { UserSettingsComponent } from './user-settings.component';
import { UserProfileComponent } from  './user-profile/user-profile.component';

const  routes:  Routes  = [
    {
        path:  'settings',
        component:  UserSettingsComponent,
        children: [
            {
                path:  'profile',
                component: UserProfileComponent 
            }
        ]
    }
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export  class  UserSettingsRoutingModule { }

注意上面使用了forChild方法。由于您已经使用了forRoot方法,因此只需将路由注册到已实例化的应用程序路由器即可。

现在您需要按照以下方式创建UserSettingsModule:

import { NgModule, CommonModule } from  '@angular/core';
import { UserSettingsRoutingModule } from  './user-settings-routing.module';

@NgModule({
    imports: [
        CommonModule,
        UserSettingsRoutingModule
    ],
    // Configure the rest of your module here
})
export class UserSettingsModule { }

现在你已经完成了!现在你只需要将这个UserSettingsModule导入到你的根AppModule以及指向各自组件的子路由中,就可以在你的应用程序中配置这些路由。

我希望这个指南能够帮助你理解Angular Router静态方法forRoot和forChild如何帮助你创建一个良好结构化的路由。想要更多信息,请查看文档


2

如果appRoutes包含网站中各种功能的路径(管理员crud、用户crud、图书crud),我们想要将它们分开,我们可以简单地这样做:

如果appRoutes包含网站中各种功能的路径(管理员crud、用户crud、图书crud),我们想要将它们分开,我们可以简单地这样做:

 imports: [
    BrowserModule, HttpModule,
    AppRoutingModule,
    RouterModule.forRoot(categoriesRoutes),
    RouterModule.forRoot(auteursRoutes),
  ],

And for routes :

const auteursRoutes:Routes=[
  {path:'auteurs/ajouter',component:CreerAuteurComponent},
]
const categoriesRoutes: Routes = [


  {path:'categories/consulter',component:ConsultercategoriesComponent},
  {path:'categories/getsouscategoriesbyid/:id',component:GetsouscategoriesbyIDComponent},
  {path:'categories/ajout',component:CreerCategorieComponent},
 {path:'categories/:id',component:ModifiercategorieComponent},
 {path:'souscategories/ajout/:id',component:AjoutersouscategorieComponent},
 {path:'souscategories/lecture/:id1',component:SouscategoriesComponent},
 {path:'souscategories/modifier/:id1',component:ModifiersupprimersouscategorieComponent},
  {path:'uploadfile',component:UploadfileComponent},
  {path:'categories',component:ConsultercategoriesComponent},



]

不应该两次调用 forRoot - Oram

2
考虑这个路由模块的实现,以便理解。需要分别处理延迟加载路由和常规路由,并支持延迟加载。
   @NgModule({
      declarations: [
        
      ],
      exports: [],
    })
    export class RouteModule {
      static forRoot(): ModuleWithProviders<RouteModule> {
        return { ngModule: RouteModule, providers: [routerHistoryService,handleCommonRoutesService] };
      }
    
      static forChild(): ModuleWithProviders<RouteModule> {
        return {
          ngModule: RouteModule, providers: [handleChildRouterService]
        };
      }
    }
    
    

forRoot -> 将添加带有提供者(服务)的routerModule,因此所有常见的Angular路由所需的服务(例如routerHistoryService)将从应用程序模块(根模块)注入到根注入器中。

forChild -> 将添加带有提供者(服务)的routerModule,但是由于常见服务(例如routerHistoryService)已经添加到了根注入器中,因此从惰性加载的模块中可以使用它们,无需再次添加,如果添加将创建两个实例。但是可能需要处理子路由的特定服务。在这种情况下,当调用forChild时,我们可以提供它们(例如:handleChildRouterService)。

如果我们不实现forRoot和forChild,则考虑以下情况:

1)在根注入器中,routerHistoryService的先前路由为“home/lazymodule”

2)但是在惰性模块中,具有新历史记录服务的先前路由为null

因此,在单击返回按钮时,它没有数据可以返回。这就是为什么routerModule按照以下模式实施以确保整个应用程序中只有一个routerModule实例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接