Angular中使用forroot和forchild

在本文中我们将深入探讨一些您可能已经见过的 Angular 模式和方法。我将讨论 Angular 管理依赖项辑器配置信息中经常会使用到的 forRoot 和 forChild 方法。

1. Angular 中的 forRoot 是什么?

当我们使用 Angular6 及以上版本创建服务时,Angular 会在@Injectable 中创建一个对象,并将其 ProvideIn 属性为设置为’root’

1
2
3
4
5
6
import { Injectable } from "@Angular/core";

@Injectable({
providedIn: "root",
})
export class UserService {}

当然,我们完全可以将其设置为我们想要的任何模块。这意味着除非我们导入在“provideIn”中指定的模块,否则 UserService 将不可用。

forRoot 用于提供初始化模块及其组件所需的配置数据。此配置数据可以是从环境变量到 API 密钥的任何数据,用于以特定方式设置应用程序。

大多数 Angular 开发人员都使用过 RouterModule 中的 forRoot 静态方法。这是为应用程序配置整个路由模块的方法,Angular 会全局创建它。

该方法本身返回一个 ModuleWithProviders 对象,其包括两个属性:ngModule 一般就是当前模块的引用,providers 一般是一组 provider。

RouterModule.forRoot()

1
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders<RouterModule>;

ModuleWithProviders

1
2
3
4
export declare interface ModuleWithProviders<T> {
ngModule: Type<T>;
providers?: Provider[];
}

2. 我们为什么需要 forRoot 和 forChild?

引入模块我们一般有 3 个方法:

  • 直接导入

    1
    2
    3
    4
    5
    6
    7
    import { XXXModule } from 'xxx.module'

    @NgModule({
    imports: [
    XXXModule
    ]
    })
  • 如果 XXXModule 实现了 forRoot 方法,我们可以:

    1
    2
    3
    4
    5
    6
    7
    import { XXXModule } from 'xxx.module'

    @NgModule({
    imports: [
    XXXModule.forRoot()
    ]
    })
  • 如果 XXXModule 实现了 forChild 方法,我们可以:

    1
    2
    3
    4
    5
    6
    7
    import { XXXModule } from 'xxx.module'

    @NgModule({
    imports: [
    XXXModule.forChild()
    ]
    })

在 angular 中,对于一些公共的组件,指令,管道,当我们需要在某个模块中使用时,我们就需要在该模块中导入一次。

但公共的服务是只需要引入一次的。对于一个服务,我们一般只需要在 root 模块中引入一次,然后就可以在其他模块中使用了。

当我们导入 XXXModule 时,angular 会为我们注册该模块上面的所有服务,如果我们在多个模块中导入了 XXXModule 模块,XXXModule 模块上面的服务是不会重复注册的,因为 angular 会确保服务都是单例的,也就是说一个应用里只会有一个该服务的实例。这能确保我们用服务共享数据时,不同模块取到的数据是一致的。

但有一种情况 angular 处理不了,当我们懒加载某一个模块时,该模块上面的服务会重新注册,如果该服务已经被注册过了,就会造成应用中一个服务有多个实例的情况。

怎么能确保完全的单例呢?这个时候我们就需要借助 forRoot 和 forChild 了。

3. 一个有关 forRoot 和 forChild 方法的示例

我们来看一个有关 RouterModule forRoot 和 forChild 方法的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const routes: Routes = {
path: '',
component: LayoutComponent,
children: [
{
path: '',
redirectTo: 'home',
pathMatch: 'full',
},
{
path: 'home',
loadChildren: ()=> {
import('features/home/home.module').then(
(m)=> m.HomeModule
)
}
},
{
path: 'dashboard',
loadChildren: ()=> {
import('features/dashboard/dashboard.module').then(
(m)=> m.DashboardModule
)
}
},
],
};

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

正如您看到的, 在 Imports 数组中我们调用了 RouterModule 模块的 forRoot 方法, 并将事先定义好的参数 routes 传递给它, 该方法是静态的, 并需要接收一些必要参数.

1
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders<RouterModule>;

当 Angular 创建应用程序的同时它会创建创建路由系统,并且在整个应用程序的生命周期, 路由系统都是有效的.
通常,forRoot 方法在应用程序中仅并调用一次,并且始终在根模块中存在。

1
2
3

static forChild(routes: Routes): ModuleWithProviders<RouterModule>;

观察 ModuleWithProviders,我们可以看到 providers 属性是可选的,他们的区别也是在 providers 上。我们可以将需要确保完全单例的服务移动到 forRoot 中去。在主模块中导入模块时通过 forRoot 方法导入,在子模块中导入模块时直接导入。这样就能确保服务只会注册一次了。

4. 创建自己的 forRoot()

您可以完全自由地创建自己的 forRoot 方法并将其放入您的模块中。毕竟,模块就是一个类。
这是一个关于如何提供核心模块实例并确保仅调用一次的示例!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@NgModule({
declarations: [],
imports: [],
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
"CoreModule is already loaded. Import it in the AppModule only"
);
}
}
static forRoot() {
return {
ngModule: CoreModule,
providers: [],
};
}
}
@NgModule({
declarations: [AppComponent],
imports: [coreModule.forRoot()],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}

正如我所说,这个方法可以被称为任何名称,但由于命名规范,我命名为 forRoot() 。它也可以接受参数。
例如,我可以将初始配置数据传递给模块及其组件.

让我们试着给模块传递初始配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

// interface
export interface UserModuleConfig {
id: string;
}

// the Module we are implementing the config
export class UserModule {

static forRoot(config: UserModuleConfig): ModuleWithProviders<UserModule> {

return {
ngModule: UserModule,
providers: [
UserService,
{
provide: 'config',
useValue: config
}
]
}

}
}
// defining the Userodule with the forRoot config
@NgModute({
declarations: [AppComponent],
imports: [
CoreHodule.forRoot(),
UserModule.forRoot ({
id: ‘abc’
})],
providers: [],
bootstrap: [AppConponent],
})
export class Apphodule {}

// implement the service

@Injectable()
export class UserService {
user_id: string;

constructor(@Inject('config') private config: UserModuleConfig) {
this.user_id = config.id
}
}

5. Angular 中的 forChild 是什么

forChild 与 forRoot 非常相似。

在接下来的路由示例之中,需要指出的是此静态方法通常在功能模块中加载,这些功能模块通常加载到根模块中。它允许我们配置功能模块而不影响整个应用程序。

当加载模块本身时,惰性模块会创建服务和提供者的新实例。这是因为 Angular 使用另一个注入器来创建模块。一个单独的。

比如当我们调用 RoutingModule.forChild() 为子模块创建路由时, 不会再创建 RoutingService, 我们有多个路由模块时,在子模块中必须调用 forChild 方法。

让我们看一个例子。这是我们之前实现的惰性模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const routes: Routes = {
path: '',
component: DashboardComponent,
children: [
{
path: '',
redirectTo: 'overview',
pathMatch: 'full',
},
{
path: 'overview',
component: OverviewComponent,
}
],
};

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

您可以在模块中为自己创建一个类似的方法,并且只能传递特定于功能的服务和提供者.

6. forRoot 和 forChild 之间的区别

让我们讨论并比较以下它们之间的差异。我将在这里重点关注 RouterModule,因为它是最常用的,并且对于理解它的工作原理也很有用。

让我们首先看看它们的相似之处:

  • 声明路由器信息
  • 管理路线

让我们再来看看它们的区别:

  • 一个区别是 forRoot 通常用于整个应用程序,而 forChild 用于功能模块。

  • 另一个关键区别是 forRoot 注册路由服务,而 forChild 则不注册!

也可以说它们具有不同的范围、使用频率以及对服务和 Provider 的访问。

7. 总结

总之,forRoot() 和 forChild() 是 Angular 中强大的方法,用于配置模块及其组件。虽然这两种方法的目的相似,但它们具有关键的差异,这使得它们在不同的环境中具有独特性和价值。
通过有效地使用 forRoot 和 forChild,您可以确保 Angular 应用程序已正确配置并准备好处理其所有要求。

8. Angular 系列文章

最新更新以及更多 Angular 相关文章请访问 Angular 合集 | 鹏叔的技术博客

9. 参考文档

using forroot and forchild in angular

angular2 forRoot 和 forChild