Angular装饰器介绍

       装饰器的作用就是在添加装饰器的地方在不改动原有代码的情况下增加额外的功能。Angular框架中装饰器是一个函数。他将元数据添加到类、类成员(属性、方法)和函数参数上。让它们在不需要做任何代码变动的前提下增加额外功能。

1 类装饰器

       类装饰器负责把元数据附加到类上,以了解类的设计意图以及这个类是如何工作的。Angular框架里面类装饰器有多种.如下所示:

装饰器 解释 备注
@NgModule 模块装饰器(帮把相关的一些代码逻辑组织在一起) NgModule 可以将其组件和一组相关代码(如服务)关联起来,形成功能单元。每个Angular应用都有一个根模块,通常命名为AppModule
@Component 组件装饰器 组件可以认为是屏幕上的一个视图. 组件定义视图。每个Angular应用都至少有一个组件,也就是根组件
@Injectable 依赖装饰器(把一个类定义为服务) 组件使用服务。对于与特定视图无关并希望跨组件共享的数据或逻辑,可以创建服务类。
@Pipe 管道装饰器 管道的作用就是传输。并且不同的管道具有不同的作用。(其实就是处理数据)
@Directive 指令装饰器 用来控制组件的某些行为

1.1 @NgModule

       @NgModule用来描述Angular应用里面的模块(NgModule)的。也就是说NgModule是一个带有@NgModule装饰器的类。@NgModule的参数是一个元数据对象,用于描述如何编译组件的模板,以及如何在运行时创建注入器。

和其他语言一样,模块指的就是把某些功能整合到一起形成一个模块.模块话编程.

       Angular应用是模块化的,拥有自己的模块化系统.一个模块就是一个NgModule。每个NgModule就是一个容器,用于存放一些内聚的代码块,这些代码块专注于某个应用领域、或者某个工作流或一组紧密相关的功能。它可以包含一些组件、服务提供商或其它代码文件,其作用域由包含它们的NgModule定义。它还可以导入一些由其它模块中导出的功能,并导出一些指定的功能供其它NgModule使用。

       每个Angular应用至少有一个模块,也就是根模块,习惯上命名为AppModule,位于一个名叫app.module.ts的文件中。引导这个根模块就可以启动你的应用。

1.1.1 @NgModule元数据解释

选项 类型 说明
providers? Provider[] 列出当前模块需要的一些共用的服务,这样我们就可以在这个模块的各个组件中通过依赖注入使用这些服务了
declarations? Array<Type 声明属于这个模块的指令,管道等等。模块内部Components/Directives/Pipes的列表
imports? Array<Type | ModuleWithProviders | any[]> 当前模块需要依赖的一些其他的模块,这样做的目的就是使我们这个模块,可以直接使用别的模块exports提供的一些指令,组件等等
exports? Array<Type | any[]> 当前模块需要导出的一些组件,指令,模块等,这样如果别的模块导入了我们这个模块,那么别的模块就可以直接使用我们在这里导出的组件,指令模块等
entryComponents? Array<Type | any[]> 一个组件的集合,它应该和当前组件一起编译。对于这里列出的每个组件,Angular 都会创建一个 ComponentFactory 并保存进 ComponentFactoryResolver 中(动态组件)
bootstrap? Array<Type | any[]> 应用的主视图,称为根组件。它是应用中所有其它视图的宿主。只有根模块才应该设置这个 bootstrap 属性
schemas? Array<SchemaMetadata | any[]> 不属于Angular的组件或者指令的元素或者属性都需要在这里进行声明,允许设置的值有: NO_ERRORS_SCHEMA 和 CUSTOM_ELEMENTS_SCHEMA。NO_ERRORS_SCHEMA: 当前模板中存在未知选择器时它不会显示错误;CUSTOM_ELEMENTS_SCHEMA: 当前模板中可以使用任何类型的自定义元素,这有助于在应用程序中合并除Angular组件之外的Web组件
id? string 它可以是一个名字或者一个路径,用来在调用getModuleFactory的时候区别模块,如果这个属性是undefined那么这个模块将不会被注册,如果有设置id,可以通过getModuleFactory()来获取到当前模块
jit? true 如果是true,则当前模块使用JIT编译,否则使用AOT编译。JIT: 即Just-in-time,动态(即时)编译,边运行边编译。AOT: Ahead Of Time,指运行前编译。

1.1.2 NgModule作用:

  • NgModule 最主要的作用是帮助开发者组织业务代码,把关系比较紧密的一些功能(组件)代码组合在一起。

  • NgModule 用来控制组件、指令、管道等是否可以使用,处于同一个 NgModule 里面的组件默认互相可见,而对于外部的组件来说,只能看到 NgModule 导出(exports )的内容,也就是说,如果你定义的 NgModule不exports任何内容,那么外部使用者即使 import了你这个模块,也没法使用里面定义的任何内容。

  • NgModule 是打包时候用到的最小单位,打包的时候会检查所有 @NgModule 和路由配置,Angular底层是使用webpack打包。因为Angular已经帮我们配置好了webpack,所以开发者轻松很多,否则就需要自己配置环境。

1.2 @Directive

       @Directive修饰的类是指令类。在Angular应用中指令一般用来控制组件(DOM)的某些行为。Angular框架也默认给我们提供很多指令,有结构型指令,属性型指令。

  • 结构型指令: 通过添加和移除DOM元素改变DOM布局的指令. 如NgFor和NgIf.
  • 属性型指令: 改变元素、组件或其它指令的外观和行为的指令。 如NgStyle 和NgSwitch.

1.1.1 @Directive元数据解释

选项 类型 说明
selector? string css选择器名,用于在模板中标记出该指令(组件),并触发其实例化
inputs? string[] 该指令(组件)的输入参数,和@Input装饰器的作用是相同的
outputs? string[] 该指令(组件)可供事件绑定的输出属性
host? {[key: string]: string;} 使用一组键-值对,把类的属性映射到宿主元素的绑定(Property、Attribute 和事件)
providers? Provider[] 服务提供商的集合
exportAs? string 一个或多个名字,可以用来在模板中把该指令赋值给一个变量。当有多个名字时,请使用逗号分隔它们
queries? {[key:string]:any} 配置将要注入到该指令中的一些查询。内容查询会在调用 ngAfterContentInit 回调之前设置好。 试图查询会在调用 ngAfterViewInit 回调之前设置好。

inputs和outputs: 当前指令(组件)的输入和输出。但是最新的Angular版本已经不推荐使用这两个属性了,推荐使用@Input(),@Output()来代替。

@Component({
  selector: 'app-report-template',
  template:
    `
       <div>
         <pre>{{hero}}</pre>
         <button (click)="deleteRequestInit()">Get</button>
       </div>
     `,
  inputs: ['hero'],
  outputs: ['deleteRequest'],
})
export class ReportTemplateComponent {

  public deleteRequest = new EventEmitter();

  deleteRequestInit() {
    this.deleteRequest.emit({'message': 'Are you sure you want to delete this record!.'})
  }
}

// 等价于下面的代码

@Component({
  selector: 'app-report-template',
  template:
    `
       <div>
         <pre>{{hero}}</pre>
         <button (click)="deleteRequestInit()">Get</button>
       </div>
     `,
})
export class ReportTemplateComponent {

  @Input()
  hero;

  @Output()
  deleteRequest = new EventEmitter<any>();

  deleteRequestInit() {
    this.deleteRequest.emit({'message': 'Are you sure you want to delete this record!.'})
  }
}

host:使用一组键-值对,把类的属性映射到宿主元素的绑定(Property、Attribute和事件)。

      Angular在变更检测期间会自动检查宿主Property绑定。如果绑定的值发生了变化,Angular 就会更新该指令的宿主元素。当 key 是宿主元素的 Property 时,这个 Property 值就会传播到指定的 DOM 属性。当 key 是 DOM 中的静态 Attribute 时,这个 Attribute 值就会传播到宿主元素上指定的 Property 去。对于事件处理:它的 key 就是该指令想要监听的 DOM 事件。 要想监听全局事件,请把要监听的目标添加到事件名的前面。 这个目标可以是 window、document 或 body。它的value就是当该事件发生时要执行的语句。如果该语句返回 false,那么就会调用这个DOM事件的preventDefault函数。这个语句中可以引用局部变量 $event 来获取事件数据。比如如下的代码实例。

    @Component({
       host: {
         '[class.nav-static]' : 'config.state["nav-static"]',
         '[class.chat-sidebar-opened]' : 'chatOpened',
         '[class.app]' : 'true',
         id: 'app'
       }
     })
   
    @Component({
       host: {
         'class' : 'myclass1 myclass2 myclass3'
       }
     })
   
    @Component({
       host: {
         '(window:blur)': 'focusOutFunction($event)',
         '(window:focus)': 'focusInFunction($event)',
       }
     })

exportAs:一个或多个名字,可以用来在模板中把该指令赋值给一个变量。当有多个名字时,请使用逗号分隔它们,我们用一个简单的例子来说明下:

   1. 定义一个指令 AdHostDirective,exportAs: 'adHost'
    @Directive({
       selector: '[ad-host]',
       exportAs: 'adHost'
    })
    export class AdHostDirective {
       constructor(public viewContainerRef: ViewContainerRef) {
       }
    }
   
   
    2. 在模板中使用该指令,我们同事添加了两次 #test='adHost',#test1='adHost'
   
     <div>
       <!-- 动态组件存放的容器 -->
       <ng-template ad-host #test='adHost'></ng-template>
       <!-- 动态组件存放的容器 -->
       <ng-template ad-host #test1='adHost'></ng-template>
     </div>
   
  
    3. 最后我们需要在对应的ts里面获取到该指令对象

     // @ViewChild(AdHostDirective) adHost: AdHostDirective; 单个的情况,可以使用,多个的情况比较麻烦
     @ViewChild('test') adHost: AdHostDirective;
     @ViewChild('test1') adHost1: AdHostDirective;
   
   
    通过exportAs 如果同意个模板里面有同一个指令多个的话,我们可以很简单的获取到对应的指令。如果不用这种方式很麻烦的

queries:配置将要注入到该指令(组件)中的一些查询,内容查询会在调用 ngAfterContentInit 回调之前设置好。 试图查询会在调用 ngAfterViewInit 回调之前设置好。说白了queries的使用就是@ViewChild,@ViewChildren,@ContentChil,@ContentChildren的使用。比如如下两段代码是等价的。

@Component({
  selector: 'app-report-template',
  templateUrl: './report-template.component.html',
  styleUrls: ['./report-template.component.less'],
  queries: {
    adHost: new ViewChild(AdHostDirective)
  }
})
export class ReportTemplateComponent {
  
  adHost: AdHostDirective;

}

// 等价于下面的代码

@Component({
  selector: 'app-report-template',
  templateUrl: './report-template.component.html',
  styleUrls: ['./report-template.component.less'],
  viewProviders: [ReportTemplateService]
})
export class ReportTemplateComponent {
  
  @ViewChild(AdHostDirective) 
  adHost: AdHostDirective;

}

1.2 @Component

       声明一个组件时,在组件类上要用@Component装饰器来告知Angular这是一个组件。

       组件控制屏幕上被称为视图的一小片区域。在组件类总定义组件的应用逻辑。在Angular应用中组件(Component)是属于模块(NgMoude)的。组件是比模块更小的一个单元。

1.2.1 @Component元数据解释

       因为Component是继承Directive的,Component也是指令的一种.所以指令能做到的事情Component也能做到.上面讲的@Directive元数据都适用于@Component。

除了@Directive的元数据之外,@Component元数据元数据还有如下属性。

选项 类型 说明
changeDetection? ChangeDetectionStrategy 当组件实例化之后,Angular 就会创建一个变更检测器,它负责传播组件各个绑定值的变化。 该策略是下列值之一:ChangeDetectionStrategy#OnPush(0) 把策略设置为 CheckOnce(按需);ChangeDetectionStrategy#Default(1) 把策略设置为 CheckAlways
viewProviders? Provider[] 定义一组可注入对象,它们在视图的各个子节点中可用
moduleId? string 包含该组件的那个模块的 ID。该组件必须能解析模板和样式表中使用的相对 URL。 SystemJS 在每个模块中都导出了 __moduleName 变量。在 CommonJS 中,它可以设置为module.id
templateUrl? string 组件模板文件的 URL。如果提供了它,就不要再用 template 来提供内联模板了
template? string 组件的内联模板。如果提供了它,就不要再用 templateUrl 提供模板了
styleUrls? string[] 一个或多个 URL,指向包含本组件 CSS 样式表的文件
styles? string[] 本组件用到的一个或多个内联 CSS 样式
animations? any[] 一个或多个动画 trigger() 调用,包含一些 state() 和 transition() 定义
encapsulation? ViewEncapsulation 供模板和 CSS 样式使用的样式封装策略
interpolation? [string, string] 改写默认的插值表达式起止分界符({{ 和 }})
entryComponents? Array<Type | any[]> 一个组件的集合,它应该和当前组件一起编译。对于这里列出的每个组件,Angular 都会创建一个 ComponentFactory 并保存进 ComponentFactoryResolver 中
preserveWhitespaces? boolean 为 true 则保留,为 false 则从编译后的模板中移除可能多余的空白字符。 空白字符就是指那些能在 JavaScript 正则表达式中匹配 \s 的字符。默认为 false,除非通过编译器选项改写了它

encapsulation:供模板和 CSS 样式使用的样式封装策略。取值有如下几种:ViewEncapsulation.Native(使用 Shadow DOM。它只在原生支持 Shadow DOM的平台上才能工作)、ViewEncapsulation.Emulated(使用垫片(shimmed)CSS来模拟原生行为)、ViewEncapsulation.None(使用全局CSS,不做任何封装。如果没有提供,该值就会从CompilerOptions中获取它)。默认的编译器选项是ViewEncapsulation.Emulated。如果该策略设置为ViewEncapsulation.Emulated,并且该组件没有指定styles或styleUrls,就会自动切换到ViewEncapsulation.None。

1.3 @Pipe

       @Pipe修饰的类是管道类。在Angular应用中,管道的作用就是传输,可以把一个值通过某种变换成一个新的值。(其实就是对数据做进一步的处理)

1.3.1 @Pipe元数据

选项 类型 说明
name string 管道对应的名字,在模板文件里面绑定管道的时候使用
pure? boolean 如果为true,则管道是pure的,这意味着只有在其输入参数发生更改时才会调用transform()方法。管道默认是pure的。如果管道具有内部状态(即,结果取决于其参数以外的状态),则将“pure”设置为false。在这种情况下,即使参数未更改,也会在每个更改检测周期调用管道。

1.3.2 Pipe使用

       我们自定义一个文件大小转换的管道.把相应的文件大小转换成对应的单位对应的大小.自定义一个FileSizePipe管道.代码如下.

import {Pipe, PipeTransform} from '@angular/core';

/**
 * 文件大小转换
 */
@Pipe({
  name: 'fileSize'
})
export class FileSizePipe implements PipeTransform {

  private static TB_UNIT = 'TB';
  private static GB_UNIT = 'GB';
  private static MB_UNIT = 'MB';
  private static KB_UNIT = 'KB';
  private static B_UNIT = 'B';

  // TB
  private static fileSizeTb(fileSize: number) {
    return fileSize / (1024 * 1024 * 1024 * 1024);
  }

  // GB
  private static fileSizeGb(fileSize: number) {
    return fileSize / (1024 * 1024 * 1024);
  }

  // MB
  private static fileSizeMb(fileSize: number) {
    return fileSize / (1024 * 1024);
  }

  // KB
  private static fileSizeKb(fileSize: number) {
    return fileSize / 1024;
  }

  /**
   * {{ value | fileSize }}
   * {{ value | fileSize:fractionDigits }}
   * {{ value | fileSize:fractionDigits:extension }}
   */
  transform(value: number, fractionDigits?: number, extension?: 'B' | 'KB' | 'MB' | 'GB' | 'TB'): string {
    if (value == null) {
      return null;
    }
    const realFractionDigits = fractionDigits == null ? 2 : fractionDigits;
    if (extension == null) {
      // TB
      if (value >= (1024 * 1024 * 1024 * 1024)) {
        return FileSizePipe.fileSizeTb(value).toFixed(realFractionDigits) + FileSizePipe.TB_UNIT;
      } else if (value >= (1024 * 1024 * 1024)) {
        return FileSizePipe.fileSizeGb(value).toFixed(realFractionDigits) + FileSizePipe.GB_UNIT;
      } else if (value >= (1024 * 1024)) {
        return FileSizePipe.fileSizeMb(value).toFixed(realFractionDigits) + FileSizePipe.MB_UNIT;
      } else if (value >= 1024) {
        return FileSizePipe.fileSizeKb(value).toFixed(realFractionDigits) + FileSizePipe.KB_UNIT;
      } else {
        return value.toFixed(realFractionDigits) + FileSizePipe.B_UNIT;
      }
    } else {
      switch (extension) {
        case "TB":
          return FileSizePipe.fileSizeTb(value).toFixed(realFractionDigits) + FileSizePipe.TB_UNIT;
        case "GB":
          return FileSizePipe.fileSizeGb(value).toFixed(realFractionDigits) + FileSizePipe.GB_UNIT;
        case "MB":
          return FileSizePipe.fileSizeMb(value).toFixed(realFractionDigits) + FileSizePipe.MB_UNIT;
        case "KB":
          return FileSizePipe.fileSizeKb(value).toFixed(realFractionDigits) + FileSizePipe.KB_UNIT;
        case "B":
          return value.toFixed(realFractionDigits) + FileSizePipe.B_UNIT;
      }
    }
    return null;
  }

}


使用

<p>
  {{1024 | fileSize}}
</p>

<p>
  {{1024 * 10 | fileSize}}
</p>

<!-- 保留三位小数 -->
<p>
  {{1024 * 1024 * 3 | fileSize:3}}
</p>

<!-- 保留三位小数,并且转换成MB -->
<p>
  {{1024 * 1024 * 2 | fileSize:3:'MB'}}
</p>

1.4 @Injectable

       @Injectable修饰的类,表示当前类是一个服务类。该类需要通过注入器根据服务提供者去创建服务对象。然后给需要使用的地方使用。这里就涉及到Angular应用里面的依赖注入问题了。有兴趣的可以参考。

选项 类型 说明
providedIn Type | ‘root’ | null 指明当前服务注入的地方

当然了根据服务提供者的不同,还有其他不同的参数。具体可以参考咱们的上一篇文章   Angular依赖注入

2 属性装饰器

       属性装饰器:把装饰器添加在属性上,是属性具有额外的一些功能。Angular系统里面属性装饰器有很多,如下:

装饰器 解释 备注
@Input 属性绑定(父组件向子组件传递数据)
@Output 事件绑定(子组件想父组件传递数据的同时触发事件)
@HostBinding 为宿主元素添加属性值
@HostListener 为宿主元素添加事件
@ContentChild 用于选择当前组件引用的内容(从ng-content中获取元素) 在父组件的 ngAfterContentInit 生命周期钩子中才能成功获取
@ContentChildren 同上(不过是尽可能多的匹配,有多少匹配多少) 在父组件的 ngAfterContentInit 生命周期钩子中才能成功获取
@ViewChild 从模板视图中获取匹配的元素(匹配到满足条件的第一个) 在父组件的 ngAfterViewInit 生命周期钩子中才能成功获取
@ViewChildren 同上(不过@ViewChildren是尽可能多的匹配,有多少匹配多少) 在父组件的 ngAfterViewInit 生命周期钩子中才能成功获取

@Input

       @Input: 负责把父组件的数据传递到子组件。然后在子组件里面做相应的处理。这个就没什么讲的了,使用起来也简单.

       Input装饰器支持一个可选的参数,用来指定组件绑定属性的名称。如果没有指定,则默认使用@Input对应装饰的属性名。不推荐为起别名,推荐直接使用默认的名字。

       使用@Input的时候我们也可以通过setter,以拦截父组件中值的变化,在子组件中采取相应的动作。

@Output

       子组件暴露一个EventEmitter属性,当事件发生时,子组件利用该属性emits(向上弹射)事件。父组件绑定到这个事件属性,并在事件发生时作出回应。子组件的EventEmitter属性是一个输出属性,通常带有@Output 装饰器。

@ViewChild、@ViewChildren

        @ViewChild、@ViewChildren:从模板视图中获取匹配的元素.

import {Component, ElementRef, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {ViewChildChildComponent} from './view-child-child.component';

@Component({
  selector: 'app-decorator-view-child',
  template: `
    <h3>@ViewChild,@ViewChildren(从模板视图中获取匹配的元素)</h3>
    <app-view-child-child #childA></app-view-child-child>
    <app-view-child-child #childB></app-view-child-child>
    <button (click)="clickMe()" >点我</button>

  `
})
export class DecoratorViewChildComponent {

  /**
   * @ViewChild 的使用
   */
    // 使用模板变量名
  @ViewChild('childA')
  child10;
  // 使用类型查询
  @ViewChild(ViewChildChildComponent)
  child11;
  // 使用模板变量名及设置查询条件
  @ViewChild('childB', {read: ElementRef})
  child20;
  @ViewChild('childB', {read: ViewContainerRef})
  child21;

  /**
   * @ViewChildren 的使用
   */
  @ViewChildren(ViewChildChildComponent)
  children;

  clickMe() {
    this.child10.callFunction('child10');
    this.child11.name = '我是child2';

    this.child20.nativeElement.lastElementChild.firstElementChild.value = '我是child3~';
    this.child21._data.componentView.component.callFunction('child21');

    this.children._results[0].callFunction('children');
  }

}

@ContentChild、@ContentChildren

       @ContentChild、@ContentChildren:用于选择当前组件引用的内容(从ng-content中获取元素) .一般在自定义组件的时候使用.

比如如下的代码,我们自定义一个组件ContentParentComponent.在这个组件里面通过@ContentChild获取到ng-conent里面的内容

import {AfterContentInit, Component, ContentChild} from '@angular/core';
import {ContentChildComponent} from './content-child.component';

@Component({
  selector: 'app-content-parent',
  template: `
    <p>Parent Component</p>
    <!-- ng-content 让使用该组件的人可以自定义里面的内容 -->
    <ng-content></ng-content>
  `
})
export class ContentParentComponent implements AfterContentInit {

  // 通过类型获取 -- ngAfterContentInit (事先我们明确了ng-content里面会放置ContentChildComponent组件)
  @ContentChild(ContentChildComponent)
  contentChild: ContentChildComponent;

  constructor() {
  }

  ngAfterContentInit() {
    // 对应contentChild做相应的操作处理
    this.contentChild.initValue = '@ContentChild';
  }

}

@Hostbinding

       @Hostbinding:为宿主元素添加属性值.

@HostListener

       @HostListener:为宿主元素添加事件.

下面我们通过一个简单的实例来说明@Hostbinding,@HostListener的使用.咱们自定义一个指令RainbowDirective.这样所有使用该指令的地方就是宿主元素了.这样可以对添加了该指令的元素做相应的改变.

import {Directive, HostBinding, HostListener} from '@angular/core';

/**
 * 主要是说明@HostBinding、@HostListener使用
 */
@Directive({
  selector: '[appRainbow]',
  exportAs: 'appRainbow'
})
export class RainbowDirective {

  possibleColors = [
    'darksalmon', 'hotpink', 'lightskyblue', 'goldenrod', 'peachpuff',
    'mediumspringgreen', 'cornflowerblue', 'blanchedalmond', 'lightslategrey'
  ];

  // 为宿主元素添加属性值
  @HostBinding('style.color') color: string;
  @HostBinding('style.borderColor') borderColor: string;

  // 为宿主元素添加事件
  @HostListener('keydown') onKeydown() {
    // 随机去一个颜色
    const colorPick = Math.floor(Math.random() * this.possibleColors.length);
    this.color = this.borderColor = this.possibleColors[colorPick];
  }

}
<input appRainbow>

3 参数装饰器

       将装饰器添加在参数上面,一般都是构造函数的参数上。获取注入器里面提供的服务对象。在咱们Angular框架里面注入器一般分为两种:组件注入器、模块注入器。组件注入器里面注入的服务对象一般通过@Component装饰器里面的provider元数据提供。模块注入器里面注入的服务对象一般通过@NgModule装饰器里面的provider元数据提供。

参数装饰器 解释 备注
@Inject 获取注入器里面注入的token对应的服务实例对象
@Optional 和@Inject类似,唯一的区别就是如果没有找到依赖关系,注入器将提供null
@Self 获取当前组件注入器里面提供的服务实例对象 只能是当前组件注入器提供的对象,模块注入器里面的都不行
@SkipSelf 从祖先组件注入器或者模块注入器里面获取提供的对象
@Host 获取宿主元素注入器里面注入的对象

@Inject

       @Inject 用于获取注入器里面注入的token对应的服务实例对象。@Inject也是咱们用的最多的一个获取依赖对象的装饰器。

@Optional

       @Optional和@Inject类似,唯一的区别就是当注入器里面没有找到token对应的对象的时候返回null。所以在使用@Optional的时候一定要做null值的处理。

@Self

       从当前组件注入器里面查找依赖对象。再强调一遍是当前组件注入器里面查找,找到了就找到了,没找到就没找到。

@Self查找范围:当前组件注入器。

import {Component, Inject, Injector, OnInit, Self} from '@angular/core';
import {SelfComponentService} from './self-component.service';
import {TOKEN_SKIP_CLASS_PROVIDER} from "../parameter-decorator-constant";
import {SelfTokenComponentService} from "./self-token-component.service";

@Component({
  selector: 'app-self-decorator',
  template: `
    <h3>@Self -- 获取当前组件(或者指令)注入器里面注入的对象(NgModule里面注入的都不行)</h3>
  `,
  providers: [
    SelfComponentService,
    {provide: TOKEN_SKIP_CLASS_PROVIDER, useClass: SelfTokenComponentService}
  ]
})
export class SelfDecoratorComponent implements OnInit {

  /**
   * @Self()只能获取当前组件注入器中注入的服务,NgModule 注入器里面注入的都不行
   */
  constructor(private injector: Injector,
              @Self() private componentService: SelfComponentService,
              @Self() @Inject(TOKEN_SKIP_CLASS_PROVIDER) private tokenComponentService: SelfTokenComponentService) {

    // // injector.get(SelfModuleService, null, InjectFlags.Self)这种写法好像有点问题,讲道理是获取不到服务的
    // const service: SelfModuleService = injector.get(SelfModuleService, null, InjectFlags.Self);

  }

  ngOnInit() {
  }

}

       @Self也可以和@Optional一起使用.这样在没有找到的情况下赋null值.如下所示

@Optional() @Self() private componentService: SelfComponentService

@SkipSelf

       从当前元素对应注入器的祖先注入器中查找。

@SkipSelf查找范围:祖先组件注入器以及模块注入器。(排除自身组件注入器)

       @SkipSelf也可以和@Optional一起使用

@Host

       获取宿主元素注入器里面注入的对象。

父子组件关系不属于宿主关系。所以@Host在父子关系使用不了。

       这里为了给大家讲明白宿主关系,我们列出@Host的两种场景。

  • 在指令里面使用@Host(指令宿主关系)

       指令里面使用@Host获取宿主元素注入器里面提供的对象。

import {Directive, Host, Inject} from '@angular/core';
import {HostComponentService} from './host-component.service';
import {TOKEN_HOST_CLASS_PROVIDER} from '../parameter-decorator-constant';
import {HostTokenComponentService} from './host-token-component.service';

@Directive({
  selector: '[appHostDecorator]'
})
export class HostDecoratorDirective {

  /**
   * @Host() 获取宿主元素里面提供的服务(宿主元素注入器提供的服务)
   * @param componentService
   * @param tokenService
   */
  constructor(@Host() private componentService: HostComponentService,
              @Host() @Inject(TOKEN_HOST_CLASS_PROVIDER) private tokenService: HostTokenComponentService) {
  }

}
  • 在ng-content使用@Host(ng-conent宿主关系)

自定义一个组件,注意使用了

import {Component} from '@angular/core';
import {HostComponentService} from './host-component.service';
import {TOKEN_HOST_CLASS_PROVIDER} from '../parameter-decorator-constant';
import {HostTokenComponentService} from './host-token-component.service';

@Component({
  selector: 'app-host-decorator',
  template: `
    <h3>@Inject -- 获取注入器里面指定token对应的服务实例对象</h3>
    <ng-content></ng-content>
  `,
  providers: [
    HostComponentService,
    {provide: TOKEN_HOST_CLASS_PROVIDER, useClass: HostTokenComponentService}
  ]
})
export class HostDecoratorComponent {

  constructor() {
  }

}

自定义一个子组件,我们会把该组件放在对应的内容里面

import {Component, Host, Inject} from '@angular/core';
import {HostComponentService} from './host-component.service';
import {TOKEN_HOST_CLASS_PROVIDER} from '../parameter-decorator-constant';
import {HostTokenComponentService} from './host-token-component.service';

@Component({
  selector: 'app-host-decorator-child',
  template: `
    <p>ng-content对应的内容</p>
  `
})
export class HostDecoratorChildComponent {

  constructor(@Host() private componentService: HostComponentService,
              @Host() @Inject(TOKEN_HOST_CLASS_PROVIDER) private tokenService: HostTokenComponentService) {
  }

}
<app-host-decorator>
  <app-host-decorator-child></app-host-decorator-child>
</app-host-decorator>

       @Host也可以和@Optional一起使用.


       关于Angular装饰器咱们就扯这么一些.主要是也为了让大家在使用这些装饰器的时候心里有个底.不同的场景用不同的装饰器.如果大家在使用过程中有什么疑问,可以留言.能力范围内尽量会解答的. 最好给出文章中的一些验证代码地址 https://github.com/tuacy/angular-decorator

猜你喜欢

转载自blog.csdn.net/wuyuxing24/article/details/90729934