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

时间:2019-06-30 18:46:23

标签: angular typescript

我试图在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();
    }
}

2 个答案:

答案 0 :(得分:0)

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) {}

答案 1 :(得分:0)

我本来想在这里做的事是不可能的。

不过,也没有必要:

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