Angular裝飾器介紹

? ? ? ?裝飾器的作用就是在添加裝飾器的地方在不改動原有代碼的情況下增加額外的功能。Angular框架中裝飾器是一個函數(shù)。他將元數(shù)據(jù)添加到類、類成員(屬性、方法)和函數(shù)參數(shù)上。讓它們在不需要做任何代碼變動的前提下增加額外功能。

1 類裝飾器

? ? ? ?類裝飾器負責把元數(shù)據(jù)附加到類上,以了解類的設(shè)計意圖以及這個類是如何工作的。Angular框架里面類裝飾器有多種.如下所示:

裝飾器 解釋 備注
@NgModule 模塊裝飾器(幫把相關(guān)的一些代碼邏輯組織在一起) NgModule 可以將其組件和一組相關(guān)代碼(如服務(wù))關(guān)聯(lián)起來,形成功能單元。每個Angular應(yīng)用都有一個根模塊,通常命名為AppModule
@Component 組件裝飾器 組件可以認為是屏幕上的一個視圖. 組件定義視圖。每個Angular應(yīng)用都至少有一個組件,也就是根組件
@Injectable 依賴裝飾器(把一個類定義為服務(wù)) 組件使用服務(wù)。對于與特定視圖無關(guān)并希望跨組件共享的數(shù)據(jù)或邏輯,可以創(chuàng)建服務(wù)類。
@Pipe 管道裝飾器 管道的作用就是傳輸。并且不同的管道具有不同的作用。(其實就是處理數(shù)據(jù))
@Directive 指令裝飾器 用來控制組件的某些行為

1.1 @NgModule

? ? ? ?@NgModule用來描述Angular應(yīng)用里面的模塊(NgModule)的。也就是說NgModule是一個帶有@NgModule裝飾器的類。@NgModule的參數(shù)是一個元數(shù)據(jù)對象,用于描述如何編譯組件的模板,以及如何在運行時創(chuàng)建注入器。

和其他語言一樣,模塊指的就是把某些功能整合到一起形成一個模塊.模塊話編程.

? ? ? ?Angular應(yīng)用是模塊化的,擁有自己的模塊化系統(tǒng).一個模塊就是一個NgModule。每個NgModule就是一個容器,用于存放一些內(nèi)聚的代碼塊,這些代碼塊專注于某個應(yīng)用領(lǐng)域、或者某個工作流或一組緊密相關(guān)的功能。它可以包含一些組件、服務(wù)提供商或其它代碼文件,其作用域由包含它們的NgModule定義。它還可以導入一些由其它模塊中導出的功能,并導出一些指定的功能供其它NgModule使用。

? ? ? ?每個Angular應(yīng)用至少有一個模塊,也就是根模塊,習慣上命名為AppModule,位于一個名叫app.module.ts的文件中。引導這個根模塊就可以啟動你的應(yīng)用。

1.1.1 @NgModule元數(shù)據(jù)解釋

選項 類型 說明
providers? Provider[] 列出當前模塊需要的一些共用的服務(wù),這樣我們就可以在這個模塊的各個組件中通過依賴注入使用這些服務(wù)了
declarations? Array<Type<any> 聲明屬于這個模塊的指令,管道等等。模塊內(nèi)部Components/Directives/Pipes的列表
imports? Array<Type<any> | ModuleWithProviders | any[]> 當前模塊需要依賴的一些其他的模塊,這樣做的目的就是使我們這個模塊,可以直接使用別的模塊exports提供的一些指令,組件等等
exports? Array<Type<any> | any[]> 當前模塊需要導出的一些組件,指令,模塊等,這樣如果別的模塊導入了我們這個模塊,那么別的模塊就可以直接使用我們在這里導出的組件,指令模塊等
entryComponents? Array<Type<any> | any[]> 一個組件的集合,它應(yīng)該和當前組件一起編譯。對于這里列出的每個組件,Angular 都會創(chuàng)建一個 ComponentFactory 并保存進 ComponentFactoryResolver 中(動態(tài)組件)
bootstrap? Array<Type<any> | any[]> 應(yīng)用的主視圖,稱為根組件。它是應(yīng)用中所有其它視圖的宿主。只有根模塊才應(yīng)該設(shè)置這個 bootstrap 屬性
schemas? Array<SchemaMetadata | any[]> 不屬于Angular的組件或者指令的元素或者屬性都需要在這里進行聲明,允許設(shè)置的值有: NO_ERRORS_SCHEMA 和 CUSTOM_ELEMENTS_SCHEMA。NO_ERRORS_SCHEMA: 當前模板中存在未知選擇器時它不會顯示錯誤;CUSTOM_ELEMENTS_SCHEMA: 當前模板中可以使用任何類型的自定義元素,這有助于在應(yīng)用程序中合并除Angular組件之外的Web組件
id? string 它可以是一個名字或者一個路徑,用來在調(diào)用getModuleFactory的時候區(qū)別模塊,如果這個屬性是undefined那么這個模塊將不會被注冊,如果有設(shè)置id,可以通過getModuleFactory()來獲取到當前模塊
jit? true 如果是true,則當前模塊使用JIT編譯,否則使用AOT編譯。JIT: 即Just-in-time,動態(tài)(即時)編譯,邊運行邊編譯。AOT: Ahead Of Time,指運行前編譯。

1.1.2 NgModule作用:

  • NgModule 最主要的作用是幫助開發(fā)者組織業(yè)務(wù)代碼,把關(guān)系比較緊密的一些功能(組件)代碼組合在一起。

  • NgModule 用來控制組件、指令、管道等是否可以使用,處于同一個 NgModule 里面的組件默認互相可見,而對于外部的組件來說,只能看到 NgModule 導出(exports )的內(nèi)容,也就是說,如果你定義的 NgModule不exports任何內(nèi)容,那么外部使用者即使 import了你這個模塊,也沒法使用里面定義的任何內(nèi)容。

  • NgModule 是打包時候用到的最小單位,打包的時候會檢查所有 @NgModule 和路由配置,Angular底層是使用webpack打包。因為Angular已經(jīng)幫我們配置好了webpack,所以開發(fā)者輕松很多,否則就需要自己配置環(huán)境。

1.2 @Directive

? ? ? ?@Directive修飾的類是指令類。在Angular應(yīng)用中指令一般用來控制組件(DOM)的某些行為。Angular框架也默認給我們提供很多指令,有結(jié)構(gòu)型指令,屬性型指令。

  • 結(jié)構(gòu)型指令: 通過添加和移除DOM元素改變DOM布局的指令. 如NgFor和NgIf.
  • 屬性型指令: 改變元素、組件或其它指令的外觀和行為的指令。 如NgStyle 和NgSwitch.

1.1.1 @Directive元數(shù)據(jù)解釋

選項 類型 說明
selector? string css選擇器名,用于在模板中標記出該指令(組件),并觸發(fā)其實例化
inputs? string[] 該指令(組件)的輸入?yún)?shù),和@Input裝飾器的作用是相同的
outputs? string[] 該指令(組件)可供事件綁定的輸出屬性
host? {[key: string]: string;} 使用一組鍵-值對,把類的屬性映射到宿主元素的綁定(Property、Attribute 和事件)
providers? Provider[] 服務(wù)提供商的集合
exportAs? string 一個或多個名字,可以用來在模板中把該指令賦值給一個變量。當有多個名字時,請使用逗號分隔它們
queries? {[key:string]:any} 配置將要注入到該指令中的一些查詢。內(nèi)容查詢會在調(diào)用 ngAfterContentInit 回調(diào)之前設(shè)置好。 試圖查詢會在調(diào)用 ngAfterViewInit 回調(diào)之前設(shè)置好。

inputs和outputs: 當前指令(組件)的輸入和輸出。但是最新的Angular版本已經(jīng)不推薦使用這兩個屬性了,推薦使用@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綁定。如果綁定的值發(fā)生了變化,Angular 就會更新該指令的宿主元素。當 key 是宿主元素的 Property 時,這個 Property 值就會傳播到指定的 DOM 屬性。當 key 是 DOM 中的靜態(tài) Attribute 時,這個 Attribute 值就會傳播到宿主元素上指定的 Property 去。對于事件處理:它的 key 就是該指令想要監(jiān)聽的 DOM 事件。 要想監(jiān)聽全局事件,請把要監(jiān)聽的目標添加到事件名的前面。 這個目標可以是 window、document 或 body。它的value就是當該事件發(fā)生時要執(zhí)行的語句。如果該語句返回 false,那么就會調(diào)用這個DOM事件的preventDefault函數(shù)。這個語句中可以引用局部變量 $event 來獲取事件數(shù)據(jù)。比如如下的代碼實例。

    @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>
       <!-- 動態(tài)組件存放的容器 -->
       <ng-template ad-host #test='adHost'></ng-template>
       <!-- 動態(tài)組件存放的容器 -->
       <ng-template ad-host #test1='adHost'></ng-template>
     </div>
   
  
    3. 最后我們需要在對應(yīng)的ts里面獲取到該指令對象

     // @ViewChild(AdHostDirective) adHost: AdHostDirective; 單個的情況,可以使用,多個的情況比較麻煩
     @ViewChild('test') adHost: AdHostDirective;
     @ViewChild('test1') adHost1: AdHostDirective;
   
   
    通過exportAs 如果同意個模板里面有同一個指令多個的話,我們可以很簡單的獲取到對應(yīng)的指令。如果不用這種方式很麻煩的

queries:配置將要注入到該指令(組件)中的一些查詢,內(nèi)容查詢會在調(diào)用 ngAfterContentInit 回調(diào)之前設(shè)置好。 試圖查詢會在調(diào)用 ngAfterViewInit 回調(diào)之前設(shè)置好。說白了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這是一個組件。

? ? ? ?組件控制屏幕上被稱為視圖的一小片區(qū)域。在組件類總定義組件的應(yīng)用邏輯。在Angular應(yīng)用中組件(Component)是屬于模塊(NgMoude)的。組件是比模塊更小的一個單元。

1.2.1 @Component元數(shù)據(jù)解釋

? ? ? ?因為Component是繼承Directive的,Component也是指令的一種.所以指令能做到的事情Component也能做到.上面講的@Directive元數(shù)據(jù)都適用于@Component。

除了@Directive的元數(shù)據(jù)之外,@Component元數(shù)據(jù)元數(shù)據(jù)還有如下屬性。

選項 類型 說明
changeDetection? ChangeDetectionStrategy 當組件實例化之后,Angular 就會創(chuàng)建一個變更檢測器,它負責傳播組件各個綁定值的變化。 該策略是下列值之一:ChangeDetectionStrategy#OnPush(0) 把策略設(shè)置為 CheckOnce(按需);ChangeDetectionStrategy#Default(1) 把策略設(shè)置為 CheckAlways
viewProviders? Provider[] 定義一組可注入對象,它們在視圖的各個子節(jié)點中可用
moduleId? string 包含該組件的那個模塊的 ID。該組件必須能解析模板和樣式表中使用的相對 URL。 SystemJS 在每個模塊中都導出了 __moduleName 變量。在 CommonJS 中,它可以設(shè)置為module.id
templateUrl? string 組件模板文件的 URL。如果提供了它,就不要再用 template 來提供內(nèi)聯(lián)模板了
template? string 組件的內(nèi)聯(lián)模板。如果提供了它,就不要再用 templateUrl 提供模板了
styleUrls? string[] 一個或多個 URL,指向包含本組件 CSS 樣式表的文件
styles? string[] 本組件用到的一個或多個內(nèi)聯(lián) CSS 樣式
animations? any[] 一個或多個動畫 trigger() 調(diào)用,包含一些 state() 和 transition() 定義
encapsulation? ViewEncapsulation 供模板和 CSS 樣式使用的樣式封裝策略
interpolation? [string, string] 改寫默認的插值表達式起止分界符({{ 和 }})
entryComponents? Array<Type<any> | any[]> 一個組件的集合,它應(yīng)該和當前組件一起編譯。對于這里列出的每個組件,Angular 都會創(chuàng)建一個 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。如果該策略設(shè)置為ViewEncapsulation.Emulated,并且該組件沒有指定styles或styleUrls,就會自動切換到ViewEncapsulation.None。

1.3 @Pipe

? ? ? ?@Pipe修飾的類是管道類。在Angular應(yīng)用中,管道的作用就是傳輸,可以把一個值通過某種變換成一個新的值。(其實就是對數(shù)據(jù)做進一步的處理)

1.3.1 @Pipe元數(shù)據(jù)

選項 類型 說明
name string 管道對應(yīng)的名字,在模板文件里面綁定管道的時候使用
pure? boolean 如果為true,則管道是pure的,這意味著只有在其輸入?yún)?shù)發(fā)生更改時才會調(diào)用transform()方法。管道默認是pure的。如果管道具有內(nèi)部狀態(tài)(即,結(jié)果取決于其參數(shù)以外的狀態(tài)),則將“pure”設(shè)置為false。在這種情況下,即使參數(shù)未更改,也會在每個更改檢測周期調(diào)用管道。

1.3.2 Pipe使用

? ? ? ?我們自定義一個文件大小轉(zhuǎn)換的管道.把相應(yīng)的文件大小轉(zhuǎn)換成對應(yīng)的單位對應(yīng)的大小.自定義一個FileSizePipe管道.代碼如下.

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

/**
 * 文件大小轉(zhuǎn)換
 */
@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>

<!-- 保留三位小數(shù) -->
<p>
  {{1024 * 1024 * 3 | fileSize:3}}
</p>

<!-- 保留三位小數(shù),并且轉(zhuǎn)換成MB -->
<p>
  {{1024 * 1024 * 2 | fileSize:3:'MB'}}
</p>

1.4 @Injectable

? ? ? ?@Injectable修飾的類,表示當前類是一個服務(wù)類。該類需要通過注入器根據(jù)服務(wù)提供者去創(chuàng)建服務(wù)對象。然后給需要使用的地方使用。這里就涉及到Angular應(yīng)用里面的依賴注入問題了。有興趣的可以參考。

選項 類型 說明
providedIn Type<any> | 'root' | null 指明當前服務(wù)注入的地方

當然了根據(jù)服務(wù)提供者的不同,還有其他不同的參數(shù)。具體可以參考咱們的上一篇文章   Angular依賴注入

2 屬性裝飾器

? ? ? ?屬性裝飾器:把裝飾器添加在屬性上,是屬性具有額外的一些功能。Angular系統(tǒng)里面屬性裝飾器有很多,如下:

裝飾器 解釋 備注
@Input 屬性綁定(父組件向子組件傳遞數(shù)據(jù))
@Output 事件綁定(子組件想父組件傳遞數(shù)據(jù)的同時觸發(fā)事件)
@HostBinding 為宿主元素添加屬性值
@HostListener 為宿主元素添加事件
@ContentChild 用于選擇當前組件引用的內(nèi)容(從ng-content中獲取元素) 在父組件的 ngAfterContentInit 生命周期鉤子中才能成功獲取
@ContentChildren 同上(不過是盡可能多的匹配,有多少匹配多少) 在父組件的 ngAfterContentInit 生命周期鉤子中才能成功獲取
@ViewChild 從模板視圖中獲取匹配的元素(匹配到滿足條件的第一個) 在父組件的 ngAfterViewInit 生命周期鉤子中才能成功獲取
@ViewChildren 同上(不過@ViewChildren是盡可能多的匹配,有多少匹配多少) 在父組件的 ngAfterViewInit 生命周期鉤子中才能成功獲取

@Input

? ? ? ?@Input: 負責把父組件的數(shù)據(jù)傳遞到子組件。然后在子組件里面做相應(yīng)的處理。這個就沒什么講的了,使用起來也簡單.

? ? ? ?Input裝飾器支持一個可選的參數(shù),用來指定組件綁定屬性的名稱。如果沒有指定,則默認使用@Input對應(yīng)裝飾的屬性名。不推薦為起別名,推薦直接使用默認的名字。

? ? ? ?使用@Input的時候我們也可以通過setter,以攔截父組件中值的變化,在子組件中采取相應(yīng)的動作。

@Output

? ? ? ?子組件暴露一個EventEmitter屬性,當事件發(fā)生時,子組件利用該屬性emits(向上彈射)事件。父組件綁定到這個事件屬性,并在事件發(fā)生時作出回應(yīng)。子組件的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;
  // 使用模板變量名及設(shè)置查詢條件
  @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:用于選擇當前組件引用的內(nèi)容(從ng-content中獲取元素) .一般在自定義組件的時候使用.

比如如下的代碼,我們自定義一個組件ContentParentComponent.在這個組件里面通過@ContentChild獲取到ng-conent里面的內(nèi)容

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 讓使用該組件的人可以自定義里面的內(nèi)容 -->
    <ng-content></ng-content>
  `
})
export class ContentParentComponent implements AfterContentInit {

  // 通過類型獲取 -- ngAfterContentInit (事先我們明確了ng-content里面會放置ContentChildComponent組件)
  @ContentChild(ContentChildComponent)
  contentChild: ContentChildComponent;

  constructor() {
  }

  ngAfterContentInit() {
    // 對應(yīng)contentChild做相應(yīng)的操作處理
    this.contentChild.initValue = '@ContentChild';
  }

}

@Hostbinding

? ? ? ?@Hostbinding:為宿主元素添加屬性值.

@HostListener

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

下面我們通過一個簡單的實例來說明@Hostbinding,@HostListener的使用.咱們自定義一個指令RainbowDirective.這樣所有使用該指令的地方就是宿主元素了.這樣可以對添加了該指令的元素做相應(yīng)的改變.

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 參數(shù)裝飾器

? ? ? ?將裝飾器添加在參數(shù)上面,一般都是構(gòu)造函數(shù)的參數(shù)上。獲取注入器里面提供的服務(wù)對象。在咱們Angular框架里面注入器一般分為兩種:組件注入器、模塊注入器。組件注入器里面注入的服務(wù)對象一般通過@Component裝飾器里面的provider元數(shù)據(jù)提供。模塊注入器里面注入的服務(wù)對象一般通過@NgModule裝飾器里面的provider元數(shù)據(jù)提供。

參數(shù)裝飾器 解釋 備注
@Inject 獲取注入器里面注入的token對應(yīng)的服務(wù)實例對象
@Optional 和@Inject類似,唯一的區(qū)別就是如果沒有找到依賴關(guān)系,注入器將提供null
@Self 獲取當前組件注入器里面提供的服務(wù)實例對象 只能是當前組件注入器提供的對象,模塊注入器里面的都不行
@SkipSelf 從祖先組件注入器或者模塊注入器里面獲取提供的對象
@Host 獲取宿主元素注入器里面注入的對象

@Inject

? ? ? ?@Inject 用于獲取注入器里面注入的token對應(yīng)的服務(wù)實例對象。@Inject也是咱們用的最多的一個獲取依賴對象的裝飾器。

@Optional

? ? ? ?@Optional和@Inject類似,唯一的區(qū)別就是當注入器里面沒有找到token對應(yīng)的對象的時候返回null。所以在使用@Optional的時候一定要做null值的處理。

@Self

? ? ? ?從當前組件注入器里面查找依賴對象。再強調(diào)一遍是當前組件注入器里面查找,找到了就找到了,沒找到就沒找到。

@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()只能獲取當前組件注入器中注入的服務(wù),NgModule 注入器里面注入的都不行
   */
  constructor(private injector: Injector,
              @Self() private componentService: SelfComponentService,
              @Self() @Inject(TOKEN_SKIP_CLASS_PROVIDER) private tokenComponentService: SelfTokenComponentService) {

    // // injector.get(SelfModuleService, null, InjectFlags.Self)這種寫法好像有點問題,講道理是獲取不到服務(wù)的
    // const service: SelfModuleService = injector.get(SelfModuleService, null, InjectFlags.Self);

  }

  ngOnInit() {
  }

}

? ? ? ?@Self也可以和@Optional一起使用.這樣在沒有找到的情況下賦null值.如下所示

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

@SkipSelf

? ? ? ?從當前元素對應(yīng)注入器的祖先注入器中查找。

@SkipSelf查找范圍:祖先組件注入器以及模塊注入器。(排除自身組件注入器)

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

@Host

? ? ? ?獲取宿主元素注入器里面注入的對象。

父子組件關(guān)系不屬于宿主關(guān)系。所以@Host在父子關(guān)系使用不了。

? ? ? ?這里為了給大家講明白宿主關(guān)系,我們列出@Host的兩種場景。

  • 在指令里面使用@Host(指令宿主關(guān)系)

? ? ? ?指令里面使用@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() 獲取宿主元素里面提供的服務(wù)(宿主元素注入器提供的服務(wù))
   * @param componentService
   * @param tokenService
   */
  constructor(@Host() private componentService: HostComponentService,
              @Host() @Inject(TOKEN_HOST_CLASS_PROVIDER) private tokenService: HostTokenComponentService) {
  }

}
  • 在ng-content使用@Host(ng-conent宿主關(guān)系)

自定義一個組件,注意使用了<ng-content>

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對應(yīng)的服務(wù)實例對象</h3>
    <ng-content></ng-content>
  `,
  providers: [
    HostComponentService,
    {provide: TOKEN_HOST_CLASS_PROVIDER, useClass: HostTokenComponentService}
  ]
})
export class HostDecoratorComponent {

  constructor() {
  }

}

自定義一個子組件,我們會把該組件放在<ng-content>對應(yīng)的內(nèi)容里面

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對應(yīng)的內(nèi)容</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一起使用.


? ? ? ?關(guān)于Angular裝飾器咱們就扯這么一些.主要是也為了讓大家在使用這些裝飾器的時候心里有個底.不同的場景用不同的裝飾器.如果大家在使用過程中有什么疑問,可以留言.能力范圍內(nèi)盡量會解答的. 最好給出文章中的一些驗證代碼地址 https://github.com/tuacy/angular-decorator

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 模板表達式“{{}}”不能引用任何全局命名空間中的成員(如:window、document等等)的原因: 我想原因...
    科研者閱讀 991評論 2 4
  • core package 概要:Core是所有其他包的基礎(chǔ)包.它提供了大部分功能包括metadata,templa...
    LOVE小狼閱讀 2,630評論 0 3
  • 組件基礎(chǔ) 組件用來包裝特定的功能,應(yīng)用程序的有序運行依賴于組件之間的協(xié)同工作。組件是angular應(yīng)用的最小邏輯單...
    oWSQo閱讀 1,394評論 0 0
  • 聲明 本系列文章內(nèi)容梳理自以下來源: Angular 官方中文版教程 官方的教程,其實已經(jīng)很詳細且易懂,這里再次梳...
    請叫我大蘇閱讀 1,099評論 0 6
  • 人生在世,短暫不過百年。多一份滿足,少一點抱怨;多一份真誠,少一些虛偽;多一份快樂,少一些悲苦;多一份明白,少一些...
    廣聰閱讀 224評論 0 0