angular 依赖注入 多级注入器

多级注入器

Angular中的注入器有一些规则,你可以利用这些规则来在应用程序中获得所需的可注入对象可见性。通俗的讲,这些规则指定了注入器的“作用域"。

两个注入器层次结构

Angular中有两个注入器层次结构:

  1. ModuleInjector层次结构 —— 在 @NgModule()@Injectable()中提供的服务。

  2. ElementInjector层次结构 —— 在 @Directive()@Component()中提供的服务。

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

  1. 优先查找ElementInjector,如果当前组件找不到提供者,将会去父组件中的ElementInjector

  2. 当所有的ElementInjector都找不到,就会去ModuleInjector中找

  3. 如果ModuleInjector也找不到,就会抛出一个错误

  4. 对于同名的令牌,只会解析遇到的第一个依赖

解析修饰符

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

  • 如果 Angular找不到你要的服务怎么办,用 @Optional()阻止报错

  • @SkipSelf()跳过自身,从父组件(指令)开始找

  • @Self()只在当前组件(指令)找

  • @Host()只在当前组件(指令)宿主上找

我们将创建一个logger组件和一个logger服务来验证上面的规则:

// logger.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class LoggerService {
  constructor() { }
  log(message: string) {
    console.log(message);
  }
}
// logger.component.ts
...
@Component({
  selector: 'app-logger',
  template: `<p>logger works!</p>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LoggerComponent implements OnInit {
  constructor(private loggerservice: LoggerService) { }
  ngOnInit(): void {
    this.loggerservice.log('这是组件内打印的日志');
  }
}

这样,我们没有在任何地方提供logger服务,浏览器会报没有提供服务的错:

在这里插入图片描述

当我们给服务加上@Optional()装饰器:

// logger.component.ts
constructor(@Optional() private loggerservice: LoggerService) { }

在看浏览器日志,原来没有提供服务的错误已经修复:

在这里插入图片描述

当我们将在组件内providers没有提供服务,而在根模块中提供logger服务:

// app.module.ts
@NgModule({
  //...
  providers: [LoggerService],
})

我们明确知道自身组件没有提供服务时,可以添加@SkipSelf()跳过自身:

// logger.component.ts
constructor(@Optional() @SkipSelf() private loggerservice: LoggerService) { }

服务依旧可用:

在这里插入图片描述

@Self()同理,当明确知道只需要在自身组件内找服务时使用。

对于@Self()@Host()的区别,官网有这么一句话:

@Host()会禁止在宿主组件以上的搜索。宿主组件通常就是请求该依赖的那个组件。 不过,当该组件投影进某个父组件时,那个父组件就会变成宿主。

那我们就使用投影来解释下这个区别。

ng g c components/logger/logger-content -s -t
// logger.component.ts
@Component({
  selector: 'app-logger',
  template: `
  <div>
    <p>logger works!</p>
    <ng-content></ng-content>
  </div>
  `,
  providers: [LoggerService]
})

调用logger-content组件:

<app-logger>
  <app-logger-content></app-logger-content>
</app-logger>

上面以内容投影的方式将logger-content组件引入到logger组件,并只在logger组件提供服务。

// logger-content.component.ts
export class LoggerContentComponent implements OnInit {
  // 使用@Host()装饰器
  constructor(@Host() private loggerservice: LoggerService) { }
  ngOnInit(): void {
    this.loggerservice.log('logger-content 组件打印的内容!');
  }
}

通过@Host(),我们是可以获取到logger-service的,即用@Host()依然能访问父组件的ElementInjector

我们知道,如果logger-content组件不是通过投影的方式引入,app-logger-content就是它的宿主。但是通过投影方式引入,引入它的组件就是它的宿主。(不太确定这样的说法是否正确,但通过@Host()装饰器的解释,或许可以这样理解)

其实一般情况下,@Host()可以完全替换@Self()的,他们之间的区别真的很微妙。

指定NgModule的注入

之前providedIn: 'root'指定从根模块中提供该服务,其实也可以像下面这样指定其它模块提供该服务。(这里有个前提:我们是将logger组件分配到ComponentsModule模块的)

// logger.service.ts
@Injectable({
  providedIn: ComponentsModule
})

如果这么做,意味着这个服务无法在模块该模块内部使用,编辑器会报一个循环依赖的警告:

在这里插入图片描述

浏览器也会报错:

在这里插入图片描述

所以,只能在其它引入了ComponentsModuleNgModule中,才能使用该服务。

viewProviders

使用 viewProviders数组是在 @Component()装饰器中提供服务的另一种方法。

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

viewProviders的修饰符与providers的修饰符用法一致。

<app-logger>
  <app-logger-content></app-logger-content>
</app-logger>

LoggerContentComponent无法找到LoggerComponent里用viewProviders提供的服务。

总结

1. Angular提供两个注入器层次结构:ModuleInjector层次结构及ElementInjector层次结构;
2. 服务查找规则可以通俗的理解为‘就近原则’;
3. 通过配置不同的解析修饰符,可以对程序进行一定程度的优化;
4. viewProviders对投影内容不可见。

欢迎关注我的公众号,公众号将第一时间更新angular教程:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yanyi24

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值