Skip to content
Andreas Ernst edited this page Mar 3, 2026 · 10 revisions

Core

The core library provides basic mechanisms that are used by the other packages:

  • dependency injection
  • aop
  • configuration values
  • tracing

Dependency Injection

Dependency is a pattern especially used in large scale applications, in order to increase flexibility, testability and last but mot least the quality and maintainability, by letting a container assemble the different parts of an application - instead or having to write hardwired dependencies - and also taking care of the lifecycle. In my mind a must for every non-trivial application, especially since the coding and runtime overhead is minimal.

Registration

In contrast to frameworks that rely on manual registration, this approach relies completely on annotated classes. Let's look at the different possibilities:

Class

Every class annotated with @injectable is eligible for injection.

Example:

@injectable()
class Bar {
  const Bar();
}

Possible arguments of the annotation are:

  • scope? string defines the lifecycle of the instance. Supported scopes are "singleton", "request", "environment". The default is singleton
  • 'eager?: boolean' if false - or missing - , the instance will be created on demand, if true directly during the initialization of the containers.
  • 'module?: string` determines the mapping to modules.

We will come back to the scopes later.

The constructor can define parameters, as long as they are injectable as well.

@injectable()
class Foo {
  const Foo(private bar: Bar);
}

In this case, the container will construct the objects in the correct order, and call the constructors with the appropriate arguments. The container will of course detect cycles, in which case an exception is thrown.

Factory Methods

Every injectable class can define additional methods, that act as factories for additional instances, by annotating methods with @create().

@injectable()
class Factory {
  @create()
  createBar() : Bar {
    return new Bar();
  }
}

The methods can contain any number of injectable arguments. The @create() accept the same arguments as @injectable.

Lifecycle Methods

It is possible to annotate methods with specific annotations to mark them as callbacks which will be called during construction of the instance. The annotations relate to the lifecycle phases which are:

  • @inject: methods called after constructor instantiation, that provide additional attributes for construction
  • @onInit: called after the inject phase, assuming that the object is now completely initialized
  • @onRunning: called after all objects have been constructed
  • @onDestroy: called after the container has been destroyed

In case of parent classes, all methods except @onDestroy are called from parent to child classes!

All methods can declare any number of injectable parameters.

Example:

@injectable()
class Foo{
  const Foo();

  @onInit()
  onInit(environment: Environment) {
    ...
  }

  @onRunning()
  void start(manager: ConfigurationManger) {
    ...
  }

  @onDestroy()
  void stop() {
    ...
  }
}

Parameters

As already mentioned, a couple of methods - including the constructor - can declare any number parameters that are injectable. These are:

  • injectable classes
  • the type Environment
  • annotated parameters that can be resolved.

Currently the annotation @value(key: string, defaultValue?: any) is possible to resolve configuration values. Check the corresponding chapter.

Module

Classes annotated with @module determine which classes will be handled by a container.

Example:

@module() // implicitely {name: ""}
class ApplicationModule extends Module {

   ...
}

A module is a regular injectable class, so it can add additional factory methods as well.

The logic how classes relate to modules are quite different to a Spring-like logic, since Typescript doesn't remember file or directory locations, so a different approach is taken by matching module names and @injectable module references:

Example:

@module({name: "test"})
class TestModule extends Module {}

@injectable({scope: "test"})
class Test {}

TestModule in tghis case will only register Test.

As a default the module name and the @injectable module property is "".

Environment

Clone this wiki locally