Angular(3)

什么是依赖注入

依赖注入(DI)是一种设计模式, 也有相应的框架,比如InversifyJS
Angular 有自己的 DI 框架, DI 框架会在实例化该类时向其提供这个类所声明的依赖项

带修饰符的参数

在ts中,一个类的参数如果带上修饰符,那个参数就变成了类的实例属性

class Mobile {
  constructor(readonly name: string = '小米') {}
  logName() {
    console.log(this.name);
  }
}

上面的name有修饰符,那么它就是Mobile类的实例属性,等同于下面写法:

class Mobile {
  readonly name: string;
  constructor() {
    this.name = '小米';
  }
  logName() {
    console.log(this.name);
  }
}

创建服务

cli创建 ng g c xxx

被@Injectable装饰的类,就是一个可以被注入的类,也称为服务(为其它服务或者组件、指令、管道提供服务)

import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
@Injectable({
  providedIn: 'root',
})
export class HeroService {
  constructor() { }
  getHeroes() { return HEROES; }
}

使用(注入)服务的方式

直接在参数中注入

import { Component }   from '@angular/core';
import { Hero }        from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'app-hero-list',
  template: `
    <div *ngFor="let hero of heroes">
      {{hero.id}} - {{hero.name}}
    </div>
  `
})
export class HeroListComponent {
  heroes: Hero[];
  //heroServe:HeroService;
  constructor(heroService: HeroService) {
      //this.heroServe = new Hervice();
    this.heroes = heroService.getHeroes();
      //console.log(this.heroServe.getHeroes());
  }
}

提供服务的地方

  • 在服务本身的 @Injectable() 装饰器中。
@Injectable({ providedIn: 'root', })  
  • 在 NgModule 的 @NgModule() 装饰器中。
@NgModule({
	providers: [
    LoggerService,
    // { provide: LoggerService, useClass: LoggerService } 
    //与上面写法相同,如果provide = useClass
  ]
})
  • 在组件的 @Component() 装饰器中。
@Component({
	providers: [
    LoggerService,
    // { provide: LoggerService, useClass: LoggerService } 
    //与上面写法相同,如果provide = useClass
        //子组件和自己使用
  ]
})

类提供者

上节课演示的就是类提供者的创建和使用 假设有logger类:

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

@Injectable()
export class LoggerService {
  logs: string[] = [];
  constructor() { }
  log(message: string) {
    this.logs.push(message);
    console.log(message);
  }
}

类提供者,使用useClass在NgModule中提供该服务

providers: [
    LoggerService,
    // { provide: LoggerService, useClass: LoggerService } 与上面写法相同
  ]

提供不同于令牌(provide)的类

//app.moudle.ts===========================================
providers: [
  //{ provide: LoggerService, useClass: LoggerService }
  { provide: LoggerService, useClass: BetterLoggerService }
]
//LoggerService===========================================
constructor(){}
log(msg:string){
    consolg.log('this is flower log')
}
//BetterLoggerService=====================================
constructor(){}
log(msg?:string){
    consolg.log('this is better log ' + msg)
}
//注入===============================================
constructor(private loggerServe:LoggerServe){}
ogOnInit():void{
    this.loggerServe.log('flower')
}

别名提供者

useExisting指向的服务一定是已经注册过的,这是和useClass的区别之一

providers: [
  UserLoggerService,
  // 如果用useClass, 则会得到两份UserLoggerService实例
  { provide: LoggerService, useExisting: UserLoggerService }
]

值提供者

对于很简单的值,没必要把它做成一个类,可用useValue提供简单的值

providers: [{ provide: LoggerService, useValue: 'simpleValue' }]

constructor(private loggerServe:LoggerServe){
	this.loggerServe     //simpleValue
}

非类令牌

上面每个provide都是一个类,那么也可以用其它数据类型作为令牌

providers: [{ provide: 'httpApi', useValue: '123.com' }]

注入方式

constructor(@Inject('httpApi') private uri:string){ }

ogOnInit():void{
    console.log('uri:',this.uri) //uri:123.com
}

InjectionToken

一般情况下无法使用ts接口作为令牌

interface AppConfig {
  apiEndpoint: string;
  title: string;
}

无法用AppConfig作为令牌:

[
  {
      provide: AppConfig,
      useValue: {
        apiEndpoint: 'api.heroes.com',
        title: 'Dependency Injection'
      }
}]

但又想要限制值的类型,可以借助InjectionToken

import { InjectionToken } from '@angular/core';
// 参数是该令牌的一个描述,可选
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config'); 
//小括号里面是描述,中括号为令牌
provides:[
    {
      provide: APP_CONFIG,
      useValue: {
        apiEndpoint: 'api.heroes.com',
        title: 'Dependency Injection'
    }
}]

注入方式

class AppComponent {
  constructor(@Inject(APP_CONFIG) config: AppConfig) {
    this.title = config.title;
  }
}

工厂提供者

自定义实例化服务 user-logger.service

import { Injectable } from '@angular/core';
import {UserService} from './user.service';
import {LoggerService} from './logger.service';

@Injectable()
export class UserLoggerService extends LoggerService {
  constructor(private userService: UserService, extra: string) {
    super();
    console.log('UserLoggerService', extra);
  }
  log(message: string) {
    const name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}
{
  provide: UserLoggerService,
  useFactory(userServe: UserService) {
    return new UserLoggerService(userServe, 'factory msg');
  },
  deps: [UserService] // 依赖其它服务的话,要列在这里
}

多级注入器

ElementInjector

  • 前两节,在@NgModule() 或 @Injectable() 提供的服务称为 ModuleInjector。

  • 在 @Directive() 或 @Component()里提供的服务称为 ElementInjector。

服务查找规则(令牌解析规则)

  1. 优先查找ElementInjector,如果当前组件找不到提供者,将会去父组件中的ElementInjector(组件自己的providers,以及他爹他爹的爹。。。的providers,直到找到)
  2. 当所有的ElementInjector都找不到,就会去ModuleInjector中找(自己的模块,以及父模块向上查找)
  3. 如果ModuleInjector也找不到,就会抛出一个错误
  4. 对于同名的令牌,只会解析遇到的第一个依赖(就近原则)

解析修饰符

默认情况下,Angular 始终从当前的 Injector 开始,并一直向上搜索。修饰符使你可以更改开始(默认是自己)或结束位置。

  • 如果 Angular 找不到你要的服务怎么办,用 @Optional()阻止报错
  • 用 @SkipSelf()跳过自身,从父组件(指令)开始找
  • 用@Self()只在当前组件(指令)找
  • 用@Host()只在当前组件(指令)宿主上找

指定NgModule的注入

之前providedIn: 'root'指定从根模块中提供该服务 也可以像下面这样指定其它模块提供该服务

@Injectable({
  providedIn: ComponentsModule
})

如果这么做,意味着这个服务无法在模块该模块内部使用,会报一个循环依赖的错误 只能在其它引入了ComponentsModule的NgModule中,才能使用该服务

viewProviders

上面说了,组件会从自身开始寻找服务,然后遍历父组件的ElementInjector,直到找到为止 viewProviders也有这个特性,区别是,对投影内容不可见

 <app-mobile-list>
    <app-mobile-content></app-mobile-content>
  </app-mobile-list>

MobileContent无法找到MobileList里,用viewProviders提供的服务

单例与多例

对于ModuleInjector来讲,不论服务在哪个NgModule提供,所有注入的地方获得的都是同一份实例
对于ElementInjector来讲,每个元数据中注入的服务都是一份单独的实例

@Host()

上节课并未看出与@self的区别,其实它们的区别表现在投影上 当一个组件投影进某个父组件时,那个父组件就会变成宿主。即用@Host依然能访问父组件的ElementInjector

上一篇:HITsz 数据库笔记


下一篇:word-wrap: break-word;与word-break: break-all;文本自动换行