Skip to content

TypeScript interfaces for Dependency Injection #135

@pavelsavara

Description

@pavelsavara

In strongly typed languages such as Java or C# we are used to program to an interface.
It allows loosely coupled components and also it allows easier unit testing with mock implementations of the interface (contract).

In TypeScript the interfaces have some interesting features.

  • structural typing which is "duck typing" inference of interface, even if the class of the object it not marked with it.
  • ambient declarations which allow you to describe 3rd party API with an interface, after the fact of the creation.
  • therefore the TS class could explicitly or implicitly implement multiple interfaces. Therefore interface could not be substituted by abstract class. Specially with prototype inheritance of underlying JavaScript.
  • interfaces in typescript are compile time only construct, useful for compiler to warn you that the (argument) doesn't meet the (API) expectation.

The fact that interfaces do not exist in form of IIFE/class as product of TypeScript compilation is the root of problem, how to use it as the DI marker/handle.

Possible solutions:

  1. generate dummy class for each interface, but do not try to cast to it (whatever it means in JavaScript). Benefit is that IIFE could contain RTTI, downside TypeScript compiler would have to implement this. Would the RTTI of classes which explicitly implemented the interface know about it ? Would there be class.GetInterfaces():type[] ?
  2. capture just the interface name as string and use it as DI handle.
  3. allow for manual annotation of dependencies

More about DI + interfaces vs autowiring with dependencies by class

  • DI is implementation of IoC pattern. The goal is to externalize the knowledge about composition and implementation of the contract.
  • Autowiring of injection of specific class is just special case of IoC.
  • The general case of DI with interfaces needs to be configured explicitly in order to choose the correct implementation of the interface.

Therefore I think we need a way how to register which implementation of interface would be injected.

In many DI frameworks people use fluent API of the DI container to configure it. Sample below is Microsoft Unity which uses RTTI of C# generics for the capture.

  • just class registration container.RegisterType<InvoicesService>();
  • class as implementation of interface container.RegisterType<IInvoicesService, InvoicesService>();
  • sometimes the interface type is not granular enough and we need name as well container.RegisterType<IInvoicesService, InvoicesService>("Czech");
  • sometimes the implementation is statefull and we need lifetime container.RegisterType<IInvoicesService, InvoicesService>(new ContainerControlledLifetimeManager());
  • sometimes the singletons need scope, for example per request. There are 2 approaches child container or scoped resolve operation.

More about intended usage:
In strongly typed languages we impose type validating compiler on our-self because the codebase is too big to remember it all.
In this case we are interested to continue usage of TypeScript, which bring this quality to JavaScript.
So if this feature is not easily doable as extension of TS compiler, we would need to find easy way how to achieve same result with manual annotation.

Hope this helps, sorry if I'm not up-to-speed with all details or visions of Angular 2.0 or di component.
-- Pavel

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions