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