2

前言

上周写代码一直出现因为模块,或者其他的引用错误而导致部分功能无法正常化的情况,一开始老师让看教程,也是看的挺懵的,后来老师给讲了一下之后才真正知道是怎么回事了.

组建的基本要素

组建必然存在模块当中,一般情况下如果直接在命令行中输入ng g c app,则会生成名为app的一个angular组建,也就是以下四种文件
image.png
1是v层的html样式文件,2是模版的v层html代码文件,3是单元测试的代码文件,4则为组建的c层文件,我们在里面定义了我们的1和2文件.其中4是必选的,其他三个都是可选的.有组建必须有模块,组建一定是生存在模块里面的,模块就是组建要素加上一个.module.ts的文件.

组建需要的能力

说完组建的几要素,那现在来说说我们从哪知道以及我们需要给组建提供什么能力才能够让此组建顺利跑起来.

构造函数中

在4文件里面,也就是组建的c层,在4文件里面往往是一个class 类,既然有类,那么就会有构造函数吧,好的,那么构造函数就是一种途径,比如下图:
image.png
可以看到,如果想要让此构造函数所在的类编一部报错,我们就需要实例化TownService、CommonService、ActivatedRoute、Router,这四个类,而如果我们想要实例化这四个类我们有需要从哪去找呢,同样,也是从构造函数中去找,比如ToenService:
image.png
可以看到我们如果想要实例化TownService,那我我们就必须提供HttpClient这个东西,从哪提供呢,或者说怎么实例化,实例化需要哪些东西呢,我们可以在此indexComponent所在的模块中引入HttpClientModule(ng s时需要),单元测试文件中引用HttpClientTestingModule(ng t时用)从而保证我们在类TownService的正常实例化,从而正常实例化indexComponent.下一个,同样的道理下面三个也是需要看相应构造函数中需要什么能力,image.png此为CommonService的构造函数,那我们就需要提供Router以及DomSanitizer了,但是呢,DomSanitizer不属于哪个模块中,所以只需呀在当前文件声明其命名空间即可.由于ActivatedRoute和Router不需要其他能力我们就直接引他们所在的模块即可.

V层代码中

<div>
  <div class="col-12 text-right">
    <a class="btn btn-primary" routerLink="add"><i class="fas fa-plus"></i>新增</a>
  </div>
</div>

<app-size [size]="pageData.size" (changeSize)="onSizeChange($event)"></app-size>
<table class="table table-striped mt-2">
  <thead>
  <tr class="table-primary">
    <th>序号</th>
    <th>名称</th>
    <th>拼音</th>
    <th>操作</th>
  </tr>
  </thead>
  <tbody>
  <tr *ngFor="let object of pageData.content; index as i">
    <td>{{i + 1}}</td>
    <td>{{object.name}}</td>
    <td>{{object.pinyin}}</td>
    <td>
      <span class="btn btn-outline-primary btn-sm" routerLink="edit/{{object.id}}">
        <i class="fas fa-pen"></i>
        编辑
      </span>
      <span class="btn btn-outline-primary btn-sm" (click)="onDelete(i, object.id)">
        <i class="fas fa-trash-alt"></i>
        删除
      </span>
    </td>
  </tr>
  </tbody>
</table>

<app-page [page]="pageData.number"
          [size]="pageData.size"
          [totalElements]="pageData.totalElements"
          (changePage)="onPageChange($event)"></app-page>

可以看到我V层代码中含有angular特有的标签属性routerLink,以及引用size和page模版的代码,如果我们不给予组建相应的能力的话那么组建是不认识他的,自然也不会通过编译,所以我们需要引入RouterTestingModule来赋予组建识别routerLink的能力,当然要使得编译器不报错我们还需要引入PageModule以及SizeModule.

最终单元测试引用代码

// 声明,此处为组建
      declarations: [IndexComponent],

      // 引入,此处为模块
      imports: [
        // 路由器测试模块,设置用于测试的路由器。
        RouterTestingModule,

        // 引用了commonModule
        PageModule,

        // 引用了commonModule和FormsModule
        SizeModule,

        // ApiTestingModule中引入了HttpClientModule,HttpClientModule中提供了HttpClient,
        // 这使得 IndexComponet -> TownService 中的HttpClient可用
        ApiTestingModule
      ]

看到这里是不是会好奇,为什么没有提供CommonService,HttpClient...如果你好奇了那请接着往下看吧!

引用的优先级

我们可以看到其实我们往往是一个类会出现在不只一个构造函数中那是不是需要实例化多个类呢,其实不是的,angular只实例化一个类,需要他的类共用他即可,而且是后面覆盖前面的.比如说我们看到在CommonService和IndexComponent中都需要Router,而IndexComponent又需要CommonService,所以笔者猜测是编译器先后初始化了两个Router,当第二个出现的时候,就会覆盖第一个,所以实际上IndexComponent和CommonService用的是一个Router,正所谓后者覆盖前者.当然为什么没有引用HttpClientModule.

引用的公有和私有

我们可以引用我们想要的东西,但是我们引用之后那些引用我们的人如果需要相同的东西他们还需要再引用一次吗,这就涉及到了引用的私有与公有.

declarations

declarations意为声明,一般情况下我们会在这个里面声明组建、指令和管道,使用declarations引用的都是私有的,即我们引用的组建、指令和管道之后这些在我们组建里面就会变成私有的,也就意味着下一个引用我们的人是不能够访问的,如果他们也需要同样的东西他们也需要再引一遍.

imports

imports意为引进,我么会把我们需要的模块全部放到他的里面,当然模块通过它引进来之后,同样也就变成了私有的,如同declarations一样.

providers

providers意为提供者,写法为

 providers: [
    {provide: CommonService, useClass: CommonStubService}
]

意为如果有CommonService,那么就用CommonService提供,如果没有那么就用类CommonStubService提供,当然useClass的位置还可以是useValue(提供值), useFactory(提供工厂)和useExisting(提供别名).
providers是公有制,即如果A提供了123,B提供了456,那么C同时引用A和B那么C就有了123456,和以上两种是不同的.

exports

exports意为出口,与imports含义相反,使用它可以将组建出口,即声明为公有类型,使得引入我们自己的组建可以使用我们出口的组建,例如:

@NgModule({
  declarations: [PageComponent],
  imports: [
    CommonModule
  ],
  exports: [PageComponent]
})
export class PageModule {
}

PageModule将PageComponent组建声明为公有的,即别的组建可以直接引用的.当然SizeModule也是这样的.

回顾引用悬案

我们之所以没有引用CommonService的提供者是因为

@NgModule({
  imports: [
    HttpClientModule
  ],
  providers: [
    {provide: CommonService, useClass: CommonStubService},
    {
      provide: HTTP_INTERCEPTORS, multi: true, useClass: MockApiTestingInterceptor.forRoot(apis)
    }]
})
export class ApiTestingModule {
}

我们可以看到我们在单元测试引用的ApiTestingModule中已经用CommonStubService provide了一个CommonSerivce了,此时我们需要实例化的就变成了CommonStubService,那我们就需要看看CommonStubService 到底需要什么了.
image.png
可以看到我们CommonStubService需要的是Router、DomSanitizer、ActivedRoute,我么有Router和ActivedRoute的提供者RoutingTestingModule的,DomSanitizer不属于模块,只需要引入命名空间.到现在CommonStubService实例化完毕,代替了CommonService.但是我们仍然需要实例化CommonService,因为我们的CommonStubService继承了CommonService,现在我们还需要的是HttpClient的能力吧,当然我们也是有的,就在我们的ApiTestingModule中引入了HttpClient这个组建,皆大欢喜,至此我们的两个类实例化成功!

总结

1、组建的所需要以及具有的能力要去找C层构造函数,V层代码.
2、被写道imports、declarations里面之后即变为私有,在providers里面为公有,exports可以声明为公有.
3、引用是可以被覆盖的,即后来者居上,后来者会覆盖掉之前的


陈大婷子
41 声望9 粉丝