# Hooks

# Introduction

Ts.ED emits different events during its initialization phase (lifecycle). These lifecycle hooks provide visibility into these key life moments and the ability to act when they occur.

This schema resume the order of hooks regard to the providers:

Here is the related code described by the previous schema:

async function bootstrap() {
  try {
    const platform = await PlatformExpress.bootstrap(Server);
    await platform.listen();

    process.on("SIGINT", () => {
      platform.stop();
    });
  } catch (error) {
    $log.error({event: "SERVER_BOOTSTRAP_ERROR", message: error.message, stack: error.stack});
  }
}

bootstrap();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

All providers are called by the emitted event and any provider can also emit his own event.

By convention

  • A hook is always prefixed by $,
  • A hook is emitted from a module,
  • A module subscribe to a hook.

# Subscribe to a hook

# Server

You can subscribe to a hook in your Server:

import {BeforeInit, Configuration} from "@tsed/common";

@Configuration({})
class Server implements BeforeInit {
  async $beforeInit(): Promise<any> {}
}
1
2
3
4
5
6

# Module / Service

You can subscribe to a hook in your Module or Service :

import {Module, OnInit} from "@tsed/common";

@Module()
export class MyModule implements OnInit {
  async $onInit(): Promise<any> {}
}
1
2
3
4
5
6

Note

Database connection can be performed with Asynchronous Provider. See custom providers

# Custom provider v6.110.0+

Since v6.110.0, it's also possible to subscribe to a hook in a custom provider:

import {Configuration, registerProvider} from "@tsed/di";
import {DatabaseConnection} from "connection-lib";

export const CONNECTION = Symbol.for("CONNECTION");

registerProvider<DatabaseConnection>({
  provide: CONNECTION,
  deps: [Configuration],
  useFactory(configuration: Configuration) {
    const options = configuration.get<any>("myOptions");

    return new DatabaseConnection(options);
  },
  hooks: {
    $onDestroy(connection) {
      // called when provider instance is destroyed
      return connection.close();
    }
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

It's now easy to close database connection through the hooks property!

# Emit event

Emit event let the developers subscribe and implement his tasks.

import {Inject, Module, InjectorService} from "@tsed/di";

export interface OnEvent {
  $myEvent(value: string): Promise<void>;
}

@Module()
export class ModuleEmitter {
  @Inject()
  protected injector: InjectorService;

  async initSomething() {
    // do something before

    await this.injector.emit("$myEvent"); // emit accept extra parameters forwarded to subscribers

    // do something after
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

A subscriber:

import {Module} from "@tsed/di";
import {OnEvent} from "module-emitter";

@Module()
export class ModuleSubscriber extends OnEvent {
  $alterEvent() {
    // do something
  }
}
1
2
3
4
5
6
7
8
9

# Alterable value event

This feature let you emit an event with a value. All providers who subscribe to it can modify the value passed as a parameter and return a new value which will be passed to the next provider.

// module-emitter
import {Inject, Module, InjectorService} from "@tsed/di";

export interface AlterEvent {
  $alterEvent(value: string): Promise<string>;
}

@Module()
export class ModuleEmitter {
  @Inject()
  protected injector: InjectorService;

  async initSomething() {
    // do something before

    const value = this.injector.alterAsync("$alterEvent", "hello"); // alterAsync and alter accept extra parameters forwarded to subscribers

    console.log(value); // "hello-world"
    // do something after
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

A subscriber:

import {Module} from "@tsed/di";
import {AlterEvent} from "module-emitter";

@Module()
export class ModuleSubscriber extends AlterEvent {
  $alterEvent(value: any) {
    return value + " world";
  }
}
1
2
3
4
5
6
7
8
9

Last Updated: 9/27/2022, 3:18:00 PM

Other topics