TypeScript interfaces for Dependency Injection · Issue #135 · angular/angular · GitHub
Skip to content

TypeScript interfaces for Dependency Injection #135

Closed

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

Activity

  1. mhevery commented on Nov 4, 2014

    @mhevery
    Contributor

    @vsavkin

    Our current thinking is to go with

    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[] ?

    The syntax for this should already be supported with

    var injector = new Injector([
            Car,
            bind(Engine).toClass(CyclicEngine)
          ]);
    

    Is there an actual Action Item here? I think we already support this so I don't think there is anything to do.

  2. modified the milestone: on Nov 4, 2014
  3. pavelsavara commented on Nov 5, 2014

    @pavelsavara
    Author

    Is there unit test where I could have look at it ?

  4. vsavkin commented on Nov 5, 2014

    @vsavkin
    Contributor

    @pavelsavara

  5. mhevery commented on Nov 6, 2014

    @mhevery
    Contributor

    Closing since I believe this is covered.

  6. pavelsavara commented on May 6, 2015

    @pavelsavara
    Author

    This doesn't work very well in typescript 1.5beta, see microsoft/TypeScript#3060

  7. mhevery commented on May 8, 2015

    @mhevery
    Contributor

    The issue is that Types don't have a reference at runtime. But I believe MS will solve it. Nothing for Angular to do here.

  8. angular-automatic-lock-bot commented on Sep 6, 2019

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  9. locked and limited conversation to collaborators on Sep 6, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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