依赖注入这部分分为两部分来学习。第一部分自然是官网上的文档,另外一部分,是自己的“血泪时间史”。
之所以称之为“血泪时间史”,是因为在这部分上花费的时间实在是有点多,也就是前面提到过的“时间的教训”,所以在这里要记录下来,避免下次再犯这样的错误。
依赖注入
依赖注入是一个用来管理代码依赖的强大模式。
应用程序全局依赖
在这里主要说的是,在应用程序根组件AppComponent中注册那些被应用程序全局使用的依赖提供商。
import { LoggerService } from './logger.service';
import { UserContextService } from './user-context.service';
import { UserService } from './user.service';
@Component({
moduleId: module.id,selector: 'my-app',templateUrl: 'app.component.html',providers: [ LoggerService,UserContextService,UserService ]
})
export class AppComponent {
/* . . . */
}
在@Component元数据的providers数组导入和注册了几个服务,这些服务都是使用类来实现的,所以服务类能充当自己的提供商。==> 泪在providers
数组中就算是注册成功了。
@Injectable()
- @Injectable装饰器只在一个服务类有自己的依赖的时候,才是必不可少的。
@Injectable()
export class UserContextService {
constructor(private userService: UserService,private loggerService: LoggerService) {
}
}
这个@Injectable()就是必不可少的。
- AppComponent类有两个依赖,但它没有@Injectable()。 它不需要@Injectable(),这是因为组件类有@Component装饰器。 在用TypeScript的Angular应用里,有一个单独的装饰器 — 任何装饰器 — 来标识依赖的类型就够了。
把服务作用域限制到一个组件支树
一个组件中注入的服务依赖,会在该组件的所有子组件中可见,而且Angular会把同样的服务实例注入到需要该服务的子组件中。
这刚好在实际工作有遇到这样的例子。
我在sino-base-data-service.component
组件中注入了BaseDataService
,那么它的所有子组件sino-list.component
就都能访问到这个service。
多个服务实例
主要讲的是,在组件级别注入服务,每个组件就能拥有自己独立的service实例。这样就能保证每个组件都有自己的服务。每个组件都有自己的工作状态,与其他组件的服务与状态相隔离,这种我们成为沙盒化。每个服务和组件都在自己的沙盒里运行。
@Optional 和 @Host
当组件申请一个依赖时,Angular从该组件本身的注入器开始,沿着依赖注入器的树往上找,直到找到第一个符合要求的提供商。如果罩杯多就会抛出一个错误。
@Optional
当Angular找不到依赖时,@Optional装饰器会告诉Angular继续执行,Angualr会把此注入参数设置为null(而不是默认的抛出错误的行为)。
@Host
该装饰器将把往上搜索的行为截止在宿主组件。
使用提供商来定义依赖
- useValue - 值提供商
- useClass
- useExisting
- useFactory
useValue
通常在单元测试中使用。useValue的值必须是立即定义的。
e.g.
{ provide: Hero,useValue: someHero },{ provide: TITLE,useValue: 'Hero of the Month' },
useClass
useClasst提供商创建并返回一个指令类的新实例。
使用该技术来为公共或默认类提供备选实现。该替代品能实现一个不同的策略,比如拓展默认类或者在测试的时候假冒真实类。
{ provide: HeroService,useClass: HeroService },{ provide: LoggerService,useClass: DateLoggerService },
useExisting - 别名-提供商
使用useExisting,提供商可以把一个令牌映射到另一个令牌上。实际上,第一个令牌是第二个令牌所对应的服务的一个别名,创造了访问同一个服务对象的两种方法。
{ provide: MinimalLogger,useExisting: LoggerService },
通过使用别名接口来把一个API变窄,是一个很重要的该技巧的使用例子.
useFactory - 工厂-提供商
useFactory提供商通过调用工厂函数来新建一个依赖对象。
{ provide: RUNNERS_UP,useFactory: runnersUpFactory(2),deps: [Hero,HeroService] }
知识点
- 令牌 - 使用提供商来定义依赖
我们通常在构造函数里面,为参数指定类型,让Angular来处理依赖注入。该参数类型就是依赖注入器所需的令牌。 Angular把该令牌传给注入器,然后把得到的结果赋给参数。
使用angular的DI系统来获取参数时,必须将该令牌需要依赖的其他服务一并注入。
e.g.
要模拟BaseDataService
必须有ModuleConfig
export class CrudModule {
static forRoot(config: any,routeConfig?: any): ModuleWithProviders {
return {
ngModule: CrudModule,providers: [
BaseDataService,{
provide: ModuleConfig,useValue: config,},{
provide: RouteConfig,useValue: routeConfig ? routeConfig : DEFAULT_ROUTE_CONFIG,],};
}
}
@Injectable()
export class SinoListComponent implements OnInit {
constructor(
private config: ModuleConfig,private baseDataService: BaseDataService,) {}
}
ng-content
把对应组件中的内容投影进组件的视图中。
<ion-content> <sino-loading-hint [state]="state" loadingTitle="loading..." needRefresh=true (onVoted)="onVoted($event)"> <h2 class="sino-list-title">{{title}}</h2> <div class="sino-list" *ngFor="let item of datas; let i = index" (click)="toDetail(item,i)"> <h3>{{item.title}}</h3> <p> 来自:{{item.ngDepartmentName}}的发文申请<br/> <span>发送时间:{{item.signDate}}</span> </p> </div> </sino-loading-hint> </ion-content>
<div class="sino-loading"> // 当state为2时,展示sino-loading-hint包裹的内容。上面�� <ng-content class="sino-loading-hint-content" *ngIf="showContent && state ===2"></ng-content> <div class="sino-loading-hint"> <div *ngIf="state ===1 || state === 3 || state === 4" class="sino-loading-hint-word"> <div class="loading" *ngIf="state === 1"> <ion-spinner class="icon" ></ion-spinner> <span>{{loadingTitle}}</span> </div> <span *ngIf="state === 3">{{errorTitle}}</span> <span *ngIf="state === 4">{{nodataTitle}}</span> </div> <div> <button *ngIf="state == 3 && needRefresh" class="button" (click)="refresh()" [disabled]="voted">重新加载</button> <button *ngIf="state == 4 && needRefresh" class="button" (click)="refresh()" [disabled]="voted">刷新</button> </div> </div> </div>