Skip to content

Lightweight, extendable and easy to use Dependency Injection for TypeScript

License

Notifications You must be signed in to change notification settings

DavidVollmers/typescript-dependency-injection

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@dvolper/tsdi

TypeScript Dependency Injection

Lightweight, extendable and easy to use Dependency Injection for TypeScript.

Usage

Install the package

npm i @dvolper/tsdi

Install reflect-metadata

npm i -D reflect-metadata

Update your tsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "downlevelIteration": true
  }
}

After that there are different ways on how to initialize the Dependency Injection. Notice that you always need an import 'reflect-metadata' in your entry file.

VueJS Initialization

For VueJS Support you will also need to install the VueJS Plugin package:

npm i @dvolper/tsdi-vue

Then update your main.ts

import 'reflect-metadata'
import Vue from 'vue'
import TypeScriptDependencyInjection from '@dvolper/tsdi-vue'
import {Store} from 'vuex'
import store from './store'

Vue.use( TypeScriptDependencyInjection, {
    // Optional
    builder: dc => {
        // Make the Vuex Store injectable
        dc.add( Store, () => store )
    }
} )

Then inside any component you can use the following setup

import Vue from 'vue'
import {Resolve} from '@dvolper/tsdi'
import {Component} from 'vue-property-decorator'
import {FooService} from '@/services/foo-service'

@Component
export default class FooComponent extends Vue {
  
    @Resolve
    private readonly _fooService: FooService
    
    // Optional
    public beforeMount(): void {
        this.$di.add( FooService )
    }
}

Dependencies for Dependencies

Whenever a dependency will be created or resolved using DependencyContainer::serve or @Resolve the dependencies of this dependency will automatically be resolved.

This also counts for constructor arguments of an injectable class:

Service FooService will be used as dependency

import {Injectable} from '@dvolper/tsdi'

// If you do not need @Resolve you should atleast use @Injectable
// Or you can do dc.add(FooService) when initializing your DependencyContainer
// Without adding or marking a class, it cannot be resolved as a dependency...
@Injectable
export class FooService {

    public foo(): void {
        console.log('foo')
    }

}

And then FooService will be required by BarService

import {Resolve} from '@dvolper/tsdi'
import {FooService} from './foo-service'

@Resolve
export class BarService {
    
    public  constructor( private readonly _fooService: FooService ) {

    }

    public bar(): void {
        this._fooService.foo()
        
        console.log('bar')
    }

}

Executing the following...

import 'reflect-metadata'
import {DependencyContainer} from '@dvolper/tsdi'
import {BarService} from './services/bar-service'

// create a new dependency container
const dc = new DependencyContainer

// create an instance of the registered dependency
const barService = dc.serve( BarService )

barService.bar()

...this output will be generated:

foo
bar

Manual Initialization

import 'reflect-metadata'
import {DependencyContainer} from '@dvolper/tsdi'
import {FooService} from './services/foo-service'

// create a new dependency container
const dc = new DependencyContainer

// create an instance of the dependency (does not need to be registered)
const instance = dc.serve( FooService )

Extended Usage

The Dependency Injection is far more powerful than what you have seen before. In the following you will see the core features explained but you can find a complete API documentation here.

Transient, Scoped and Singleton

Each Dependency will be injected in style of one of the following behaviours:

Transient

The default injection behaviour. This means whenever an instance of the dependency is required, a new one will be served.

Scoped

All dependencies marked as @Scoped.

Per default this behaviour is the same as Transient. Only when using DependencyContainer::useScope it will have an effect: Whenever an instance of the dependency is required, a new one per new scope will be served.

Singleton

All dependencies marked as @Singleton.

This means whenever an instance of the dependency is required, the same one will be served.

Injection by Abstraction

When having injectable classes which derive from others or when adding dependencies with an abstraction...

import {DependencyContainer} from '@dvolper/tsdi'
import {AbstractFooService} from './abstractions/foo-service'
import {FooService} from './services/foo-service'

const dc = new DependencyContainer

// Only needed if the base is a real abstraction (e.g. abstract class) and is not marked as dependency (e.g. @Injectable)
dc.add( AbstractFooService, FooService )

...you can resolve dependencies by that abstraction:

// query the DependencyContainer by an abstraction (the <AbstractFooService> type hint is needed or else instance will be of type Object)
const query = dc.abstract<AbstractFooService>( AbstractFooService )

// lazy load an instance which implements the required abstraction
const instance = query.single()

You can read more about Abstract Queries here.

About

Lightweight, extendable and easy to use Dependency Injection for TypeScript

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published