Angular 8:如何为同一组件的多个实例提供不同的服务实例

Posted

技术标签:

【中文标题】Angular 8:如何为同一组件的多个实例提供不同的服务实例【英文标题】:Angular 8: How to provide different service instances to multiple instances of the same component 【发布时间】:2019-11-11 14:42:53 【问题描述】:

我正在尝试在 Angular 8 中创建可重用的基于 d3 的仪表板组件。我想创建可以打包在模块中并重用的组件(如条形图),而无需修改组件代码(甚至不是构造函数参数) )。我还想允许 x 个显示不同数据的同级实例。

我有一个工作组件,我已将它的 api 分解为简单显示配置的输入参数和一个所有组件都需要数据/交互的服务接口。我将该服务接口实现为一个抽象基类,并且该组件将该基类作为构造函数参数(用户/开发人员不能在我的场景中修改构造函数参数或任何组件代码)。

这给我留下了一个问题——如何在不修改组件构造函数参数的情况下为不同的组件实例提供服务基类的不同实现。

我也已经尝试为条形图创建一个抽象基类,然后创建仅通过获取派生 barchartservice 实例不同的派生条形图实例,但问题是模板和样式不是从组件库继承的类。

export class BarChartComponent implements AfterViewInit

    ...

    public constructor(public service: BarChartService) 

    public ngAfterViewInit() 
        ...

        this.service.dataInit$.subscribe(() => 
            let data = this.service.barChartData;
            this.drawChart(data);
        );

        this.service.dataRefresh$.subscribe(() => 
            let data = this.service.barChartData;
            this.drawChart(data);
        );
    

    private drawChart(data): void 
        ...
    


@Injectable()
export abstract class BarChartService 

    abstract barChartData: any;

    abstract dataInit$: Observable<boolean>;
    abstract dataRefresh$: Observable<boolean>;

    abstract barChartSelect(string): void;
    abstract barChartXValue: (d: any) => any;
    abstract barChartYValue: (d: any) => any;

最终我只想要可以显示不同数据的可重用多实例组件。任何见解将不胜感激。

为了清楚起见,这是 barChartService 派生类正在做的事情(审批服务参数包含一个后端访问逻辑和跨仪表板共享的交叉过滤器):

@Injectable()
export class ApprovalsBarChartService implements BarChartService 

    private init = new Subject<boolean>();
    dataInit$ = this.init.asObservable();

    private refresh = new Subject<boolean>();
    dataRefresh$ = this.refresh.asObservable();

    public get barChartData(): any 
        return this.service.approvalsGroupedByApprover.all();
    

    public barChartXValue = function (d)  return d.key; ;
    public barChartYValue = function (d)  return d.value; ;

    public constructor(public service: ApprovalService)    

        this.service.init$.subscribe(() => 
            this.init.next(true);
        );

        this.service.refresh$.subscribe(() => 
          this.refresh.next(true);
        );
     

    public barChartSelect(processor: string): void 
        this.service.approvalsIndexedByApprover.filter(processor);
        this.service.selectedProcessor = processor;
        this.service.selectItems = +this.service.xfilter.groupAll().reduceCount().value();
        this.service.refreshCharts();
    

【问题讨论】:

谁决定在BarChartComponent 上使用哪个BarChartService 实现?开发人员(您或您的 api 的消费者)?用户(最终用户)? BarChartComponent 的每个超类都可以使用BarChartService 的任何超类吗?或特定的“BarChartComponent”使用特定的BarChartService 开发人员在创建仪表板时执行此操作。例如,开发人员可以在仪表板上添加三个条形图标签实例以显示不同的数据。为此,他们创建了 3 个派生的 BarChartService 实例,为每个图表配置一个。我遇到的问题是适当地分配它们。我试图不创建 BarChart 的派生类,但它创建了上面提到的问题,基类模板没有被继承 - 否则会起作用。 【参考方案1】:

BarChartService 真的需要成为服务吗?在我看来,如果您不需要在不同组件之间重用相同的服务实例,那么您就不需要服务。

因此,您可以在组件的构造函数中实例化“服务”,而不是注入它。所以而不是:

public constructor(public service: BarChartService) 

你可以:

public service: BarChartService;
public constructor() 
    this.service = new BarChartService();

请注意,由于该类是抽象类,您需要实例化具体的子类。

现在,如果您真的想将服务中的不同实例注入到不同的组件中(例如,其中一些服务将在某些组件之间共享),那么您可以为每个实例提供不同的注入令牌,并使用在组件的构造函数中注入标记以选择您想要的实例:

声明两个注入令牌:

export const MyToken1 = new InjectionToken<BarChartService>('MyToken1');
export const MyToken2 = new InjectionToken<BarChartService>('MyToken2');

为每个令牌分配不同的服务实例:

@NgModule(...) 
export class ... 
    ...
    providers: [
        provide: MyToken1, useValue: new MyServiceInstance(1, 2, 3),
        provide: MyToken2, useValue: new MyServiceInstance(4, 5, 6),
    ]

然后在组件中,选择你要注入的服务:

public constructor(@Inject(MyToken1) public service: BarChartService) 

【讨论】:

我认为确实如此,服务是引入可变性的地方。定义使用什么图表 x 和 y 值、单击条形时运行什么代码以及数据源是什么的函数都在服务的派生/子类中设置。如果没有变化,我当然会在组件本身中添加服务代码。 用唯一的令牌变量修改构造函数参数让我在我已经在的地方。该组件旨在可重复使用并密封,以防止开发人员修改。在这种情况下,我将创建组件的多个实例来修改构造函数参数。 感谢您在此问题上的时间和帮助 - 您是正确的,不需要该服务。我需要做的是将服务中定义的所有内容作为输入参数传递给组件。我不知道我可以将函数作为输入参数传入以允许自定义行为。【参考方案2】:

我最初在这里尝试做的事情是不可能的。

幸好,这也不是必须的:

解决方案:不需要服务 (BarChartService)。 BarChartService 中定义的所有内容都可以并且应该作为 @Input 参数传入,但 BarChartSelect 除外,它应该是在父级中捕获并调用 ApprovalService 上的方法的 @Output 事件,该方法本质上是一个数据检索服务它包含整个仪表板的交叉过滤器(不是特定于组件)。我不知道我们可以将函数作为输入参数传递给组件来自定义行为。

【讨论】:

以上是关于Angular 8:如何为同一组件的多个实例提供不同的服务实例的主要内容,如果未能解决你的问题,请参考以下文章

如何为组件的每个实例触发 vue 生命周期事件?

如何为同一服务使用@Autowired 注解两个或多个不同的组件类?

Angular 6 - 多个子组件应该是同一个实例

如何使同一个 Angular 2 组件的多个实例在同一个容器中正常工作

具有多个子组件实例的Angular2父组件

如何为 Angular 组件注释 ngdoc?