Service registration

Tiny Injector provide similar interface to .Net DI with almost identical behavior. There are several service registration methods that are useful in different scenarios.

  1. Using Injector
  2. Using @Injectable decorator.

Using Lifetime methods#

Single registration#

SyntaxExample
Injector.Add{LIFETIME}({IMPLEMENTATION})Injector.AddSingleton(ConcreteDep)
Injector.Add{LIFETIME}({SERVICE}, {IMPLEMENTATION})Injector.AddSingleton(AbstractDep, ConcreteDep)
Injector.Add{LIFETIME}({SERVICE}, (context) => new {IMPLEMENTATION})Injector.AddSingleton(AbstractDep, (context) => new ConcreteDep())
Injector.Add{LIFETIME}({InjectionToken}, (context) => {IMPLEMENTATION})Injector.AddSingleton(new InjectionToken<{number}>('A Token'), (context) => 10)

Registering a service with only an implementation type is equivalent to registering that service with the same implementation and service type.

Injector.AddSingleton(ServiceType);
Injector.AddSingleton(ServiceType, ServiceType);

Calling Add{Lifetime} more than one time will result in throwing ServiceExistException, if your intentions to register multiple implementations, check out Append{lifetime} instead.

Multiple registrations#

SyntaxExample
Injector.Append{LIFETIME}({IMPLEMENTATION})Injector.AppendSingleton(ConcreteDep)
Injector.Append{LIFETIME}({SERVICE}, {IMPLEMENTATION})Injector.AppendSingleton(AbstractDep, ConcreteDep)
Injector.Append{LIFETIME}({SERVICE}, (context) => new {IMPLEMENTATION})Injector.AppendSingleton(AbstractDep, (context) => new ConcreteDep())
Injector.Append{LIFETIME}({InjectionToken}, (context) => {IMPLEMENTATION})Injector.AppendSingleton(new InjectionToken<{number}>('A Token'), (context) => 10)
Injector.AppendSingleton(ServiceType);
Injector.AppendSingleton(ServiceType, ServiceType);
Injector.AppendSingleton(ServiceType, ImplementationType);
Injector.AppendSingleton(ServiceType, implementationFactory);

Example

abstract class Logger {}

@Injectable()
class InformativeLogger extends Logger {}

@Injectable()
class WarningLogger extends Logger {}

Injector.AppendSingleton(Logger, InformativeLogger);
Injector.AppendSingleton(Logger, WarningLogger);

You can use AddSingleton for the first add, but it is recommended to use AppendSingleton if you want array of implementations.

Multiple implementations of a service cannot be registered using the methods that don't take an explicit service type. These methods can register multiple instances of a service, but they will all have the same implementation type.

Injector.AppendSingleton(ServiceType);
Injector.AppendSingleton(ServiceType);
Injector.AppendSingleton(ServiceType);

const services = Injector.GetServices(ServiceType);
// services[0], services[1], and services[2] will have the same type as ServiceType
// Basically, it is like creating 3 instances of ServiceType

Calling Injector.GetRequiredService(ServiceType) will return the last registered implementation

Safe registration#

SyntaxExample
Injector.TryAdd{LIFETIME}({IMPLEMENTATION})Injector.TryAddSingleton(ConcreteDep)
Injector.TryAdd{LIFETIME}({SERVICE}, {IMPLEMENTATION})Injector.TryAddSingleton(AbstractDep, ConcreteDep)
Injector.TryAdd{LIFETIME}({SERVICE}, (context) => new {IMPLEMENTATION})Injector.TryAddSingleton(AbstractDep, (context) => new ConcreteDep())
Injector.TryAdd{LIFETIME}({InjectionToken}, (context) => {IMPLEMENTATION})Injector.TryAddSingleton(new InjectionToken<{number}>('A Token'), (context) => 10)

Register the service only if there isn't already an implementation registered.

Injector.TryAdd(ServiceType, FirstImplementationType);
Injector.TryAdd(ServiceType, SecondImplementationType);

Only FirstImplementationType will be registered.

Example#

@Injectable()
abstract class Logger {}

@Injectable()
class InformativeLogger extends Logger {}

Injector.AddSingleton(Logger, InformativeLogger);

Using factory as implementation

Injector.AddSingleton(Logger, (context) => {
	// Some interesting logic
	return new InformativeLogger();
});

Make sure that ServiceType is always class syntax otherwise an error will be thrown. @Injectable is must present on the service class otherwise it won't be resolved

Using @Injectable#

The other approche is to use @Injectable decorator as it is the best choice for tree shaking purposes. with @Injectable you don't need to explict service registration using Add{Lifetime} methods.

In some cases you might need to use litetime methods such as, deffered registration or if you want to provide factoryFn as implementation

Example#

Register ServiceType as Singleton,

@Injectable({
	lifetime: ServiceLifetime.Singleton,
})
class ServiceType {}

Remember, registering a service with only an implementation type is equivalent to registering that service with the same implementation and service type. so this would be registered as this behind the scene

@Injectable()
class ServiceType {}

Injector.AddSingleton(ServiceType, ServiceType);

Register InformativeLogger as Singleton against Logger

abstract class Logger {}

@Injectable({
	lifetime: ServiceLifetime.Singleton,
	serviceType: Logger,
})
class InformativeLogger extends Logger {}

This is an alias for

Injector.AddSingleton(Logger, InformativeLogger);

keep in mind that @Injectable is always needed in order to resolve the class constructor parameters type.

Strict Type#

Thanks to TypeScript, the implementation will be constrained to the ServiceType.

e.g

@Injectable()
class Vehicle {
	start() {}
}

@Injectable()
class Weapon {
	shoot() {}
}

Injector.AddSingleton(Vehicle, Weapon);

Injector.AddSingleton(Vehicle, (context) => new Weapon());

the above sample won't work since Weapon is not subtype of Vehicle

@Injectable()
class Vehicle {
	start() {}
}

@Injectable()
class Truck extends Vehicle {
	warmUp() {}
}

Injector.AddSingleton(Vehicle, Truck);

Injector.AddSingleton(Vehicle, (context) => new Truck());

now the implementation either factory or type will work just fine since the Truck is subtype of Vehicle.

Type Equivalence#

Because of JavaScript nature, having different classes with the same properties and methods make them equivalent in regards to type, so the below code will work with no errors, although, this code can be handled at runtime using instanceof keyword

class Test {
	variable = 10;
}

class Service {
	variable = 10;
}

class Implementation extends Test {}

Injector.AddSingleton(Service, Implementation);