Angular依赖注入详解
<p>参考:<br>https://juejin.cn/post/7019184783242559496#heading-5</p>
<h2 id="依赖注入简介">依赖注入简介</h2>
<p>依赖注入是前端开发者也是 Angular 开发者一道很难迈过去的坎。软件只有到达了一定的复杂度才会需要各种设计原则和模式,那么依赖倒置原则(Dependency Inversion Principle )就是为了解决软件模块之间的耦合性提出的一种思想,让大型软件变的可维护,高层模块不应该依赖低层模块,两者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。那么控制反转(Inversion of Control) 就是依赖倒置原则的一种代码设计思路,具体采用的方法就是所谓的依赖注入(Dependency Injection),通过依赖注入实现控制权的反转,除了依赖注入外,还有可以通过模板方法模式实现控制反转,那么所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。</p>
<p>依赖注入的基本元素:</p>
<ul>
<li><strong>@Injectable()</strong> 装饰器来提供元数据,表示一个服务可以被注入的(在之前的版本中不加也是可以被注入的,后来5.0版本改成静态注入器后必须要标识一下才可以被注入,否则会报错)</li>
<li><strong>注入器(Injector)</strong>会创建依赖、维护一个容器来管理这些依赖,并尽可能复用它们,Angular 默认会创建各种注入器,甚至感觉不到他的存在,但是理解注入器的底层逻辑后再看依赖注入就更简单了</li>
<li><strong>@Inject()</strong> 装饰器表示要在组件或者服务中注入一个服务</li>
<li><strong>提供者(Provider</strong>是一个对象,用来告诉注入器应该如何获取或创建依赖值。</li>
</ul>
<h2 id="基本使用">基本使用</h2>
<p>在 Angular 中,通过 <code>@angular/cli</code> 提供的命令 <code>ng generate service heroes/hero</code> (简写 <code>ng g s heroes/hero</code>) 可以快速的创建一个服务,创建后的服务代码如下:</p>
<pre><code class="language-typescript">// src/app/heroes/hero.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor() { }
}
</code></pre>
<p>HeroService 通过 <code>@Injectable() </code>装饰器标记为可以被注入的服务,<code>providedIn: 'root'</code>表示当前服务在 Root 注入器中提供,简单理解就是这个服务在整个应用所有地方都可以注入,并全局唯一实例。<br>
添加完服务后,我们就可以在任何组件中通过构造函数注入 HeroService,通过 TS 的构造函数赋值属性的特性设置为公开,这样组件内和模板中都可以使用该服务端的函数和方法。</p>
<p>简化版的代码:</p>
<pre><code class="language-typescript">import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-list',
template: 'Heroes: {{heroes | json}}'
})
export class HeroListComponent implements OnInit {
heroes!: Hero[];
constructor(public heroService: HeroService) {}
ngOnInit(): void {
this.heroes = this.heroService.getHeroes();
}
}
</code></pre>
<h2 id="依赖注入中的provider">依赖注入中的Provider</h2>
<p>Provider的作用:</p>
<ul>
<li>告诉注入器如何提供依赖值</li>
<li>限制服务可使用的范围</li>
</ul>
<p>在组件或者模块中通过装饰器元数据 providers 定义提供者。 比如: 类提供者。</p>
<p><img src="https://img2022.cnblogs.com/blog/1507862/202210/1507862-20221015180533202-570832270.png"></p>
<ul>
<li>provide 属性是依赖令牌,它作为一个 key,在定义依赖值和配置注入器时使用,可以是一个类的类型、InjectionToken、或者字符串,甚至对象,但是不能是一个 Interface、数字和布尔类型</li>
<li>第二个属性是一个提供者定义对象,它告诉注入器要如何创建依赖值。 提供者定义对象中的 key 可以是 useClass —— 就像这个例子中一样。 也可以是 <code>useExisting</code> 、<code>useValue</code>或 <code>useFactory</code> , 每一个 key 都用于提供一种不同类型的依赖。</li>
<li><img src="https://img2022.cnblogs.com/blog/1507862/202210/1507862-20221015181446775-1923670569.png"></li>
</ul>
<h3 id="typeprovider--classprovider">TypeProvider & ClassProvider</h3>
<p>类提供者应该是最常用的一种,文章开始中的示例就是,简写和全写的配置如下:</p>
<pre><code class="language-typescript">provides: [ Logger ] // 简写
provides: [{ provide: Logger, useClass: Logger }]// 全写
</code></pre>
<p>所有 class 定义的服务默认都是用 类提供者<br>
指定替代性的类提供者,替换原有服务的行为实现可扩展性,这样我在使用的时候还是注入 Logger ,但是实际返回的对象是 <code>BetterLogger</code>。<br>
示例:</p>
<pre><code class="language-ts">@Injectable()
export class LoggerService {
constructor() {
}
logMessage(msg: string): void {
console.log(` ${msg}`);
}
}
@Injectable()
export class BetterLoggerService extends LoggerService{
constructor() {
super();
}
override logMessage(msg: string): void {
const date = new Date().toLocaleString();
console.log(`[${date}] ${msg}`);
}
}
</code></pre>
<p>使用:</p>
<pre><code class="language-ts">import {Component, Inject, InjectionToken} from '@angular/core';
import {BETTER_LOGGER_TOKEN, BetterLoggerService, LoggerService, silentLogger} from "./LoggerService";
@Component({
selector: 'app-root',
providers: [{provide: LoggerService, useClass: BetterLoggerService}],
})
export class AppComponent {
constructor(
private _logger: BetterLoggerService,
) {
this._logger.logMessage('welcome');
}
}
</code></pre>
<h3 id="existingprovider">ExistingProvider</h3>
<p>在下面的例子中,当组件请求新的或旧的 Logger 时,注入器都会注入一个 NewLogger 的实例。 通过这种方式, <code>OldLogger</code> 就成了 <code>NewLogger</code>的别名。</p>
<pre><code class="language-ts">[
NewLogger,
// Alias OldLogger reference to NewLogger
{ provide: OldLogger, useExisting: NewLogger}
]
</code></pre>
<ul>
<li><code>useExisting</code> 值是一个 DI Token ,provide 也是一个 DI Token, 2个 Token 指向同一个实例</li>
<li><code>useClass</code> 值是一个可以实例化的类,也就是可以 new 出来的类,这个类可以是任何类</li>
</ul>
<p>示例:</p>
<pre><code class="language-ts">export abstract class MinimalLogger {
abstract logs: string[];
abstract logInfo: (msg: string) => void;
}
{ provide: MinimalLogger, useExisting: LoggerService },
</code></pre>
<pre><code class="language-ts">// parent.ts
class abstract Parent {
...
}
// alex.component.ts
providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }]
class AlexComponent {
// ChildComponent
}
// child.component.ts
class ChildComponent {
constructor(parent: Parent)
}
</code></pre>
<h3 id="valueprovider">ValueProvider</h3>
<p>要注入一个对象,可以用 <code>useValue</code>选项来配置注入器,下面的提供者定义对象使用 <code>useValue</code> 作为 key 来把该变量与 <code>Logger</code> 令牌关联起来。</p>
<p>示例:</p>
<pre><code class="language-ts">@Injectable()
export class LoggerService {
constructor() {
}
timezone: string = '?'
logMessage(msg: string): void {
console.log(` ${msg}`);
}
}
export const silentLogger= {
timezone: 'ust-8',
logMessage: (msg: string) => {},
};
</code></pre>
<pre><code class="language-ts">@Component({
selector: 'app-root',
providers: [{provide: BetterLoggerService, useValue: silentLogger}],
})
export class AppComponent {
title = 'graphql-ang-demo';
constructor(
private _logger: BetterLoggerService,
) {
this._logger.logMessage(this._logger.timezone);
this._logger.logMessage('welcome');
}
}
</code></pre>
<h3 id="factoryprovider">FactoryProvider</h3>
<p>Factory需要提供一个方法,通过调用方法构建出Service。<br>
示例:</p>
<pre><code class="language-ts">export const FactoryLoggerService = (loggerService: LoggerService) => {
console.log('Use a factory');
return new BetterLoggerService();
}
</code></pre>
<pre><code class="language-ts">@Component({
selector: 'app-root',
providers: [{provide: BetterLoggerService, useFactory: FactoryLoggerService}],
})
export class AppComponent {
title = 'graphql-ang-demo';
constructor(
private _logger: BetterLoggerService,
) {
this._logger.logMessage(this._logger.timezone);
this._logger.logMessage('welcome');
}
</code></pre>
<h3 id="injectiontoken">InjectionToken</h3>
<p>每当你要注入的类型无法确定(没有运行时表示形式)时,比如在注入接口、可调用类型、数组或参数化类型时,都应使用 <code>InjectionToken</code>。</p>
<p><code>InjectionToken</code> 在 T 上的参数化版本,T 是 <code>Injector</code> 返回的对象的类型。这提供了更高级别的类型安全性。</p>
<p>当创建 <code>InjectionToken</code> 时,可以选择指定一个factory函数,该函数返回(可能通过创建)参数化类型 T 的默认值。这将使用工厂型提供者设置 <code>InjectionToken</code>,就像它是在应用程序的根注入器中显式定义的一样。如果使用需要注入依赖项的零参数工厂函数,则可以使用 <code>inject</code> 函数来这样做。参见以下示例。</p>
<pre><code class="language-ts">export const BETTER_LOGGER_TOKEN = new InjectionToken<BetterLoggerService>('app.betterLogger',
{
providedIn: 'root',
factory: () => {
console.log('created a logger by InjectionToken')
return new BetterLoggerService()
},
},
);
</code></pre>
<pre><code class="language-ts">@Component({
selector: 'app-root',
// 这里不写ProvidedIn
})
export class AppComponent {
title = 'graphql-ang-demo';
constructor(
@Inject(BETTER_LOGGER_TOKEN) private _logger: BetterLoggerService
) {
console.log(this._logger.timezone);
this._logger.logMessage(this._logger.timezone);
this._logger.logMessage('welcome');
}
}
</code></pre>
<h2 id="多极注入器multiinjector">多极注入器(MultiInjector)</h2>
<p>通过依赖注入的概念我们知道,创建实例的工作都交给 Ioc 容器(也就是注入器)了,通过构造函数参数装饰器<code>@Inject(DIToken)</code> 告诉注入器我们需要注入 <code>DIToken</code> 对应的依赖,注入器就会帮我们查找依赖并返回值,Angular 应用启动会默认创建相关的注入器,而且 Angular 的注入器是有层级的,类似于一个 Dom 树。</p>
<p>Angular 中有两个注入器层次结构:</p>
<ul>
<li><code>ModuleInjector</code> —— 使用 <code>@NgModule()</code> 或<code>@Injectable()</code> 装饰器在此层次结构中配置 <code>ModuleInjector</code>。</li>
<li><code>ElementInjector</code>—— 在每个 DOM 元素上隐式创建。除非你在 <code>@Directive()</code>或<code>@Component()</code>的 providers 属性中进行配置,否则默认情况下,<code>ElementInjector</code> 为空</li>
</ul>
<h3 id="moduleinjector">ModuleInjector</h3>
<p>可以通过以下两种方式之一配置 ModuleInjector :</p>
<ul>
<li>使用<code>@Injectable()</code> 的 <code>providedIn</code> 属性引用 <code>NgModuleType</code> 、<code>root</code>、<code>platform</code> 或者 <code>any</code> 。</li>
<li>使用<code>@NgModule()</code> 的 <code>providers</code> 数组。</li>
</ul>
<blockquote>
<p>Tree-shaking and <code>@Injectable()</code> - 摇树优化与 <code>@Injectable()</code><br>
使用 <code>@Injectable()</code>的 <code>providedIn</code> 属性优于 <code>@NgModule()</code>的 <code>providers</code> 数组,因为使用 <code>@Injectable()</code> 的 <code>providedIn</code> 时,优化工具可以进行摇树优化,从而删除你的应用程序中未使用的服务,以减小捆绑包尺寸。摇树优化对于库特别有用,因为使用该库的应用程序不需要注入它。在 服务与依赖注入简介了解关于可摇树优化的提供者的更多信息。<br>
需要特别注意:<code>ModuleInjector</code> 由<code>@NgModule.providers</code> 和 <code>NgModule.imports</code> 属性配置。<code>ModuleInjector</code> 是可以通过 <code>NgModule.imports</code> 递归找到的所有 <code>providers</code> 数组的扁平化。子 <code>ModuleInjector</code> 是在惰性加载其它 <code>@NgModules</code> 时创建的。</p>
</blockquote>
<h4 id="root和nullinjector">root和NullInjector</h4>
<p>所有非<code>LazyLoader</code>模块的 <code>providers</code> 和<code>@Injectable({providedIn: "root"})</code> 供应商都时在<code>root</code>根注入器中提供,那么在 <code>root</code> 之上还有两个注入器,一个是额外的平台 <code>ModuleInjector</code> ,一个是<code>NullInjector</code>。</p>
<p><img src="https://img2022.cnblogs.com/blog/1507862/202210/1507862-20221016162220269-138165231.png"></p>
<h4 id="providein-any--root--platform--ngmoduletype">provideIn: "any" | "root" | "platform" | NgModuleType</h4>
<ul>
<li><code>root</code> 表示在根模块注入器(root ModuleInjector)提供依赖</li>
<li><code>platform</code> 表示在平台注入器提供依赖</li>
<li>指定模块表示在特定的特性模块提供依赖(注意循环依赖)</li>
<li><code>any</code>所有急性加载的模块都会共享同一个服务单例,惰性加载模块各自有它们自己独有的单例</li>
</ul>
<h3 id="elementinjector">ElementInjector</h3>
<p>除了模块注入器外,Angular 会为每个 DOM 元素隐式创建 <code>ElementInjector</code>。<br>
可以用<code>@Component()</code> 装饰器中的<code>providers</code> 或 <code>viewProviders</code> 属性来配置 <code>ElementInjector</code> 以提供服务。</p>
<pre><code class="language-ts">@Component({
...
providers: [{ provide: ItemService, useValue: { name: 'lamp' } }]
})
export class TestComponent
</code></pre>
<p>注意事项:</p>
<ul>
<li>在组件中提供服务时,可以通过 <code>ElementInjector</code>在该组件以及子组件/指令处通过注入令牌使用该服务</li>
<li>当组件实例被销毁时,该服务实例也将被销毁</li>
<li>Component是一种特殊类型的Directive,这意味着 <code>@Directive()</code> 和 <code>@Component()</code> 都具有 <code>providers</code> 属性</li>
</ul>
<h3 id="解析规则">解析规则</h3>
<p>前面已经介绍了 Angular 会有2个层级的注入器,那么当组件/指令解析令牌时,Angular 分为两个阶段来解析它:</p>
<ul>
<li>针对 ElementInjector 层次结构(其父级)</li>
<li>针对 ModuleInjector 层次结构(其父级)</li>
</ul>
<p>Angular 会先从当前组件/指令的<code>ElementInjector</code> 查找令牌,找不到会去父组件中查找,直到根组件,如果根还找不到,就去当前组件所在的模块注入器中查找,如果不是懒加载那么就是根注入器,一步一步到最顶层的<code>NullInjector</code>,整个解析过程如下所示:</p>
<p><img src="https://img2022.cnblogs.com/blog/1507862/202210/1507862-20221016163231186-1038497208.png"></p>
<h3 id="解析修饰符">解析修饰符</h3>
<p>修饰符可以更改开始(默认是自己)或结束位置,从而达到一些高级的使用场景</p>
<h4 id="optional">@Optional</h4>
<p><code>@Optional()</code> 允许 Angular 将你注入的服务视为可选服务。这样,如果无法在运行时解析它,Angular 只会将服务解析为 null,而不会抛出错误。</p>
<pre><code class="language-ts">export class OptionalComponent {
constructor(@Optional() public optional?: OptionalService) {}
}
</code></pre>
<h4 id="self">@Self</h4>
<p><code>@Self()</code>让 Angular 仅查看当前组件或指令的 ElementInjector 。可以和<code>@Optional</code>组合使用</p>
<pre><code class="language-ts">@Component({
selector: 'app-self-no-data',
templateUrl: './self-no-data.component.html',
styleUrls: ['./self-no-data.component.css']
})
export class SelfNoDataComponent {
constructor(@Self() @Optional() public flower?: FlowerService) { }
}
</code></pre>
<h4 id="skipself">@SkipSelf</h4>
<p><code>@SkipSelf()</code> 与 <code>@Self()</code>相反,使用<code>@SkipSelf()</code>,Angular 在父 <code>ElementInjector</code> 中开始搜索服务,而不是从当前 <code>ElementInjector</code> 中开始搜索服务。</p>
<pre><code class="language-ts">@Injectable({
providedIn: 'root'
})
export class FlowerService {
emoji = '🌿';
constructor() {}
}
</code></pre>
<pre><code class="language-ts">import { Component, OnInit, SkipSelf } from '@angular/core';
import { FlowerService } from '../flower.service';
@Component({
selector: 'app-skipself',
templateUrl: './skipself.component.html',
styleUrls: ['./skipself.component.scss'],
providers: [{ provide: FlowerService, useValue: { emoji: '🍁' } }]
})
export class SkipselfComponent implements OnInit {
constructor(@SkipSelf() public flower: FlowerService) {}
ngOnInit(): void {}
}
</code></pre>
<p>上面的示例会得到 root 注入器中的 🌿,而不是组件所在的 ElementInjector 中提供的 🍁。 如果值为 null 可以同时使用 <code>@SkipSelf()</code> 和 <code>@Optional()</code> 来防止错误。</p>
<h4 id="host">@Host</h4>
<p>@Host() 使你可以在搜索提供者时将当前组件指定为注入器树的最后一站,即使树的更上级有一个服务实例,Angular 也不会继续寻找。</p>
<pre><code class="language-ts">@Component({
selector: 'app-host',
templateUrl: './host.component.html',
styleUrls: ['./host.component.css'],
//provide the service
providers: [{ provide: FlowerService, useValue: { emoji: '🌼' } }]
})
export class HostComponent {
// use @Host() in the constructor when injecting the service
constructor(@Host() @Optional() public flower?: FlowerService) { }
}
</code></pre>
<p>由于 <code>HostComponent</code> 在其构造函数中具有 <code>@Host()</code> ,因此,无论 <code>HostComponent</code> 的父级是否可能有 <code>flower.emoji</code> 值,该 <code>HostComponent</code> 都将使用 🌼</p><br><br>
来源:https://www.cnblogs.com/Asp1rant/p/16794671.html
頁:
[1]