来做注重注入的约束,0装饰器祥解

时间: 2019-08-13阅读: 264标签: 代码

图片 1

什么是装饰器?

依赖注入 是编写可测试/复用代码的关键。 在 TypeScript 中所有对象、属性和方法都有类型,可以大幅简化人工标注的代码,这让很多人重新考虑在 JavaScript 中实现依赖注入。 比如Angular2 以后的 DI 实现。 本文用来讨论 TypeScript 仍然无法解决哪些问题,以及相关技术可能存在的风险。

Angular架构

装饰器是一个函数,它将元数据添加到类、类成员(属性、方法)和函数参数。
装饰器是一个 JavaScript 的语言特性,装饰器在 TypeScript 里已经实现,并被推荐到了ES2016(也就是ES7)。
要想应用装饰器,把它放到被装饰对象的上面或左边。
Angular 使用自己的一套装饰器来实现应用程序各部件之间的相互操作
@NgModule装饰器
NModule是一个装饰器函数,它接收一个用来描述模块属性的元数据对象。其中最重要的属性是:

首先简单过一下基于 TypeScript 做依赖注入的步骤。 ES6 中提出了Reflect用来访问和操作对象对象属性。 而Reflect Metadata 提案让 Reflect API 可以提供对类型的元数据进行操作的方法。 这样就可以在 tsc 编译时产出注册元数据的代码,在运行时就可以读到编译时的类型了,这一类型就提供了依赖注入的 Token。 运行时的注入器根据函数签名的类型拿到依赖关系,再根据类型对应的 Provider 来创建依赖树。

用angular扩展语法编写html模板,用组件类管理这些模板,用服务添加应用逻辑,用模块打包发布组件与服务。

declarations
声明本模块中拥有的视图类。 Angular 有三种视图类:组件、指令和管道。
exports
declarations 的子集,可用于其它模块的组件模板。
imports
本模块声明的组件模板需要的类所在的其它模块。
providers
服务的创建者,并加入到全局服务列表中,可用于应用任何部分。
bootstrap
指定应用的主视图(称为根组件),它是所有其它视图的宿主。只有根模块才能设置bootstrap
属性。

装饰器可能和标准分裂

以上包括了Angular 8个主要的架构

@Component组件装饰器

TypeScript 编译 Reflect Metadata 需要打开一个叫做emitDecoratorMetadata的开关, 但这个开关只有在存在装饰器的方法上起作用。 不知这是设计缺陷还是故意的,总之要通过加装饰器来生成 metadata,两个特性是绑在一起的。 也就是说基于 TypeScript 做依赖注入一定要用 decorator。 坏消息是 ECMA 最新的Proposal Decorators和 TypeScript 提出的装饰器很不一样。 如果大量使用相关特性,后续可能面临代码迁移。

一、模块

Angular 应用是模块化的,并且 Angular 有自己的模块系统,它被称为 Angular 模块NgModules
   根模块在一些小型应用中可能是唯一的模块,大多数应用会有很多特性模块,没eigenvalue模块都是一个内聚的代码块 专注于某个应用领域、工作流或紧密相关的功能。
   angular模块无论是跟模块还是特性模块都是一个带有@NgModule装饰的类。
   装饰器用来修饰javascript类的函数。angular有很多装饰类,它们负责把元数据附加到类上,好让那些类知道是如何工作的。
   @NgModule是一个装饰函数,它接手一个用来描述模块属性的元素对象,其中最重要的属性是:

  • declarations - 声明本模块拥有视图类,Angular有三种视图类:组件、指令、管道
  • exports - declarations的自己,用于其他模块的组件模板
  • providers - 创建服务,并加入到全局服务列表中,可用于任何部分
  • bootstrap - 指定应用的主视图(成为根组件),它是所有视图的主视图,只有**根模块才能设置bootstrap属性。
    示例模版:
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
  imports:      [ BrowserModule ],
  providers:    [ Logger ],
  declarations: [ AppComponent ],
  exports:      [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

它把紧随其后的类标记成了组件类@Component装饰器能接受一个配置对象, Angular 会基于这些信息创建和展示组件及其视图。@Component的配置项包括:
moduleId: 为与模块相关的 URL(例如templateUrl)提供基地址。
selector: CSS 选择器,它告诉 Angular 在父级 HTML 中查找<hero-list>
标签,创建并插入该组件。 例如,如果应用的 HTML 包含<hero-list></hero-list>, Angular 就会把HeroListComponent
的一个实例插入到这个标签中。
templateUrl:组件 HTML 模板的模块相对地址,如前所示。
providers 组件所需服务的依赖注入提供商数组。 这是在告诉 Angular:该组件的构造函数需要一个HeroService
服务,这样组件就可以从服务中获得英雄数据

函数无法装饰

二、组件

图片 2

图意

组建 负责控制屏幕上的一小块区域,我们称它为视图。
   我们在类中定义组建的应用逻辑,为视图提供支持。组件通过一些由属性和方法组成的API与视图交互。
示例:

export class HeroListComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private service: HeroService) { }

  ngOnInit() {
    this.heroes = this.service.getHeroes();
  }

  selectHero(hero: Hero) { this.selectedHero = hero; }
}

以上HeroListComponent类有个heroes属性,它返回一个数组,这个数组从HeroService服务中获得。HeroListComponent还有一个selectHero()方法,当用户从列表中选中数组中一个,就把它设置到selectedHero属性。这里的HeroListComponent类它就是一个组件。

@Component里面的元数据会告诉 Angular 从哪里获取你为组件指定的主要的构建块。模板、元数据和组件共同描绘出这个视图
@Component扩展了@Directive, 以便@Directive中的配置项也能用在组件上moduleId: module.id如果设置了,templateUrl和styleUrl会被解析成相对于组件的。

目前 TypeScript 的编译器中,装饰器不能修饰函数(对象之外的独立 function)。 装饰器无法装饰独立的方法,也就是说无法为独立的工厂方法自动生成依赖列表。 只能把工厂方法改成工厂类,否则就需要手动声明依赖。

三、模板

图片 3

图意

  我们通过组件的自带模板来定义组件视图,模板以HTML形式存在,告诉Angular如何渲染组件。

<h2>Hero List</h2>
<p><i>Pick a hero from the list</i></p>
<ul>
  <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
    {{hero.name}}
  </li>
</ul>
<hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>

模版就可以理解为HTML元素,可以在typescript中直接写模版,也可单独建一个页面,然后引用该模版。在Angular中还可以自定义模版,以上<hero-detail></hero-detail>就是自定义的模版。

viewProviders: [MyService, { provide: ... }]

A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. –typescriptlang.org

四、元数据

元数据告诉Angular如何处理一个类。
  在TypeScript中。用装饰器来附加元数据。

@Component({
  selector:    'hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}

这里的@Component就是一个装饰器,它可以接受一个配置对象,然后Angular会基于这些信息创建和展示组件和视图。 。

  • selector:CSS选择器,它告诉Angular在父级HTML中查找<hero-list>标签,创建并插入该组件,例如,如果应用的 HTML 包含<hero-list></hero-list>, Angular 就会把HeroListComponent的一个实例插入到这个标签中。
  • templateUrl:组件HTML模板的模块相对地址。
  • providers: 组件所需服务的*服务依赖注入提供商数组。告诉Angular在该组件的构造函数需要一个HeroServive服务,这样组件就可以从服务中获得该数据。
      这里我的理解是元数据、模版、还有组件三个都存在就展现了视图,元数据告诉Angular从那个地方获取我们为组件制定的主要构建模块。

依赖注入provider的数组,局限于当前组件的视图中。

这就是为什么Angular 中只有 factory 需要手动声明依赖列表:

五、数据绑定

Angular有四种数据绑定形式:

    <li>{{hero.name}}</li>  
    <hero-detail [hero]="selectedHero"></hero-detail>
    <li (click)="selectHero(hero)"></li>  
    <input [(ngModel)]="hero.name">

第一种:显示组件的hero.name属性的值。
  第二种:属性绑定把负组件HeroListComponentselectedHero的值传到子组件HeroDetailComponenthero属性中。
  第三种:事件绑定在用户点击它的时候调用组件的selectHero方法。
  第四种:它使用ngModel指令组合了属性绑定和事件绑定的功能。
这里粗略的了解一下,日后深入。

template: 'Hello {{name}}'templateUrl: 'my-component.html'

export let heroServiceProvider = { provide: HeroService, useFactory: heroServiceFactory, deps: [Logger, UserService] };

六、指令

组件是一个带模板的指令@Component装饰器实际上就是一个@Directive装饰器,只是扩展了一些面向模板的特性。虽然严格来讲的话组件就是一个指令,但在Angular 4.0 中 指令被独立了出来。
  指令包括了两种类型指令:结构型指令和属性型指令。
   结构型指令就是在页面DOM中改变了页面结构,在DOM中添加、删除、和替换元素来修改了布局。
示例:

<li *ngFor="let hero of heroes"></li>  
<hero-detail *ngIf="selectedHero"></hero-detail>

*ngFor告诉 Angular 为heroes列表中的每个英雄生成一个<li>标签。
  *ngIf表示只有在选择的方法存在时,才会包含HeroDetail组件。
  属性型指令也就好理解了,就是修改元素的属性。示例:

<input [(ngModel)]="hero.name">

当前组件视图的内联模板或外部模板地址

NestJS也只有 factory 类型的 provider 需要提供 inject 数组:

七、服务

服务是一个广义的范畴,包括:值、函数,或应用所需的特性。 这是官方的解释,其实我也没明白服务具体到底是个什么鬼,也可能只是不理解。
   组件类应保持精简。组件本身不从服务器获得数据、不进行验证输入,也不直接往控制台写日志。 它们把这些任务委托给服务
  这里就不做示例了,还没有理解透。

styles: ['.primary {color: red}']styleUrls: ['my-component.css']

const connectionFactory = { provide: 'CONNECTION', useFactory: (optionsProvider: OptionsProvider) = { const options = optionsProvider.get(); return new DatabaseConnection(options); }, inject: [OptionsProvider],};

八、依赖注入

Angular使用依赖注入来提供新组件以及组件所需的服务。当Angular创建组件时,会首先为组件索取要的服务请求一个注入器(injector)
  注入器维护了一个服务实例的容器。存放着以前创建的实例。如果说请求的服务实例不在容器中,注入器就会创建一个服务实例,并且添加到容器中,然后把这个服务返回给Angular。当所有请求的服务都被解析完并返回时。Angular会以这些服务为参数去调用组件的构造函数。这就是依赖注入.
   如果注入器没有说需要的服务。我们就必须用注入器为需要的服务注册一个提供商(provider)。这个提供商用来创建或返回服务,通常就是这个服务类本身(相当new HeroServe())。
   我们可以在模块中或组件中注册提供商
   但通常会把提供商添加到根模块上,以便在任何地方都使用服务的同一个实例。 示例:

providers: [
  BackendService,
  HeroService,
  Logger
],   

   或者也可以在@Component元数据中的providers属性中把它注册在组件层:

@Component({
  selector:    'hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})

   把它注册在组件级表示该组件的每一个新实例都会有一个服务的新实例。
   关于依赖注入的要点是:

  • 依赖注入渗透在整个Angular框架中,被到处使用。
  • 注入器(injector) 是本机制的核心
  •    注入器负责维护一个容器,用于存放它创建过的服务实例。
  •    注入器能使用提供商创建一个新的服务实例。
  • 提供商是一个用于创建服务的地方。
  • 提供商注册到注入器。

  对于以上总结就是先创建模块,然后创建模版和组件,通过元数据展示到视图,组件管理模版,服务复杂应用的逻辑。
   后面配上代码。

内联CSS样式或外部样式表URL的列表,用于给组件的视图添加样式。

interface 仍然需要显式声明

****@Injectable装饰器

这一小节标题比较抽象,需要先看个例子。 这是一个依赖注入的典型场景(省略了一些注册、装饰器等操作):

声明当前类有一些依赖,当依赖注入器创建该类的实例时,这些依赖应该被注入到构造函数中。

// 声明一个 Person 类class Person { constructor(p: Parent, a: Age) {}}// 请求创建一个 Person 对象const person = injector.create(Person)

** @Pipe({...})装饰器** 声明当前类是一个管道,并且提供关于该管道的元数据

依赖注入的核心设计就是使用和创建分离。 这里我们要用一个 Person 对象,如何创建完全交给 injector,它会去分析并创建 Person 的依赖([Parent, Age]),然后再创建 Person 并返回。 如果 Parent 和 Age 不是具体的类而是接口,那么运行时拿到的依赖列表将会是[Object, Object],这样 injector 就无法知晓和创建依赖了。 所以对于接口类型,仍然需要声明一下给运行时一些信息,比如这样:

** @Directive装饰器**
声明当前类是一个指令,并提供关于该指令的元数据

class Person { constructor(@inject('IParnet') p: IParent, @inject('IAge') a: Age) {}}

** @Input() @Output() @HostBinding @HostListener @ContentChild装饰器**

其实类似这样的声明有个通用的名字叫做 Inject Token,本是用于基本数据类型注入,或希望通过类型之外的信息来创建的时候。 只是在 TypeScript 中,即使是非常常用的接口也必须采用这种相对复杂的写法。

@Input() myProperty;

原文:-limit-for-dependency-injection.html

声明一个输入属性,以便我们可以通过属性绑定更新它。(比如: <my-cmp [my-property]="someExpression">
).

@Output() myEvent = new EventEmitter();

声明一个输出属性,以便我们可以通过事件绑定进行订阅。(比如:<my-cmp (my-event)="doSomething()">
).

@HostBinding('[class.valid]') isValid;

把宿主元素的属性(比如CSS类:valid
)绑定到指令/组件的属性(比如:isValid
)。

@HostListener('click', ['$event']) onClick(e) {...}

通过指令/组件的方法(例如onClick
)订阅宿主元素的事件(例如click
),可选传入一个参数($event
)。

@ContentChild(myPredicate) myChildComponent;

把组件内容查询(myPredicate
)的第一个结果绑定到类的myChildComponent
属性。

@ContentChildren(myPredicate) myChildComponents;

把组件内容查询(myPredicate
)的全部结果,绑定到类的myChildComponents
属性。

@ViewChild(myPredicate) myChildComponent;

把组件视图查询(myPredicate
)的第一个结果绑定到类的myChildComponent
属性。对指令无效。

@ViewChildren(myPredicate) myChildComponents;

把组件视图查询(myPredicate
)的全部结果绑定到类的myChildComponents
属性。对指令无效

@SkipSelf装饰器
在注入器injector层级中的祖先injector中查找 CoreModule
,如果没有祖先injector则会提供一个CoreModule实例

本文由澳门威斯尼人平台登录发布于Web前端,转载请注明出处:来做注重注入的约束,0装饰器祥解

相关阅读