2

I have an abstract class called AbstractTableViewComponent<T>, which is designed to handle the rendering of API service responses as a table. The constructor for AbstractTableViewComponent uses dependency injection to receive an API service object for fetching the data that will be displayed in the table.

@Component({...})
export abstract class AbstractTableViewComponent<T extends ApiEntity> implements OnInit
{
    ...
    constructor(
        protected service: AbstractService<T>
    )

For each API resource, I have a separate table class extending from AbstractTableComponent like so:

@Component({...})
export class UserListComponent extends AbstractTableViewComponent<User>
{
    ...
    constructor(
        protected service: UserService
    )

@Component({...})
export class ProjectListComponent extends AbstractTableViewComponent<Project>
{
    ...
    constructor(
        protected service: ProjectService
    )

Each sub-class defines the column layout, and any special behaviours unique to its API resource. So far, that has worked quite well for me. Typescript's generic classes seem to be encouraging me to use inheritance in this way.

However, we are now discovering that we would like our tables to do more, like spawn modal windows, handle routing, etc. So, for each new behaviour we are going to need a new dependency, and now our base class constructor looks like this:

constructor(
    protected service: AbstractService<T>,
    protected modalService: SimpleModalService,
    protected router: Router,
    protected fooService: FooService
)

Naturally, each time we add a behaviour to the base class, we have to update the constructor for every sub class also. This seems slightly disingenious when the sub classes are not interested in the Router or the FooService object because the logic for the behaviours requiring those dependencies is encased entirely in the base class.

This is further compounded when we want to add a behaviour that only half our sub classes need. Rather than bake that into the base class, inheritance design patterns would have you create a further abstract classes to encapsulate those behaviours:

@Component({...})
export abstract class AbstractLovelyTableViewComponent<T extends ApiEntity> extends AbstractTableViewComponent<T>

@Component({...})
export abstract class AbstractSmellyTableViewComponent<T extends ApiEntity> extends AbstractTableViewComponent<T>

It seems inevitable that I will later run into the requirement that a table can be both Lovely and Smelly, and I'll have a problem. Or more likely, a lot of constructors with very long, repetitive parameter lists.

The solution, it seems, would be to use Composition over Inheritance, a design pattern that promises to solve problems like these, but none of the examples I have seen address the issue of dependency injection, instead demonstrating simple behaviours that only operate on the object's internal state. I'm familiar with the concepts of mixins and traits, but fail to understand how I can apply those principles in this case. Perhaps I'm just misunderstanding what CoI sets out to achieve.

Surely I'm not doing something particularly bizarre here? Is there a better approach I should be taking with my component class designs?

revlob
  • 21
  • 1
  • 1
    This is maybe not the best approach. However, maybe a service locator can help you with you DI issue.. something like this: https://stackoverflow.com/questions/42461852/inject-a-service-manually/42462579#42462579 – MikeOne Jan 27 '21 at 10:42

0 Answers0