Angular 4 依賴注入教程之六 Injectable裝飾器

目錄

閱讀須知

本系列教程的開發環境及開發語言:shell

基礎知識

裝飾器是什麼

  • 它是一個表達式

  • 該表達式被執行後,返回一個函數

  • 函數的入參分別爲 targe、name 和 descriptor

  • 執行該函數後,可能返回 descriptor 對象,用於配置 target 對象 

裝飾器的分類

  • 類裝飾器 (Class decorators)

  • 屬性裝飾器 (Property decorators)

  • 方法裝飾器 (Method decorators)

  • 參數裝飾器 (Parameter decorators)

TypeScript 類裝飾器

類裝飾器聲明:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) =>    
  TFunction | void

類裝飾器顧名思義,就是用來裝飾類的。它接收一個參數:

  • target: TFunction - 被裝飾的類

看完第一眼後,是否是感受都很差了。沒事,咱們立刻來個例子:

function Greeter(target: Function): void {
  target.prototype.greet = function (): void {
    console.log('Hello!');
  }
}

@Greeter
class Greeting {
  constructor() { // 內部實現 }
}

let myGreeting = new Greeting();
myGreeting.greet(); // console output: 'Hello!';

上面的例子中,咱們定義了 Greeter 類裝飾器,同時咱們使用了 @Greeter 語法,來使用裝飾器。

Injectable 類裝飾器使用

import { Injectable } from '@angular/core';

@Injectable()
class HeroService {}

Injectable 裝飾器

在介紹 Injectable 裝飾器前,咱們先來回顧一下 HeroComponent 組件:

@Component({
  selector: 'app-hero',
  template: `
    <ul>
      <li *ngFor="let hero of heros">
        ID: {{hero.id}} - Name: {{hero.name}}
      </li>
    </ul>
  `
})
export class HeroComponent implements OnInit {
  heros: Array<{ id: number; name: string }>;

  constructor(private heroService: HeroService,
    private loggerService: LoggerService) { }

  ngOnInit() {
    this.loggerService.log('Fetching heros...');
    this.heros = this.heroService.getHeros();
  }
}

HeroComponent 組件的 ngOnInit 生命週期鉤子中,咱們在獲取英雄信息前輸出相應的調試信息。其實爲了不在每一個應用的組件中都添加 log 語句,咱們能夠把 log 語句放在 getHeros() 方法內。

更新前 HeroService 服務

export class HeroService {
    heros: Array<{ id: number; name: string }> = [
        { id: 11, name: 'Mr. Nice' },
        { id: 12, name: 'Narco' },
        { id: 13, name: 'Bombasto' },
        { id: 14, name: 'Celeritas' },
        { id: 15, name: 'Magneta' },
        { id: 16, name: 'RubberMan' },
        { id: 17, name: 'Dynama' },
        { id: 18, name: 'Dr IQ' },
        { id: 19, name: 'Magma' },
        { id: 20, name: 'Tornado' }
    ];

    getHeros() {
        return this.heros;
    }
}

更新後 HeroService 服務

import { LoggerService } from './logger.service';

export class HeroService {
    constructor(private loggerService: LoggerService) { }

    heros: Array<{ id: number; name: string }> = [
        { id: 11, name: 'Mr. Nice' },
        { id: 12, name: 'Narco' },
        { id: 13, name: 'Bombasto' },
        { id: 14, name: 'Celeritas' },
        { id: 15, name: 'Magneta' }
    ];

    getHeros() {
        this.loggerService.log('Fetching heros...');
        return this.heros;
    }
}

當以上代碼運行後會拋出如下異常信息:

Uncaught Error: Can't resolve all parameters for HeroService: (?).

上面異常信息說明沒法解析 HeroService 的全部參數,而 HeroService 服務的構造函數以下:

export class HeroService {
   constructor(private loggerService: LoggerService) { }
}

該構造函數的輸入參數是 loggerService 且它的類型是 LoggerService 。在繼續深刻研究以前,咱們來看一下 HeroService 最終生成的 ES5 代碼:

var HeroService = (function() {
   function HeroService(loggerService) {
     this.loggerService = loggerService;
     this.heros = [{...}, ...];
   }
   HeroService.prototype.getHeros = function() {
     this.loggerService.log('Fetching heros...');
     return this.heros;
   };
   return HeroService;
}());

咱們發現生成的 ES5 代碼中,HeroService 構造函數中是沒有包含任何類型信息的,所以 Angular Injector (注入器) 就沒法正常工做了。那麼要怎麼保存 HeroService 類構造函數中參數的類型信息呢?相信你已經想到了答案 — 固然是使用 Injectable 裝飾器咯。接下來咱們更新一下 HeroService

import { Injectable } from '@angular/core';
import { LoggerService } from './logger.service';

@Injectable()
export class HeroService {
  // ...
}

更新完上面的代碼,成功保存後,在 http://localhost:4200/ 頁面,你將看到熟悉的 "身影":

ID: 11 - Name: Mr. Nice
ID: 12 - Name: Narco
ID: 13 - Name: Bombasto
ID: 14 - Name: Celeritas
ID: 15 - Name: Magneta

如今咱們再來看一下 HeroService 類生成的 ES5 代碼:

var HeroService = (function() {
     function HeroService(loggerService) {
       this.loggerService = loggerService;
       this.heros = [{...}, ...];
     }
     HeroService.prototype.getHeros = function() {
       this.loggerService.log('Fetching heros...');
       return this.heros;
     };
     return HeroService;
}());
HeroService = __decorate([__webpack_require__.i(
  __WEBPACK_IMPORTED_MODULE_0__angular_core__["c"/* Injectable */
])(), __metadata("design:paramtypes", ...)], HeroService);

__decorate 函數

var __decorate = (this && this.__decorate) || function(decorators, target, key, desc) {...};

__metadata 函數

var __metadata = (this && this.__metadata) || function(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
    return Reflect.metadata(k, v);
};

咱們發現相比未使用 Injectable 裝飾器,HeroService 服務生成的 ES5 代碼多出了 HeroService = __decorate(...) 這些代碼。簡單起見,我稍微介紹一下,經過 Injectable 裝飾器,在編譯時會把 HeroService 服務構造函數中參數的類型信息,經過 Reflect API 保存在 window['__core-js_shared__'] 對象的內部屬性中。當 Injector 建立 HeroService 對象時,會經過 Reflect API 去讀取以前已保存的構造函數中參數的類型信息,進而正確的完成實例化操做。
有興趣的讀者,能夠查看 Angular 4.x 修仙之路Decorator(裝飾器) 章節的相關文章。

我有話說

@Injectable() 是必須的麼?

若是所建立的服務不依賴於其餘對象,是能夠不用使用 Injectable 類裝飾器。但當該服務須要在構造函數中注入依賴對象,就須要使用 Injectable 裝飾器。不過比較推薦的作法無論是否有依賴對象,在建立服務時都使用 Injectable 類裝飾器。

相關文章
相關標籤/搜索