Angular模块与依赖注入

Angular模块划分和依赖注入的思想可以说是Angular架构核心中的核心,最近通读了一遍Angular官网上的模块和依赖注入两章,也领会到了其中的一些精髓。

Angular的模块NgModule,其作用其实有点类似于Jave的package和C#的namespace,是代码组织和代码分割的一种形式。下面是Angular官网上的一段经典的NgModule的写法:

@NgModule({
  declarations: [
    AppComponent,
    ItemDirective
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

可以看出,NgModule实际上是在一个普通的class上加上一些描述性的元数据,这些元数据的意义是:

  • declarations: 声明某些组件、指令和管道属于这个模块。
  • exports: 公开其中的部分组件、指令和管道,以便其它模块中的组件模板中可以使用它们。
  • imports: 导入其它带有组件、指令和管道的模块,这些模块中的元件都是本模块所需的。
  • providers: 提供一些供应用中的其它组件使用的服务。

1. 模块间分享组件

NgModule通过declares来申明了一些组件(component)和类组件(如directivepipe,下文统称为组件)是隶属于自己模块的,而且这些组件和类组件必须且仅仅只能属于一个NgModule,就好像每个人都必须是属于某一个国家。
当你要用到其他NgModule的组件,就需要import其他的模块了。但是这里注意,import的模块不是所有的组件都是可以使用的,import模块必须明确表明了它的哪些组件是可以外用的,这由exports数组来定义。三者的关系见下图:

clipboard.png

2. 模块间分享服务

NgModule的元数据中还有一个属性叫providers,这个属性里一般是声明了一些service。一般情况下我们是希望这些service在模块内的组件中共享的,Angular确实也是如此设计的,因为providers中申明的services都是单例模式的。
但是如果我们要用到其他模块的服务,是否也是像模块间共享组件一样,通过import把模块引入进来就可以呢?确实如此,但是我们还需要理解得更深刻一些。
在Angular的设计思想里,组件是私有化的,服务是公有化的。你可以这么来理解,把组件当作“人才”,把服务当作“知识”,知识是无国界的,可以共享,但是人才是有国界的,不能随便共享(只有exports出去的才可以共享)。当外来模块被import进来后,它的服务就被共享到你的模块中了。
我们知道,每个NgModule都有injector,里面存放着通过providers声明过的service。如果通过imports导入了外来模块,那么外来模块的服务就都注入到了你所在模块的injectors中,如下图所示。

clipboard.png

3. 懒加载下的服务共享

如果你的app中应用到了懒加载,那么情况就会更加复杂了。因为懒加载模块是在特定的情况下app需要用到的时候才会被加载进来,所以一般情况下懒加载模块下的serivce不会被imports到主模块中的,也就不会注入到root injector的,而是在root injector下重新开辟了一个child injector。如果你在主模块和懒加载模块都provide了同样的service,那么就会在两个模块中分别拥有不同的实例。而这往往是开发者不想看到的,因为服务的作用就是共享数据,而此时不知不觉有两个实例存在,每个实例单独维护一份数据,那么就会造成逻辑上的错误,更可怕的是,很多开发者并不知道这一点。所以记得在懒加载模块中不要注入跟主模块相同的服务,用主模块中的就好了。但是有时候你因为要用到某些特殊指令,又不得不导入相同的模块,比如路由模块,在主模块和懒加载模块甚至是一些特征模块中都需要导入,这个时候,就需要在主模块中用到forRoot了。那么forRoot到底起到一个什么作用呢?其实他们只是一个Angular模块中约定俗成的写法,主要是它们有一个返回类型ModuleWithProviders,其实就是想把moduleproviders给区分开来,它的意思就是告诉导入模块可以共享被导入模块中的组件(被exports出的组件),但是服务注入一次就好了,以后要是再有同样的服务需要注入就忽略之,不要创建两个不同实例的服务。
懒加载模块代码如下:

import { NgModule, ModuleWithProviders } from '@angular/core';

import { MyDirective } from './my.directive';
import { FunPipe } from './fun.pipe';
import { SomeService } from './some.service';

@NgModule({
  declarations: [
      FunPipe,
      MyDirective
  ],
  exports: [
      FunPipe,
      MyDirective
  ]
})
export class SharedModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule:SharedModule,
            providers:[ SomeService ]
        };
    }
}

我们在NgModule的元数据中像往常一样声明和导出我们的管道和指令,但是我们不提供服务,而是在模块的类中定义一个静态方法forRoot,该方法返回一个实现Angular的 ModuleWithProviders 接口的对象。
在我们的应用模块中,导入懒加载模块并调用forRoot静态方法来提供我们的服务:

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

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

这样,在导入模块和被导入的懒加载模块中仅仅维护了一个service实例。

4. 服务在组件中私有化

在Angular的@Component里也有个providers,但是如果你在组件级别注入了service,那么这个service就只能在该组件和它的子组件中使用了,别的组件即使在同一个模块中也不能使用这个service。组件这个时候类似于一个家庭,这个私有的服务其实类似于传家宝,这个传家宝只能在你们这个家庭里传下去,它有很强的排他性,而且每个组件实例都会独享一份service,这可以保证这个service只服务于你这个组件实例,是专属于你自己的“私人银行”。比如有很多个申请表,虽然申请表都是一样的,每个服务都必须服务于自己的申请表,这种情况下就适合于用到component级别的provide。

总结

如果想系统的学习Angular的模块和依赖注入知识,建议去Angular官网好好研读。我这里只是学习过后加入了自己的理解,又由于我最近在做关于分包加载涉及到Angular Module和DI比较多,也比较有体会,遂成此拙文。

前端攻城狮一枚

220 声望
11 粉丝
0 条评论

前端攻城狮一枚

220 声望
11 粉丝
宣传栏