了解角度forroot和forchild

forRoot / forChild is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it:

forRoot / forChild是我们大多数人都从路由了解的单例服务模式。 路由实际上是它的主要用例,并且因为它不在其外部广泛使用,所以如果大多数Angular开发人员没有再考虑一下,我也不会感到惊讶。 但是,正如Angular官方文档所述:

“Understanding how forRoot() works to make sure a service is a singleton will inform your development at a deeper level.”

“了解 forRoot() 如何 确保服务是单例将更深入地指导您的开发。”

So let’s go.

所以走吧

提供者和注射者 (Providers & Injectors)

Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You inject the service and the dependency injection system takes care of providing an instance.

Angular带有依赖项注入(DI)机制。 当组件依赖于服务时,您无需手动创建服务的实例。 您注入了服务,并且依赖项注入系统负责提供实例。

import { Component, OnInit } from '@angular/core';
import { TestService } from 'src/app/services/test.service';


@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss'],
})
export class TestComponent implements OnInit {
  text: string;


  constructor(
    private testService: TestService, // the DI makes sure you get an instance
  ) { }


  ngOnInit() {
    this.text = this.testService.getTest();
  }
}

Dependency injection is not limited to services. You can use it to inject (almost) anything you like, for example objects like Routes in the RouterModule.

依赖注入不限于服务。 您可以使用它注入(几乎)任何您喜欢的东西,例如RouterModule Routes之类的RouterModule

Injectors are responsible of creating the objects to inject and injecting them in the components that request them. You tell injectors how to create these objects by declaring a provider. In the provider you can tell the injector to use a given value or use a class to instantiate for example.

注入器负责创建要注入的对象并将其注入到请求它们的组件中。 您可以通过声明提供者来告诉注入器如何创建这些对象。 在提供程序中,您可以告诉注入器使用给定值或使用类实例化。

Injected objects are always singletons inside an injector but you can have more than one injector in your project. They are created by Angular: A root injector is created in the bootstrap process and injectors are created for components, pipes or directives. Each lazy-loaded module also gets its own.

注入的对象始终是注入器内部的单例对象,但是您的项目中可以有多个注入器。 它们是由Angular创建的:在引导过程中创建root注入器,并为组件,管道或指令创建注入器。 每个延迟加载的模块也都有自己的模块。

You might require different instances of a given service in different modules or components. For some others it might not really matter, except maybe for performance, if more than one instance exists in the application at a given time. For some services however you need to make sure that they are real singletons, meaning that there is only one instance in the whole application.

您可能需要在不同的模块或组件中使用给定服务的不同实例。 对于另一些实例,如果在给定的时间在应用程序中存在多个实例,则除了性能方面可能并不重要。 但是对于某些服务,您需要确保它们是真实的单例,这意味着整个应用程序中只有一个实例。

Providers for services are usually the service class itself and you would usually use the providedIn shortcut to provide the service in the root injector.

服务的提供者通常是服务类本身,您通常会使用providedIn快捷方式在root注入器中提供服务。

import { Injectable } from '@angular/core';


@Injectable({
  providedIn: 'root',
})
export class TestService {
  getTest(): string {
    return 'test';
  }
}

You might come across cases where you have to declare the provider in the module, when providing other kinds of objects for example:

当提供其他类型的对象时,例如,您可能会遇到必须在模块中声明提供者的情况:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
  ],
  providers: [{ provide: SOME_OBJECT, useValue: { key: 'value' }],
  bootstrap: [AppComponent]
})
export class AppModule { }

In such a case, keeping SOME_OBJECT a singleton becomes tricky when dealing with lazy-loaded modules.

在这种情况下,在处理延迟加载的模块时,保持SOME_OBJECT为单例变得很棘手。

延迟加载模块 (Lazy-loaded Modules)

When you provide values in eager-loaded modules imported into each other, the modules’ providers are merged. We can see that best with services as we can log the number of instances:

当您在相互导入的预先加载的模块中提供值时,模块的提供程序将合并。 我们可以看到最好的服务,因为我们可以记录实例数:

import { Injectable } from '@angular/core';


@Injectable()
export class SingletonService {
  static instances = 0;


  constructor() {
    SingletonService.instances += 1;
  }


  someMethod(): void {
    console.log(`There are ${SingletonService.instances} instances of the service`);
  }
}

If you provide this service in a providers array in two modules and import one of these modules in the other one, injectors are going to be merged and you will still have only one instance of the service.

如果您在providers数组中的两个模块中提供此服务,而在另一个模块中导入这些模块之一,则注入器将被合并,并且您将仍然只有该服务的一个实例。

import { Component, OnInit } from '@angular/core';
import { SingletonService } from './singleton.service';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  constructor(
    private singletonService: SingletonService
  ) { }


  ngOnInit(): void {
    this.singletonService.someMethod();
  }
}

You can check the console now, you would see twice the message “There are 1 instances of the service”.

您现在可以检查控制台,您将看到两次消息“该服务有1个实例”。

This gets more complicated with lazy-loaded modules. Each lazy-loaded module gets its own injector. In the previous example, if you lazy-load the moduleA instead of simply importing it, its injector will create a new instance of the SingletonService. You would see “There are 2 instances of the service” in the console.

对于延迟加载的模块,这变得更加复杂。 每个延迟加载的模块都有自己的注入器。 在前面的示例中,如果您延迟加载moduleA而不是简单地导入它,则其注入器将创建SingletonService的新实例。 您会在控制台中看到“服务有2个实例”。

forRoot / forChild (forRoot/forChild)

Angular supports another way of importing a module with providers. Instead of passing the module class reference you can pass an object that implements ModuleWithProviders interface.

Angular支持使用提供程序导入模块的另一种方法。 无需传递模块类引用,您可以传递实现ModuleWithProviders接口的对象。

interface ModuleWithProviders {
ngModule: Type<any>;
providers?: Provider[];
}

You can for example decide to import the module with different providers in the AppModule and in child modules:

例如,您可以决定在AppModule和子模块中使用不同的提供程序导入模块:

const moduleWithProviders = {
  ngModule: ModuleAModule,
  providers: [SingletonService]
};


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    moduleWithProviders,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

A more elegant solution would be to define static methods on the ModuleA :

一个更好的解决方案是在ModuleA上定义静态方法:

import { ModuleWithProviders, NgModule } from '@angular/core';
import { SingletonService } from './singleton.service';


import { ModuleAComponent } from './moduleA.component';


@NgModule({
  declarations: [
    ModuleAComponent
  ],
  exports: [ModuleAComponent],
})
export class ModuleAModule {
  static forRoot(): ModuleWithProviders<ModuleAModule> {
    return { ngModule: ModuleAModule, providers: [SingletonService] };
  }


  static forChild(): ModuleWithProviders<ModuleAModule> {
    return {
      ngModule: ModuleAModule, providers: [{ provide: SingletonService, useValue: {} }]
    };
  }
}

and use these methods when importing the module. We named them forRoot and forChild but we would have been technically free to choose any name.

并在导入模块时使用这些方法。 我们将它们命名为forRootforChild但从技术forChild ,我们可以自由选择任何名称。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';


import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ModuleAModule } from './moduleA/moduleA.module';




@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ModuleAModule.forRoot(),
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

路由 (Routing)

In the case of routing, theRouterModule provides the Router service. Without forRoot / forChild, each feature module would create a new Router instance but there can only be one Router. By using the forRoot method, the root application module gets a Router, and all feature modules use forChild and do not instantiate another Router.

在路由的情况下, RouterModule提供Router服务。 如果没有forRoot / forChild ,则每个功能模块将创建一个新的Router实例,但只能有一个Router 。 通过使用forRoot方法,根应用程序模块获得一个Router ,并且所有功能模块都使用forChild而不实例化另一个Router

Since forRoot and forChild are just methods you can pass parameters when calling them. For the RouterModule you pass the value of an additional provider, the routes, and some options:

由于forRootforChild只是方法,因此可以在调用它们时传递参数。 对于RouterModule您传递附加提供程序的值,路由和一些选项:

static forRoot(routes: Routes, config?: ExtraOptions) {
return {
ngModule: RouterModule,
providers: [
{provide: ROUTES, multi: true, useValue: routes},
...,
],
...
}static forChild(routes: Routes) {
return {
ngModule: RouterModule,
providers: [
{provide: ROUTES, multi: true, useValue: routes},
...,
],
...
}

To sum up, forRoot / forChild solves a problem that can occur in a really particular situation.

综上所述, forRoot / forChild解决了在特定情况下可能发生的问题。

Lazy-loaded modules have their own injectors and this can lead to issues when trying to keep some provided service a singleton.

延迟加载的模块具有自己的注入器,这在尝试使某些提供的服务保持单一状态时可能导致问题。

You can solve this by importing modules using the ModuleWithProviders interface. forRoot / forChild is only a convenient pattern to wrap this a clean way. It is not technically a part of Angular, but it is the solution the Angular team chose for the RouterModule and it is a good practice to solve similar problems using the same pattern.

您可以通过使用ModuleWithProviders接口导入模块来解决此问题。 forRoot / forChild只是一种方便的模式,可以用一种干净的方式包装它。 从技术上讲,它不是Angular的一部分,但这是Angular团队为RouterModule选择的解决方案,并且是使用相同模式解决类似问题的好习惯。

普通英语JavaScript (JavaScript In Plain English)

Enjoyed this article? If so, get more similar content by subscribing to Decoded, our YouTube channel!

喜欢这篇文章吗? 如果是这样,请订阅我们的YouTube频道解码,以获得更多类似的内容

翻译自: https://medium.com/javascript-in-plain-english/understand-angulars-forroot-and-forchild-f27fbc41cb7b

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值