【问题标题】:Inject Specific Service Instances into Multiple Instances of a Component in One Parent Component将特定服务实例注入到一个父组件中的一个组件的多个实例中
【发布时间】:2017-07-23 21:22:50
【问题描述】:

我现在正在处理一个非常大的项目,我们正在努力使我们的代码尽可能模块化。我们有多个 Angular 应用程序,我们创建了一个单独的通用 UI 组件库和一个 API 工具包,其中包含跨这些应用程序使用的通用服务。

我们在尝试构建需要与服务一起使用的通用组件时遇到了问题。例如,我现在正在开发一个自动完成组件。该组件应该能够从远程源获取数据并根据输入到组件的输入字段中的内容过滤结果。

对于一个实例来说,这种实现很容易。我在自动完成组件的构造函数中注入自动完成服务,然后在父级上提供它。这使我可以在使用服务时灵活地更改服务的实现细节,同时仍然能够创建一个可与定义的接口一起使用的可重用组件。

例如:

我们要定义自动完成组件使用的接口的服务:

@Injectable()
export class AutocompleteService {
  public url: string = 'my-api-default';

  constructor(http: Http) {}

  fetch(): any {
    return this.http.get(this.url);
  }
}

自动完成组件实现:

@Component({
  selector: 'my-autocomplete',
  templateUrl: 'my-autocomplete.component.html'
})
export class MyAutocompleteComponent {
  constructor(private autocompleteService: AutocompleteService) {}

  getData() {
    return this.autocompleteService.fetch();
  }
  ...autocomplete logic...
}

现在我可以定义一个实现自动完成服务的熊服务。我可以将熊服务连接到我的自动完成组件,这样我就可以在我的表单中选择熊的种类。

@Injectable()
export class BearService {
  public url: string = 'bear-url';

  constructor(http: Http){}

  fetch() {
    return this.http.get(this.url);
  }
}

接下来,我定义了使用我的自动完成组件并提供熊服务来获取我的熊种类数据的父级。

@Component({
  selector: 'my-form-component',
  template: `
    <form>
      <my-autocomplete [(ngModel)]="bear"></my-autocomplete>
      <button type="submit">Submit</button>
    </form>
  `,
  providers: [
    {provide: AutocompleteService, useClass: BearService}
  ]
})
export class MyFormComponent {
  ...component logic...
}

到目前为止,一切都很好。

当我需要构建一个使用多个自动完成组件的大型表单时,我的问题就出现了。我的老板告诉我,我需要在此表单上填写三个自动填充字段,一个用于熊类,一个用于甜菜类,一个用于太空堡垒卡拉狄加角色。我的第一个想法是这样做:

我定义了服务实例:

@Injectable()
export class BeetsService {
  public url: string = 'beets-url';

  constructor(http: Http){}

  fetch() {
    return this.http.get(this.url);
  }
}

@Injectable()
export class BattleStarGallacticaService {
  public url: string = 'battlestar-gallactica';
  constructor(http: Http){}

  fetch() {
    return this.http.get(this.url);
  }
}

然后我更新父模板和提供者:

@Component({
  selector: 'my-form-component',
  template: `
    <form>
      <my-autocomplete [(ngModel)]="bear"></my-autocomplete>
      <my-autocomplete [(ngModel)]="beet"></my-autocomplete>
      <my-autocomplete [(ngModel)]="battleStarGallactica"></my-autocomplete>
      <button type="submit">Submit</button>
    </form>
  `,
  providers: [
    {provide: AutocompleteService, useClass: BearService},
    {provide: AutocompleteService, useClass: BeetService},
    {provide: AutocompleteService, useClass: BattlestarGalacticaService},
  ]
})
export class MyFormComponent {
  ...component logic...
}

现在我如何判断哪个自动完成组件使用哪个服务?

我知道我现在将始终使用为 AutocompleteService 提供的最后一个提供程序,因为这就是 Angular DI 框架的工作方式。

我也知道我不能在这方面使用多提供者,因为 Angular 只为 NG_VALIDATORSNG_ASYNC_VALIDATORS 定义了它们。

那么,有人知道如何解决我的问题吗?我不在乎如何问题本身得到解决,但我仍然需要能够:

  1. 定义服务接口
  2. 在可重用组件中使用该服务接口
  3. 为我自己的需要创建一个实现原始接口的新服务实例
  4. 能够使用多个组件来实现相同的服务接口,并在单个父组件中使用不同的服务实现

【问题讨论】:

    标签: angularjs angular service dependency-injection components


    【解决方案1】:

    我会将提供程序移至有选择地应用的指令

    @Directive({
      selector: 'my-autocomplete[bear]',
      providers: [{ provide: AutoCompleteService, useClass: BearService}]
    })
    export class BearDirective {}
    
    @Directive({
      selector: 'my-autocomplete[battlestarGalactica]',
      providers: [{ provide: AutoCompleteService, useClass: BattlestarGalacticaService}]
    })
    export class BattelstarGalacticaDirective{}
    

    然后像这样使用它

    <form>
      <my-autocomplete bear [(ngModel)]="bear"></my-autocomplete>
      <my-autocomplete bear [(ngModel)]="beet"></my-autocomplete>
      <my-autocomplete battlestarGalactica [(ngModel)]="battleStarGallactica"></my-autocomplete>
      <button type="submit">Submit</button>
    </form>
    

    并从MyFormComponent 中删除提供程序

    【讨论】:

    • 感谢您的回答!我要把它付诸实践,它并不像我希望的那样优雅。现在,使用我们库的人已经添加了创建新指令并将其添加到模块以及创建新服务的步骤。我很想看到一种直接在视图上注册提供者的方法 也许将来我们会得到一些很酷的方法来使用多个提供者并直接在看法。我知道我们也有 viewProviders,但这种情况有点不同。
    • 您需要在构造函数中注入子类 (BattlestarGalacticaService),否则 DI 现在可以知道要注入什么。您可以做的是{provide: 'AutocompleteService', useValue: {bear: new BearService(), beet: new BeetService(), battlestarGalactica: new BattlestarGalacticaService()}},然后在组件中添加constructor(@Inject('AutoCompleteService') private ac:any){} @Input() type:string;,当您需要访问您使用的服务时this.ac[type]
    • 这太棒了,完美。非常感谢君特。
    • 如果AutoCompleteService(或其子类)需要注入依赖项,您也可以使用useFactory 而不是useValue
    • 我不知道如何使这种方法在不增加复杂性的情况下适用于工厂。当我使用工厂时,如何配置组件以了解要使用的服务? function providerFactor(http: Http, someOpaqueToken) { if (someOpaqueToken === 'bear') { return new BearService(); } else if (someOpaqueToken === 'beet') { return new BeetService(http);我怎样才能从孩子那里设置那个令牌呢?似乎必须在父级上设置它,或者我必须创建一些应用程序范围的服务才能从子级设置和更新它?
    猜你喜欢
    • 2017-02-13
    • 2018-07-05
    • 2017-12-09
    • 1970-01-01
    • 2018-12-02
    • 1970-01-01
    • 2012-08-28
    • 2020-11-19
    • 2017-06-10
    相关资源
    最近更新 更多