Dependency Injection Decorators

@inject

Syntax:@inject(bindingSelector: BindingSelector, metadata?: InjectionMetadata).

@inject is a decorator to annotate class properties or constructor argumentsfor automatic injection by LoopBack’s IoC container.

The injected values are applied to a constructed instance, so it can only beused on non-static properties or constructor parameters of a Class.

The @inject decorator allows you to inject dependencies bound to anyimplementation of the Context object, such as an Applicationinstance or a request context instance. You can bind values, class definitions,and provider functions to those contexts and then resolve the values (or theresults of functions that return those values!) in other areas of your code.

src/application.ts

  1. import {Application} from '@loopback/core';
  2. import * as fs from 'fs-extra';
  3. import * as path from 'path';
  4. export class MyApp extends RestApplication {
  5. constructor() {
  6. super();
  7. const app = this;
  8. const widgetConf = JSON.parse(
  9. fs.readFileSync(path.resolve('widget-config.json')).toString(),
  10. );
  11. function logInfo(info: string) {
  12. console.log(info);
  13. }
  14. app.bind('config.widget').to(widgetConf);
  15. app.bind('logger.widget').to(logInfo);
  16. }
  17. }

Now that we’ve bound the ‘config.widget’ key to our configuration object, andthe ‘logger.widget’ key to the function logInfo(), we can inject them in ourWidgetController:

src/controllers/widget.controller.ts

  1. import {inject} from '@loopback/context';
  2. export class WidgetController {
  3. // injection for property
  4. @inject('logger.widget')
  5. private logger: Function;
  6. // injection for constructor parameter
  7. constructor(
  8. @inject('config.widget') protected widget: any, // This will be resolved at runtime!
  9. ) {}
  10. // etc...
  11. }

The @inject decorator now also accepts a binding filter function so that anarray of values can be injected. If the target type is not Array, an errorwill be thrown.

  1. class MyControllerWithValues {
  2. constructor(
  3. @inject(binding => binding.tagNames.includes('foo'))
  4. public values: string[],
  5. ) {}
  6. }

To sort matched bindings found by the binding filter function, @inject honorsbindingComparator in metadata:

  1. class MyControllerWithValues {
  2. constructor(
  3. @inject(binding => binding.tagNames.includes('foo'), {
  4. bindingComparator: (a, b) => {
  5. // Sort by value of `foo` tag
  6. return a.tagMap.foo.localeCompare(b.tagMap.foo);
  7. },
  8. })
  9. public values: string[],
  10. ) {}
  11. }

A few variants of @inject are provided to declare special forms ofdependencies.

@inject.getter

@inject.getter injects a getter function that returns a promise of the boundvalue of the key.

Syntax: @inject.getter(bindingSelector: BindingSelector).

  1. import {inject, Getter} from '@loopback/context';
  2. import {UserProfile} from '@loopback/authentication';
  3. import {get} from '@loopback/rest';
  4. export class HelloController {
  5. constructor(
  6. @inject.getter('authentication.currentUser')
  7. private userGetter: Getter<UserProfile>,
  8. ) {}
  9. @get('/hello')
  10. async greet() {
  11. const user = await this.userGetter();
  12. return `Hello, ${user.name}`;
  13. }
  14. }

@inject.getter also allows the getter function to return an array of valuesfrom bindings that match a filter function.

  1. class MyControllerWithGetter {
  2. @inject.getter(filterByTag('prime'))
  3. getter: Getter<number[]>;
  4. }

@inject.setter

@inject.setter injects a setter function to set the bound value of the key.

Syntax: @inject.setter(bindingKey: BindingAddress, {bindingCreation?: …}).

The setter function injected has the following signature:

  1. export type Setter<T> = (value?: T) => void;

The binding resolution/creation is controlled by bindingCreation option. See@inject.binding for possible settings.

The following example shows the usage of @inject.setter and the injectedsetter function.

  1. export class HelloController {
  2. constructor(
  3. @inject.setter('greeting') private greetingSetter: Setter<string>,
  4. ) {}
  5. @get('/hello')
  6. greet() {
  7. const defaultGreet = 'Greetings!';
  8. this.greetingSetter(defaultGreet); // key 'greeting' is now bound to 'Greetings!'
  9. return defaultGreet;
  10. }
  11. }

Please note the setter function simply binds a value to the underlying bindingusing binding.to(value).

To set other types of value providers such as toDynamicValueor toClass, use@inject.binding instead.

@inject.binding

@inject.binding injects a binding for the given key. It can be used to bindvarious types of value providers to the underlying binding or configure thebinding. This is an advanced form of @inject.setter, which only allows to seta constant value (using Binding.to(value) behind the scene) to the underlyingbinding.

Syntax: @inject.binding(bindingKey: BindingAddress, {bindingCreation?: …}).

  1. export class HelloController {
  2. constructor(
  3. @inject.binding('greeting') private greetingBinding: Binding<string>,
  4. ) {}
  5. @get('/hello')
  6. async greet() {
  7. // Bind `greeting` to a factory function that reads default greeting
  8. // from a file or database
  9. this.greetingBinding.toDynamicValue(() => readDefaultGreeting());
  10. return this.greetingBinding.get<string>(this.greetingBinding.key);
  11. }
  12. }

The @inject.binding takes an optional metadata object which can containbindingCreation to control how underlying binding is resolved or created basedon the following values:

  1. /**
  2. * Policy to control if a binding should be created for the context
  3. */
  4. export enum BindingCreationPolicy {
  5. /**
  6. * Always create a binding with the key for the context
  7. */
  8. ALWAYS_CREATE = 'Always',
  9. /**
  10. * Never create a binding for the context. If the key is not bound in the
  11. * context, throw an error.
  12. */
  13. NEVER_CREATE = 'Never',
  14. /**
  15. * Create a binding if the key is not bound in the context. Otherwise, return
  16. * the existing binding.
  17. */
  18. CREATE_IF_NOT_BOUND = 'IfNotBound',
  19. }

For example:

  1. @inject.setter('binding-key', {bindingCreation: BindingCreationPolicy.NEVER_CREATES})

@inject.tag

@inject.tag injects an array of values by a pattern or regexp to match bindingtags.

Syntax: @inject.tag(tag: BindingTag | RegExp).

  1. class Store {
  2. constructor(@inject.tag('store:location') public locations: string[]) {}
  3. }
  4. const ctx = new Context();
  5. ctx.bind('store').toClass(Store);
  6. ctx
  7. .bind('store.locations.sf')
  8. .to('San Francisco')
  9. .tag('store:location');
  10. ctx
  11. .bind('store.locations.sj')
  12. .to('San Jose')
  13. .tag('store:location');
  14. const store = ctx.getSync<Store>('store');
  15. console.log(store.locations); // ['San Francisco', 'San Jose']

@inject.view

@inject.view injects a ContextView to track a list of bound values matchinga filter function.

  1. import {inject} from '@loopback/context';
  2. import {DataSource} from '@loopback/repository';
  3. export class DataSourceTracker {
  4. constructor(
  5. @inject.view(filterByTag('datasource'))
  6. private dataSources: ContextView<DataSource[]>,
  7. ) {}
  8. async listDataSources(): Promise<DataSource[]> {
  9. // Use the Getter function to resolve data source instances
  10. return this.dataSources.values();
  11. }
  12. }

In the example above, filterByTag is a helper function that creates a filterfunction that matches a given tag. You can define your own filter functions,such as:

  1. export class DataSourceTracker {
  2. constructor(
  3. @inject.view(binding => binding.tagNames.includes('datasource'))
  4. private dataSources: ContextView<DataSource[]>,
  5. ) {}
  6. }

The @inject.view decorator takes a BindingFilter function. It can only beapplied to a property or method parameter of ContextView type.

@inject.context

@inject.context injects the current context.

Syntax: @inject.context().

  1. class MyComponent {
  2. constructor(@inject.context() public ctx: Context) {}
  3. }
  4. const ctx = new Context();
  5. ctx.bind('my-component').toClass(MyComponent);
  6. const component = ctx.getSync<MyComponent>('my-component');
  7. // `component.ctx` should be the same as `ctx`

NOTE: It’s recommended to use @inject with specific keys for dependencyinjection if possible. Use @inject.context only when the code needs to accessthe current context object for advanced use cases.

For more information, see the Dependency Injectionsection under LoopBack Core Concepts.