服务相互依赖


73

在我的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 { (...) }

服务一:

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

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

服务二:

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

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

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

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

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

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


5
好吧,角度需要一个TempService来构造一个Temp2Service,它需要一个Temp2Service来构造一个TempService。这是鸡和鸡蛋的问题。创建第三个服务,并委托给该第三个服务以打破依赖关系。
JB Nizet

Answers:


47

这称为循环依赖。Angular2本身不是问题。我所知道的任何语言都不允许使用。

您将需要重构代码以删除此循环依赖项。您可能需要将这些服务之一分解为新服务。

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


92
实际上,我所知道的所有语言都允许循环依赖。问题在于构造函数需要依赖项。如果不是,您可以这样做let a = new A(); let b = new B(); a.b = b; b.a = a;。您仍将具有循环依赖关系,但一切运行正常。我并不是说循环依赖是一件好事。只是说它们是可能的。
JB Nizet

2
在某些情况下,无法避免循环依赖。我发现朱利安·法尔希(Julian FARHI)的参考前向参考答案很有用。解决了我的问题。
陈星

4
forwardRef用于同一文件中类的循环依赖关系。我的答案是关于DI依赖关系。forwardRef不会影响可注入对象的循环构造函数依赖项。从构造函数中删除一个依赖项可以打破循环,而注入Injector和使用setTimeout(() => { this.someDep = injector.get(SomeDependency); }将是一种解决方法。
君特Zöchbauer

5
“遵循单一负责任的原则,您将发现您不会陷入循环依赖陷阱。” 这似乎是一个错误的陈述。我有一个视图,可以显示为页面的一部分,也可以显示为模式对话框。我的视图显示了相关的模型,您可以单击以在模式对话框中查看。在Angular的眼中,这创建了一个循环依赖项(模式未注入到构造函数中)。在两种情况下,实际视图的职责没有不同(显示模型),但是它创建了循环依赖关系。我很欣赏我的情况与问题不同。
约翰(John)

5
您不需要重构代码。只是不要使用基于构造函数的注入。使用显式注入。为什么这是最佳答案?
查尔斯·罗伯逊

32

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

可以通过注入Injector和请求强制性依赖来分解它,例如:

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

另请参见圆弧依赖注入角度2


7
你能指望什么?构造函数注入不适用于循环依赖项。唯一好的选择是摆脱圈子。
君特Zöchbauer

this.payrollService = this.injector.get<ICustomInterface>('PayrollService')我认为应该足够好。不知道是否setTimeout...有必要。
raneshu

是的,setTimeout是必需的,否则您仍然有循环。
君特Zöchbauer

哦好的。我不必添加setTimeout即可使其工作,因为我在构造函数之外的函数中对其进行了初始化。
raneshu

即使将injector.get调用放在从未调用过的方法中,我也会收到此错误。Angular CLI-不确定是否有所不同。
WillyC '17

23

这里的关键不是通过构造函数注入服务,而是使用显式的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;
    }

}

3
基于KISS原则的人,应该被认为是正确的答案
Dimas Crocco

2
@Dimas Crocco感谢您的评论。我同意。提供理论很好,但是归根结底,OP想要一个具体的例子,我觉得我给了他。
查尔斯·罗伯逊

6

我更新了此解决方案以使其与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中无济于事。
Senthe

1
这是在玩火-避免它:)
亚历山大·米尔斯

1
@AlexanderMills:为什么说它在玩火?您有什么更好的解决方案?谢谢。
安德烈·迪亚科内斯库

这是解决不合理问题的合理解决方案。
超级

5

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

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

export class ServiceB {

 private a: ServiceA

 constructor(){

 }

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

}

那可能是最好的方法。


一个简单而快速的解决烦人的问题的方法。道具。
telion

1

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

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

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


1

如果您使用的是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]();
    });
  }
}

我不会拒绝投票,因为所有这些都是骇客(感谢Angular2,因为没有提供适当的装饰器),但是,这是骇客。不过,还可以选择替代解决方案。
科迪

@Cody先生,不允许黑客入侵吗?
OldGaurd17年

是的,允许黑客入侵-我认为,但我不是仲裁者。我是说这只是一个问题-并不是Angular提供了很多替代方案。我感谢提供另一种解决方案-除了针对Angular的积分之外,我对您的积分也没有更多。
科迪

@Cody似乎很有趣。谢谢
OldGaurd01


0

我尝试使用setTimeout修复循环依赖关系警告或使用注入器并将注入从构造函数移至另一个函数的所有方法对角度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;
  }
  ...
}

0

我遇到了循环依赖问题,找到了很多答案,下面是找到解决该问题的最佳方法。

据我了解,当您尝试传递a: Ab: B 构造函数时,就会出现问题。因此避免这种情况的方法是创建您的对象,然后仅将其设置ab

至于我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)
    }
}

兔子

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

    constructor (anyThingYouWantButRabbitComponent: RabbitMother){
    }
}

0

查询API时遇到问题。A可以有很多BB有一个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;

-1

如果不接受单身人士,则可以尝试在其中一项服务上调用NEW。喜欢

this._sessionService = new TempService(this);

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


好。您的意思是,如果您不需要它@Injectable-如果它是一个非常静态的对象/类。
科迪

1
这是一个坏主意,因为它消除了整个DI点,例如测试能力。
Yahya Uddin

同意@YahyaUddin。
破坏
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.