【问题标题】:Services depending on each other服务相互依赖
【发布时间】:2016-07-22 14:00:38
【问题描述】:

在我的 Angular 2 应用程序中,我有两个相互依赖的服务(服务 A 从服务 B 调用方法,反之亦然)。

以下是相关代码:

app.component.ts:

import {Component} from 'angular2/core';
import {TempService} from '../services/tmp';
import {Temp2Service} from '../services/tmp2';

@Component({
    selector: 'my-app',
    templateUrl: 'app/app/app.component.html',
    providers: [TempService, Temp2Service]
})
export class AppComponent { (...) }

服务 1:

import {Injectable} from 'angular2/core';
import {Temp2Service} from './tmp2';

@Injectable()
export class TempService {
  constructor (private _sessionService: Temp2Service) {}
}

服务 2:

import {Injectable} from 'angular2/core';
import {TempService} from './tmp';

@Injectable()
export class Temp2Service {
  constructor (private _sessionService: TempService) {}
}

运行应用程序会导致以下错误:

例外:无法解析所有参数 'Temp2Service'(未定义)。确保所有参数都是 用 Inject 装饰或具有有效的类型注释,并且 'Temp2Service' 用 Injectable 装饰

在其中一项服务中注释构造函数时,应用程序运行良好。所以我的猜测是这两个服务的“交叉引用”导致了问题。

您知道这里出了什么问题吗?还是我的方法已经错了?

【问题讨论】:

  • 嗯,Angular 需要一个 TempService 来构造一个 Temp2Service,它需要一个 Temp2Service 来构造一个 TempService。这是一个先有鸡还是先有蛋的问题。创建第三个服务,并委托给第三个服务以打破依赖关系。

标签: angular


【解决方案1】:

这是一个循环依赖,不幸的是它是一个基本的计算机科学问题或信息问题,Angular 无法解决的问题。尝试做这样的事情:

export class ServiceA {
 constructor(private b: ServiceB){
    b.setA(this);
 }
}

export class ServiceB {
 
 private a: ServiceA

 constructor(){
    
 }

 setA(a){
   this.a = a;
 }

}

这可能是最好的方法。

【讨论】:

  • 解决烦人问题的简单快速解决方案。道具。
【解决方案2】:

我在查询我的 API 时遇到了问题。 A 可以有多个 Bs,B 有一个 A 父级。在建立这些模型时,需要忽略其中一种关系以避免循环依赖。然后,当您查询时,您可以简单地将类型转换为any

this.repositoryService.Query<A>(`$filter=Id eq ${B.AId}`).subscribe(As=>this.importantProp = (As[0] as any).C.property;)

那么在你对 A 的定义中:

@JsonProperty('B', [Object], true) Bs = new Array<Object>();
@JsonProperty('C', [Object], true) C = null;

【讨论】:

    【解决方案3】:

    如果您使用的是 Angular 2,并且需要循环依赖以在某些事件上调用彼此的函数,您可以使用 Observables 并在注入其他服务的 Service 中订阅它们。

    例子:

    @Injectable()
    class Service1 {
    
      observeEvents() {
        return Obsevable.create((o) => {
          //store `o` it in any class variable
          //whenever you want to call function of Service2 from this class, do this `o.next('method_name');`
        });
      }
    }
    
    @Injectable()
    class Service2 {
      constructor(private service1: Service1) {
        this.service1.subscribe((method) => {
          this[method]();
        });
      }
    }
    

    【讨论】:

    • 我不会投反对票,因为所有这些都是 hack(感谢 Angular2,因为没有提供适当的装饰器)——但这——这是一个 hack。不过,支持替代解决方案。
    • @Cody 先生,不允许黑客攻击吗?
    • 是的,黑客是允许的——我认为,但我不是这方面的仲裁者。我是说这有点像——并不是说 Angular 提供了很多替代方案。我很欣赏另一个解决方案的提议——我不再指望你而不是反对 Angular。
    • @Cody 看起来很有趣。谢谢
    【解决方案4】:

    我遇到了循环依赖问题,找到了很多答案,我发现解决它的最佳方法如下。

    据我了解,当您尝试将a: A 传递给b: B 到构造函数 时,就会出现问题。所以避免它的方法是创建你的对象,然后只将a设置为b

    至于我 A 和 B 不是那么自言自语,我的例子就是我的案例

    请注意,导入不会影响循环依赖问题。

    rabbit.component.ts

    export class RabbitComponent {
    
        public rabbitsArray: Rabbit[] = []
    
        constructor(){
            let lRabbit: Rabbit = new Rabbit(God.rabbitMotherFromEve())
            lRabbit.setRabbitComponent(this)
            rabbits.push(lRabbit)
        }
    }
    

    兔子.ts

    export class Rabbit {
        public feetsArray: Foot[] // I know its not the best practices but more concise for example
        public rabbitComponent: RabbitComponent
    
        constructor (anyThingYouWantButRabbitComponent: RabbitMother){
        }
    }
    

    【讨论】:

      【解决方案5】:

      这就是所谓的循环依赖。这不是 Angular2 本身的问题。我所知道的任何语言都不允许这样做。

      你需要重构你的代码来移除这个循环依赖。您可能需要将其中一项服务拆分为新服务。

      如果你遵循单一责任原则,你会发现你不会陷入循环依赖陷阱。

      【讨论】:

      • 实际上,我所知道的所有语言都允许循环依赖。问题是构造函数需要依赖项。如果不是,您可以使用let a = new A(); let b = new B(); a.b = b; b.a = a;。你仍然会有一个循环依赖,但一切都会运行良好。我并不是说循环依赖是一件好事。只是说他们是可能的。
      • 在某些情况下无法避免循环依赖。我发现 Julian FARHI 引用前向参考的答案很有用。解决了我的问题。
      • forwardRef 用于同一文件中类的循环依赖。我的回答是关于 DI 依赖关系。 forwardRef 不会受到可注入的循环构造函数依赖的任何影响。从构造函数中删除一个依赖项以打破循环,而是注入 Injector 并使用 setTimeout(() =&gt; { this.someDep = injector.get(SomeDependency); } 将是一种解决方法。
      • “在你遵循单一责任原则时,你会发现你不会陷入循环依赖陷阱。”这似乎是一个错误的陈述。我有一个可以显示为页面的一部分或显示为模式对话框的视图。我的视图显示了相关模型,您可以单击以在模式对话框中查看。在 Angular 看来,这会创建一个循环依赖(模态不会注入到构造函数中)。实际视图的职责在这两种情况下没有什么不同(显示模型),但它创建了一个循环依赖。不过,我很欣赏我的情况与问题不同。
      • 你不需要重构你的代码。只是不要使用基于构造函数的注入。使用显式注入。为什么这是最佳答案?
      【解决方案6】:

      我尝试使用 setTimeout 或使用注入器修复循环依赖警告以及将注入从构造函数移动到另一个函数的所有方法都不适用于 Angular 7。

      这是我的工作解决方案:

      我创建了另一个服务只是为了将服务引用到第一个服务:

      @Injectable()
      export class AnotherService {
      
        private _service: AService;
      
        get service(): AService {
          return this._service;
        }
        set service(service: AService) {
          this._service = service;
        }
      }
      

      然后我可以这样使用它:

      @Injectable()
      export class AService {
      
        constructor(private anotherService: AnotherService) {
          anotherService.service = this;
        }
        ...
      }
      

      这里:

      @Injectable()
      export class BService {
        private aService: AService;
      
        constructor(private injector: Injector) {
          const anotherService = injector.get(AnotherService);
          this.aService = anotherService.service;
        }
        ...
      }
      

      【讨论】:

        【解决方案7】:

        这里的关键不是通过构造函数注入服务,而是使用显式的setter & getter。我会在 Angular 4 中使用以下模式:

        app.component.ts

        import { FooService } from './foo/foo.service';
        import { BarService } from './bar/bar.service';
        
        export class AppComponent {
        
          constructor(public fooService: FooService, public barService: BarService) {
        
            this.fooService.setBarService(barService);
        
          }
        
        }
        

        foo.service.ts

        @Injectable()
        export class FooService {
        
            barService: any;
        
            constructor(){
            }
        
            setBarService(barService: any): void {
                this.barService = barService;
            }
        
            getBarService(): any {
                return this.barService;
            }
        
        }
        

        【讨论】:

        • 基于KISS原则,应该被认为是正确答案
        • @Dimas Crocco 感谢您的评论。我同意。提供理论很好,但归根结底,OP 想要一个具体的例子,我觉得我给了他。
        • 感谢您遵循 Stack Overflow 的精神并实际提供答案。
        【解决方案8】:

        我更新了这个解决方案以使用 Angular > 4. 使用 Injector 类,您可以将服务注入到另一个服务中

        import { Injector } from '@angular/core';
        import { TempService } from './tmp';
        
        
        @Injectable()
        export class Temp2Service {
        
          private tempService: any;
        
          constructor (private injector: Injector) { }
        
          public funcA() {
             this.tempService = this.injector.get(TempService);
             this.tempService.doSomething();
          }
        }
        

        【讨论】:

        • 这在 Angular 4.0 中没有帮助。
        • 这是在玩火 - 避免它:)
        • @AlexanderMills:你为什么说它在玩火?你有什么更好的解决方案?谢谢。
        • 这是一个不合理问题的合理解决方案。
        • 这实际上在以后的版本中仍然有效。这是在玩火吗?是的,因为它围绕 DI 工作,并且会干扰测试。但是,当您有一个不明显的循环引用并且正在花费您的生产时间并且开发人员坐在他们的手上时,这是一种解决方法,可以让您的开发人员再次移动,而一个人深入研究并试图弄清楚它.
        【解决方案9】:

        我们可以通过forwordRef函数来解决这个问题。

        //允许引用尚未定义的引用。

        @Inject(forwardRef(() => MyService)) 私有 httpProxy: MyService

        【讨论】:

          【解决方案10】:

          构造函数注入可防止循环依赖。

          可以通过注入 Injector 并命令式地请求依赖项来分解它:

          private payrollService:PayrollService;
          constructor(/*private payrollService:PayrollService*/ injector:Injector) {
            setTimeout(() => this.payrollService = injector.get(PayrollService));
          }
          

          另见Circular dependency injection angular 2

          【讨论】:

          • 你期待什么?构造函数注入不能处理循环依赖。唯一好的选择是摆脱圈子。
          • this.payrollService = this.injector.get&lt;ICustomInterface&gt;('PayrollService') 我认为应该足够好了。不确定是否需要setTimeout...
          • 是的,setTimeout是必须的,否则你还有循环。
          • 哦,好的。我不必添加setTimeout 来使其工作,因为我在构造函数之外的函数中对其进行了初始化。
          • 即使我将 injector.get 调用放在从未调用过的方法中,我也会收到此错误。 Angular CLI - 不确定这是否有所作为。
          【解决方案11】:

          如果没有单例是可以接受的,您可以尝试在其中一项服务上调用 NEW。喜欢

          this._sessionService = new TempService(this);
          

          这是我采用的方法,因为两个服务都没有使用未定义的成员变量。

          【讨论】:

          • 好。你的意思是,如果你不需要它是@Injectable——如果它是一个非常静态的对象/类。
          • 这是个坏主意,因为它消除了 DI 的全部意义,例如测试能力。
          • 同意@YahyaUddin。违背了可注入服务的全部目的
          【解决方案12】:

          使用接口 - 这是许多语言中的常见模式。

          查看君特斯的回答

          Circular dependency with Angular 2 and SystemJS

          【讨论】:

          • 我投反对票,因为链接到其他答案而不是直接提供答案违反了 Stack Overflow 的精神。
          猜你喜欢
          • 2021-05-12
          • 1970-01-01
          • 1970-01-01
          • 2020-09-17
          • 2021-01-11
          • 1970-01-01
          • 1970-01-01
          • 2011-01-25
          • 2014-01-15
          相关资源
          最近更新 更多