3

與ES6類我跑進了以下問題結合採用了棱角分明1.6:(!驚喜)AngularJS:繼承的依賴關係需要重複嗎?

我寫了一個服務,一些依賴

class myService { 

    /*@ngInject*/ 
    constructor($q){ 
     this.$q = $q; 
     this.creationDate = new Date(); 
    } 

    doStuff(data){ 
     return this.$q.when(data); 
    } 
} 

angular.module('app').service('myService', myService) 

但是我有一個積累的目標,其中,是有點票友服務所需,所以我延伸它,在這種情況下使用的擴展服務,而不是:

class myFancyService extends myService{ 

    /*@ngInject*/ 
    constructor($q, $http){ 
     super($q); 
     this.$http = $http; 
    } 

    doFancyStuff(data){ 
     console.log(this.creationDate); 
     return this.doStuff(data) 
      .then((stuff) => this.$http.post('api.myapp', stuff)); 
    } 
} 

angular.module('app').service('myService', myFancyService) 

這工作得很好,到目前爲止,但有一個主要的缺點:

通過調用super(dependencies),我的基類的依賴關係不能從@ngInject自動注入。因此,我需要非常清楚,只要我改變myService的依賴關係,myFancyService(以及任何其他潛在的未來子類)的依賴關係也需要改變。

我不能使用組合而不是繼承,因爲myService未註冊爲角度服務,因此無法注入爲依賴關係。

問:

是否有辦法來自動反正注入基類的依賴呢?

如果沒有,是否至少有一種方法讓我的unittests提醒我,我需要更新myFancyService的依賴關係?如果super($q)的參數(或者可能只是參數的個數)等於myService- constructor的(數量)參數,我還找不到一種方法來使用karma/jasmine進行測試。

回答

2

兩件事情要記住:

  1. Inheritance模式有界面的一致性是必不可少的,子類可以重新實現方法或屬性,但他們不能改變如何方法調用(參數等)
  2. 您是仍在註冊BaseServicedependency injection,但您可能不需要這樣做,因爲它看起來像一個抽象類。

這可以解決你的問題(運行腳本來看看發生了什麼) 你基本上需要的static $inject property每個derived class延伸,在各子構造函數中使用解構:

  • 好處:您不需要知道父類的依賴關係。
  • 約束:始終使用第一參數在你的子類(因爲rest operator必須是最後一個)

function logger(LogService) { 
 
    LogService.log('Hello World'); 
 
} 
 

 
class BaseService { 
 
    static get $inject() { 
 
    return ['$q']; 
 
    } 
 

 
    constructor($q) { 
 
    this.$q = $q; 
 
    } 
 
    
 
    log() { 
 
    console.log('BaseService.$q: ', typeof this.$q, this.$q.name); 
 
    } 
 
} 
 

 
class ExtendedService extends BaseService { 
 
    static get $inject() { 
 
    return ['$http'].concat(BaseService.$inject); 
 
    } 
 

 
    constructor($http, ...rest) { 
 
    super(...rest); 
 
    this.$http = $http; 
 
    } 
 
    
 
    log() { 
 
    super.log(); 
 
    console.log('ExtendedService.$http: ', typeof this.$http, this.$http.name); 
 
    } 
 
} 
 

 

 
class LogService extends ExtendedService { 
 
    static get $inject() { 
 
    return ['$log', '$timeout'].concat(ExtendedService.$inject); 
 
    } 
 

 
    constructor($log, $timeout, ...rest) { 
 
    super(...rest); 
 
    this.$log = $log; 
 
    this.$timeout = $timeout; 
 
    } 
 
    
 
    log(what) { 
 
    super.log(); 
 
    this.$timeout(() => { 
 
     this.$log.log('LogService.log', what); 
 
    }, 1000); 
 
    } 
 
} 
 

 
angular 
 
    .module('test', []) 
 
    .service('BaseService', BaseService) 
 
    .service('ExtendedService', ExtendedService) 
 
    .service('LogService', LogService) 
 
    .run(logger) 
 
;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script> 
 

 
<section ng-app="test"></section>


我也曾在巴貝爾開了feature request -plugin-angularjs-annotatehttps://github.com/schmod/babel-plugin-angularjs-annotate/issues/28

+1

因爲['properties initializers'](https:// esdiscuss。),所以我使用了'static getter'。org/topic/es7-property-initializers)不是標準的(如果你使用babel或打字稿,你也可以使用它們)。 – Hitmands

+1

這太好了,非常感謝!我正在更多地閱讀'ng-annotate'和'$ inject'-property,看看我能不能自動注入服務而不是輸入字符串數組(並保持它與參數列表同步) )。但除此之外,這正是我一直在尋找的! –

+0

如果它完成了這項工作,那麼請在調查後接受它。 – Hitmands

0

在上面的代碼super需要明確指定參數。

更failproof方法是做的所有依賴作業當前等級:

constructor($q, $http){ 
    super(); 
    this.$q = $q; 
    this.$http = $http; 
} 

如果這些服務在父類的構造使用這就會產生問題。測試父構造函數的參數並不容易,因爲這涉及到模塊模擬。一個簡單的和相對可靠的方法來測試,這是斷言:

expect(service.$q).toBe($q); 
expect(service.$http).toBe($http); 

這應該在任何角度單元測試來完成,事實上,即使沒有繼承的類。

一個更好的辦法是引入處理DI基類,考慮到所有@ngInject並創造$inject註釋:

class Base { 
    constructor(...deps) { 
    this.constructor.$inject.forEach((dep, i) => { 
     this[dep] = deps[i]; 
    } 
    } 
} 
BaseService.$inject = []; 

class myService extends Base { 
    /*@ngInject*/ 
    constructor($q){ 
     super(...arguments); 
     ... 
    } 
    ... 
} 

在這一點上可以很明顯看出@ngInject並不能真正幫助了和要求與arguments混亂。如果沒有@ngInject,就變成:

class myService extends Base { 
    static get $inject() { 
     return ['$q']; 
    } 

    constructor(...deps){ 
     super(...deps); 
     ... 
    } 
    ... 
} 

如果依賴任務是在孩子的構造做的唯一的東西,一個構造可以有效地省略:

class myService extends Base { 
    static get $inject() { 
     return ['$q']; 
    } 

    ... 
} 

它甚至整潔與類字段和巴貝爾/打字稿(在瀏覽器中沒有原生支持):

class myService extends Base { 
    static $inject = ['$q']; 

    ... 
} 
+0

據我所見,這並不回答我的問題:測試Child-class如'expect(service。$ q).toBe($ q);'不會提醒我更新child-類依賴關係,除非我已經記得更新測試。此外,您的Baseclass示例實現不能被用作服務而不被繼承,因爲它沒有自己的依賴關係。 (它比抽象的基礎級更像一個工作服務,在某些情況下需要增強)。如果我誤解了,請詳細說明如何使用它來讓孩子只管理自己的依賴關係! –

+0

我不確定你的意思。上面代碼的目的是避免像'super($ q)'這樣的事情,因爲它們需要仔細維護。是的,'Base'是抽象類,所有服務類都應該繼承它,它允許管理子類中的所有代碼。我不認爲你的單元測試問題有一個好的解決方案,如果你忘記了測試的東西,它將不會被測試。它看起來更像是TypeScript的用例,它能夠檢測出這種人爲錯誤。 – estus