Folio Contents

What is Binding?

Bounden represents items within a Context instance. A binding connects its value to a unique primal as the address to admission the entry in a context.

Attributes of a bounden

A binding typically has the post-obit attributes:

  • central: Each binding has a key to uniquely identify itself within the context
  • scope: The telescopic controls how the binding value is created and cached within the context
  • tags: Tags are names or name/value pairs to describe or annotate a binding
  • value: Each bounden must be configured with a type of value provider so that it tin can be resolved to a constant or calculated value

Binding

How to create a binding?

There are a few ways to create a binding:

  • Utilise Binding constructor:

                                      import                  {                  Context                  ,                  Binding                  }                  from                  '                  @loopback/core                  '                  ;                  const                  context                  =                  new                  Context                  ();                  const                  binding                  =                  new                  Bounden                  (                  '                  my-key                  '                  );                  ctx                  .                  add                  (                  binding                  );                              
  • Use Bounden.demark()

                                      import                  {                  Context                  ,                  Bounden                  }                  from                  '                  @loopback/core                  '                  ;                  const                  context                  =                  new                  Context                  ();                  const                  binding                  =                  Bounden                  .                  bind                  (                  '                  my-key                  '                  );                  ctx                  .                  add                  (                  binding                  );                              
  • Utilise context.bind()

                                      import                  {                  Context                  ,                  Binding                  }                  from                  '                  @loopback/core                  '                  ;                  const                  context                  =                  new                  Context                  ();                  context                  .                  bind                  (                  '                  my-key                  '                  );                              

How to gear up a binding?

The Binding course provides a set of fluent APIs to create and configure a binding.

Supply the value or a way to resolve the value

The value can be supplied in ane the post-obit forms:

A constant

If bounden is always resolved to a fixed value, we can bind information technology to a constant, which can be a string, a function, an object, an assortment, or any other types.

Please note the constant value cannot be a Promise to avoid confusions.

A manufacturing plant function

Sometimes the value needs to be dynamically calculated, such as the current time or a value fetched from a remote service or database.

                          bounden              .              toDynamicValue              (()              =>              '              my-value              '              );              bounden              .              toDynamicValue              (()              =>              new              Engagement              ());              binding              .              toDynamicValue              (()              =>              Hope              .              resolve              (              '              my-value              '              ));                      

The factory role can receive extra information about the context, binding, and resolution options.

                          import              {              ValueFactory              }              from              '              @loopback/core              '              ;              // The manufactory function now have admission extra metadata well-nigh the resolution              const              manufacturing plant              :              ValueFactory              <              string              >              =              resolutionCtx              =>              {              render              `Howdy,                            ${              resolutionCtx              .              context              .              proper name              }              #              ${              resolutionCtx              .              binding              .              central              }                                          ${              resolutionCtx              .              options              .              session              ?.              getBindingPath              ()}              `              ;              };              const              b              =              ctx              .              bind              (              '              msg              '              ).              toDynamicValue              (              factory              );                      

Object destructuring tin can be used to further simplify a value factory function that needs to access context, binding, or options.

                          const              manufacturing plant              :              ValueFactory              <              cord              >              =              ({              context              ,              binding              ,              options              })              =>              {              return              `Hello,                            ${              context              .              name              }              #              ${              binding              .              fundamental              }                                          ${              options              .              session              ?.              getBindingPath              ()}              `              ;              };                      

An advanced form of value factory is a class that has a static value method that allows parameter injection.

                          import              {              inject              }              from              '              @loopback/core              '              ;              class              GreetingProvider              {              static              value              (@              inject              (              '              user              '              )              user              :              string              )              {              return              `Hello,                            ${              user              }              `              ;              }              }              const              b              =              ctx              .              bind              (              '              msg              '              ).              toDynamicValue              (              GreetingProvider              );                      

A class

The bounden tin represent an case of a form, for example, a controller. A class can be used to instantiate an instance equally the resolved value. Dependency injection is often leveraged for its members.

                          class              MyController              {              constructor              (@              inject              (              '              my-options              '              )              individual              options              :              MyOptions              )              {              // ...              }              }              binding              .              toClass              (              MyController              );                      

A provider

A provider is a class with value() method to calculate the value from its instance. The main reason to apply a provider class is to leverage dependency injection for the factory role.

                          form              MyValueProvider              implements              Provider              <              string              >              {              constructor              (@              inject              (              '              my-options              '              )              private              options              :              MyOptions              )              {              // ...              }              value              ()              {              return              this              .              options              .              defaultValue              ;              }              }              binding              .              toProvider              (              MyValueProvider              );                      

The provider form serves as the wrapper to declare dependency injections. If dependency is non needed, toDynamicValue can be used instead.

An injectable class

An injectable class is one of the following types of classes optionally decorated with @injectable.

  • A class
  • A provider form
  • A dynamic value manufacturing plant grade

The toInjectable() method is a shortcut to demark such classes using toClass/toProvider/toDynamicValue respectively by introspecting the form, including the binding metadata added by @injectable.

                          @              injectable              ({              scope              :              BindingScope              .              SINGLETON              })              form              MyController              {              constructor              (@              inject              (              '              my-options              '              )              private              options              :              MyOptions              )              {              // ...              }              }              binding              .              toInjectable              (              MyController              );                      

The code to a higher place is similar as follows:

                          const              binding              =              createBindingFromClass              (              MyController              );                      

An alias

An alias is the key with optional path to resolve the value from another binding. For example, if we want to go options from RestServer for the API explorer, we tin can configure the apiExplorer.options to be resolved from servers.RestServer.options#apiExplorer.

                          ctx              .              bind              (              '              servers.RestServer.options              '              ).              to              ({              apiExplorer              :              {              path              :              '              /explorer              '              }});              ctx              .              bind              (              '              apiExplorer.options              '              )              .              toAlias              (              '              servers.RestServer.options#apiExplorer              '              );              const              apiExplorerOptions              =              await              ctx              .              become              (              '              apiExplorer.options              '              );              // => {path: '/explorer'}                      

Configure the telescopic

A binding provides values for requests such every bit ctx.go(), ctx.getSync(), and dependency injections. The binding scope controls whether a binding returns a new value or share the same value for multiple requests within the same context hierarchy. For case, value1 and value2 in the code beneath can be unlike or the same depending on the scope of Bounden(my-key).

                          const              value1              =              wait              ctx              .              get              (              '              my-key              '              );              const              value2              =              ctx              .              getSync              (              '              my-cardinal              '              );                      

We allow a binding to be resolved inside a context using one of the following scopes:

  • BindingScope.TRANSIENT (default)
  • BindingScope.CONTEXT (deprecated to favor APPLICATION/SERVER/REQUEST)
  • BindingScope.SINGLETON
  • BindingScope.APPLICATION
  • BindingScope.SERVER
  • BindingScope.Request

For a consummate list of descriptions, please see BindingScope.

                          bounden              .              inScope              (              BindingScope              .              SINGLETON              );                      

The binding scope can be accessed via binding.scope.

Cull the right scope

The binding scope should be determined by answers to the following questions:

  1. Do you demand to have a new value from the binding for each request?
  2. Does the resolved value for a binding hold or access states that are asking specific?

Please note that the bounden scope has no effect on bindings created with to(). For instance:

                          ctx              .              demark              (              '              my-name              '              ).              to              (              '              John Smith              '              );                      

The my-proper name bounden will always resolve to 'John Smith'.

The binding scope will impact values provided by toDynamicValue, toClass, and toProvider.

Let'south say we demand to have a binding that gives us the electric current date.

                          ctx              .              demark              (              '              current-date              '              ).              toDynamicValue              (()              =>              new              Date              ());              const              d1              =              ctx              .              getSync              (              '              current-date              '              );              const              d2              =              ctx              .              getSync              (              '              current-date              '              );              // d1 !== d2                      

By default, the binding scope is TRANSIENT. In the code higher up, d1 and d2 are resolved by calling new Date() for each getSync('electric current-appointment'). 2 unlike dates are assigned to d1 and d2 to reflect the respective date for each resolution.

Now yous can guess the code snippet below will produce the aforementioned date for d1 and d2, which is not desirable.

                          ctx              .              bind              (              '              current-date              '              )              .              toDynamicValue              (()              =>              new              Date              ())              .              inScope              (              BindingScope              .              SINGLETON              );              const              d1              =              ctx              .              getSync              <              Date              >              (              '              current-appointment              '              );              const              d2              =              ctx              .              getSync              <              Engagement              >              (              '              electric current-engagement              '              );              // d1 === d2                      

The SINGLETON scope is useful for some use cases, such as:

  1. Share states in a single instance beyond multiple consumers of the bounden

                                      export                  grade                  GlobalCounter                  {                  public                  count                  =                  0                  ;                  }                  ctx                  .                  bind                  (                  '                  global-counter                  '                  )                  .                  toClass                  (                  GlobalCounter                  )                  .                  inScope                  (                  BindingScope                  .                  SINGLETON                  );                  const                  c1                  :                  GlobalCounter                  =                  await                  ctx                  .                  get                  (                  '                  global-counter                  '                  );                  c1                  .                  count                  ++                  ;                  // c1.count is now i                  const                  c2                  :                  GlobalCounter                  =                  await                  ctx                  .                  become                  (                  '                  global-counter                  '                  );                  // c2 is the same instance as c1                  // c2.count is one too                              
  2. Forbid cosmos of multiple instances if one single case tin can be shared equally the consumers do non need to agree or access different states

    For example, the post-obit GreetingController implementation does not access any data beyond the method parameters which are passed in equally arguments. A shared instance of GreetingController can invoke greet with dissimilar arguments, such equally c1.greet('John') and c1.greet('Jane').

                                      // Mark the controller class a candidate for singleton binding                  @                  injectable                  ({                  telescopic                  :                  BindingScope                  .                  SINGLETON                  })                  export                  class                  GreetingController                  {                  greet                  (                  name                  :                  string                  )                  {                  return                  `Hello,                                    ${                  proper name                  }                  `                  ;                  }                  }                              

    GreetingController is a good candidate to utilize SINGLETON so that simply one example is created within the application context and it tin can be shared past all requests. The scope eliminates the overhead to instantiate GreetingController per request.

                                      // createBindingFromClass() respects `@injectable` and sets the binding scope to `SINGLETON'                  const                  binding                  =                  ctx                  .                  add together                  (                  createBindingFromClass                  (                  GreetingController                  ));                  const                  c1                  =                  ctx                  .                  getSync                  (                  bounden                  .                  key                  );                  const                  c2                  =                  ctx                  .                  getSync                  (                  binding                  .                  key                  );                  // c2 is the same case as c1                  c1                  .                  greet                  (                  '                  John                  '                  );                  // invoke c1.greet for 'John' => 'Hello, John'                  c2                  .                  greet                  (                  '                  Jane                  '                  );                  // invoke c2.greet for 'Jane' => 'Hello, Jane'                              

Dominion of pollex: Use TRANSIENT as the rubber default and choose SINGLETON if yous want to share the same instance for all consumers without breaking concurrent requests.

Allow'south look at another use case that we demand to access the information from the current asking, such as http url or logged in user:

                          export              grade              GreetingCurrentUserController              {              @              inject              (              SecurityBindings              .              USER              )              individual              currentUserProfile              :              UserProfile              ;              greet              ()              {              return              `Hello,                            ${              this              .              currentUserProfile              .              name              }              `              ;              }              }                      

Instances of GreetingCurrentUserController depend on currentUserProfile, which is injected as a belongings. Nosotros have to employ TRANSIENT scope so that a new case is created per request to hold the logged in user for each asking.

The constraint of being transient tin can be lifted by using method parameter injection to motility the request-specific injection to parameters per method invocation.

                          export              class              SingletonGreetingCurrentUserController              {              greet              (@              inject              (              SecurityBindings              .              USER              )              currentUserProfile              :              UserProfile              )              {              return              `Howdy,                            ${              this              .              currentUserProfile              .              name              }              `              ;              }              }                      

The new implementation above does not hold asking specific states as properties in its instances anymore and thus it'southward qualified to be in SINGLETON telescopic.

                          ctx              .              bind              (              '              controllers.SingletonGreetingCurrentUserController              '              )              .              toClass              (              SingletonGreetingCurrentUserController              )              .              inScope              (              BindingScope              .              SINGLETON              );                      

A single case of SingletonGreetingCurrentUserController is created within the context that contains the bounden. But the greet method tin still be invoked with different request contexts, each of which has its own logged in user. Method parameter injections are fulfilled with the request context, which tin exist different from the context (such as application) used to instantiate the class every bit a singleton.

There are some limitation of SINGLETON, CONTEXT, and TRANSIENT scopes. Consider the following typical context hierarchy formed by a LoopBack REST application:

                          // We take a context chain: invocationCtx -> requestCtx -> serverCtx -> appCtx              appCtx              .              bind              (              '              services.MyService              '              )              .              toClass              (              MyService              )              .              inScope              (              BindingScope              .              TRANSIENT              );                      

We utilise TRANSIENT scope for controllers/services so that each asking volition become a new instance. Just if a controller/service is resolved within the invocationCtx (by interceptors), a new instance will exist created again.

                          // In a middleware              const              serviceInst1              =              requestCtx              .              get              <              MyService              >              (              '              services.MyService              '              );              // In an interceptor              const              serviceInst2              =              invocationCtx              .              go              <              MyService              >              (              '              services.MyService              '              );              // serviceInst2 is a new case and information technology's NOT the same as serviceInst1                      

It tin can too happen with dependency injections:

                          class              MyMiddlewareProvider              implements              Provider              <              Middleware              >              {              constructor              (@              inject              (              '              services.MyService              '              )              private              myService              )              {}              }              class              MyInterceptor              {              constructor              (@              inject              (              '              services.MyService              '              )              private              myService              )              {}              }              // For the same asking, the middleware and interceptor will receive two unlike              // instances of `MyService`                      

Ideally, we should get the same instance at the subtree of the requestCtx. Even worse, resolving a binding twice in the same reqCtx will get two dissimilar instances too.

Neither SINGLETON or CONTEXT can satisfy this requirement. Typically, controllers/servers are discovered and loaded into the awarding context. Those from components such as RestComponent too contribute bindings to the appCtx instead of serverCtx. With SINGLETON telescopic, nosotros volition get 1 instance at the appCtx level. With CONTEXT telescopic, we will get 1 case per context. A ready of fine-grained scopes has been introduced to permit improve scoping of binding resolutions.

  • BindingScope.Awarding
  • BindingScope.SERVER
  • BindingScope.Asking

The scopes higher up are checked against the context bureaucracy to discover the first matching context for a given telescopic in the chain. Resolved binding values will be cached and shared on the scoped context. This ensures a binding to have the aforementioned value for the scoped context.

                          // We take a context concatenation: invocationCtx -> requestCtx -> serverCtx -> appCtx              appCtx              .              demark              (              '              services.MyService              '              )              .              toClass              (              MyService              )              .              inScope              (              BindingScope              .              Request              );                      

Now the aforementioned instance of MyService will exist resolved in the MyMiddleware and MyInterceptor.

Resolve a binding value by central and scope within a context hierarchy

Binding resolution happens explicitly with ctx.get(), ctx.getSync(), or bounden.getValue(ctx). It may be as well triggered with dependency injections when a class is instantiated or a method is invoked.

Within a context bureaucracy, resolving a binding involves the following context objects, which tin can be the same or different depending on the context chain and binding scopes.

Permit's assume nosotros have a context chain configured as follows:

                          import              {              Context              }              from              '              @loopback/cadre              '              ;              const              appCtx              =              new              Context              (              '              application              '              );              appCtx              .              scope              =              BindingScope              .              Awarding              ;              const              serverCtx              =              new              Context              (              appCtx              ,              '              server              '              );              serverCtx              .              telescopic              =              BindingScope              .              SERVER              ;              const              reqCtx              =              new              Context              (              serverCtx              ,              '              request              '              );              reqCtx              .              telescopic              =              BindingScope              .              Asking              ;                      
  1. The owner context

    The owner context is the context in which a bounden is registered by ctx.demark() or ctx.add() APIs.

    Permit's add some bindings to the context chain.

                                      appCtx                  .                  bind                  (                  '                  foo.app                  '                  ).                  to                  (                  '                  app.bar                  '                  );                  serverCtx                  .                  bind                  (                  '                  foo.server                  '                  ).                  to                  (                  '                  server.bar                  '                  );                              

    The possessor context for the code snippet above will exist:

    • 'foo.app': appCtx
    • 'foo.server': serverCtx
  2. The current context

    The electric current context is either the one that is explicitly passed in APIs that starts the resolution or implicitly used to inject dependencies. For dependency injections, it will exist the resolution context of the owning class binding. For example, the current context is reqCtx for the argument below:

                                      const                  val                  =                  await                  reqCtx                  .                  get                  (                  '                  foo.app                  '                  );                              
  3. The resolution context

    The resolution context is the context in the chain that will exist used to observe bindings past key. Only the resolution context itself and its ancestors are visible for the bounden resolution.

    The resolution context is determined for a binding primal as follows:

    a. Find the get-go binding with the given key in the electric current context or its ancestors recursively

    b. Use the scope of bounden establish to locate the resolution context:

    • Employ the current context for CONTEXT and TRANSIENT scopes
    • Use the owner context for SINGLETON scope
    • Use the commencement context that matches the binding scope in the concatenation starting from the current context and traversing to its ancestors for Awarding, SERVER and REQUEST scopes

    For example:

                                      import                  {                  generateUniqueId                  }                  from                  '                  @loopback/core                  '                  ;                  appCtx                  .                  demark                  (                  '                  foo                  '                  ).                  to                  (                  '                  app.bar                  '                  );                  serverCtx                  .                  demark                  (                  '                  foo                  '                  )                  .                  toDynamicValue                  (()                  =>                  `foo.server.                  ${                  generateUniqueId                  ()}                  `                  )                  .                  inScope                  (                  BindingScope                  .                  SERVER                  );                  serverCtx                  .                  bind                  (                  '                  xyz                  '                  )                  .                  toDynamicValue                  (()                  =>                  `abc.server.                  ${                  generateUniqueId                  ()}                  `                  )                  .                  inScope                  (                  BindingScope                  .                  SINGLETON                  );                  const                  val                  =                  wait                  reqCtx                  .                  become                  (                  '                  foo                  '                  );                  const                  appVal                  =                  await                  appCtx                  .                  go                  (                  '                  foo                  '                  );                  const                  xyz                  =                  await                  reqCtx                  .                  get                  (                  '                  xyz                  '                  );                              

    For const val = await reqCtx.get('foo');, the bounden volition be foo (scope=SERVER) in the serverCtx and resolution context will be serverCtx.

    For const appVal = await appCtx.get('foo');, the binding will be foo (scope=TRANSIENT) in the appCtx and resolution context will be appCtx.

    For const xyz = await reqCtx.go('xyz');, the binding will be xyz (scope=SINGLETON) in the serverCtx and resolution context will exist serverCtx.

    For dependency injections, the current context will exist the resolution context of the class binding that declares injections. The resolution context will be located for each injection. If the bindings to exist injected is Non visible (either the fundamental does not exist or only exists in descendant) to the resolution context, an error will be reported.

Refresh a binding with not-transient scopes

SINGLETON/CONTEXT/Application/SERVER scopes can be used to minimize the number of value instances created for a given bounden. Merely sometimes we would similar to force reloading of a binding when its configuration or dependencies are inverse. For example, a logging provider can be refreshed to pick upwards a new logging level. The aforementioned functionality can exist achieved with TRANSIENT scope but with much more than overhead.

The binding.refresh() method invalidates the cache so that its value will exist reloaded adjacent time.

WARNING: The land held in the buried value will be gone.

                          let              logLevel              =              1              ;              // 'info'              // Logging configuration              export              interface              LoggingOptions              {              level              :              number              ;              }              // A simple logger              export              class              Logger              {              constructor              (@              config              ()              private              options              :              LoggingOptions              )              {}              log              (              level              :              string              ,              message              :              string              )              {              if              (              this              .              options              .              level              >=              level              )              {              console              .              log              (              '              [%d] %s              '              ,              level              ,              message              );              }              }              }              // Bind the logger              const              binding              =              ctx              .              bind              (              '              logger              '              )              .              toClass              (              Logger              )              .              inScope              (              BindingScope              .              SINGLETON              );              // Start with `info` level logging              ctx              .              configure              (              bounden              .              key              ).              to              ({              level              :              i              });              const              logger              =              look              ctx              .              get              <              Logger              >              (              '              logger              '              );              logger              .              log              (              ane              ,              '              info message              '              );              // Prints to console              logger              .              log              (              5              ,              '              debug message              '              );              // Does not print to console              // Now change the configuration to enable debugging              ctx              .              configure              (              binding              .              key              ).              to              ({              level              :              5              });              // Strength a refresh on the binding              binding              .              refresh              (              ctx              );              const              newLogger              =              await              ctx              .              get              <              Logger              >              (              '              logger              '              );              newLogger              .              log              (              one              ,              '              info message              '              );              // Prints to panel              newLogger              .              log              (              five              ,              '              debug message              '              );              // Prints to console also!                      

Draw tags

Tags tin can be used to comment bindings so that they can be grouped or searched. For example, we can tag a binding as a controller or repository. The tags are often introduced by an extension point to marking its extensions contributed past other components.

There are two types of tags:

  • Uncomplicated tag - a tag string, such as 'controller'
  • Value tag - a name/value pair, such as {name: 'MyController'}

Internally, nosotros utilize the tag name equally its value for simple tags, for example, {controller: 'controller'}.

                          binding              .              tag              (              '              controller              '              );              bounden              .              tag              (              '              controller              '              ,              {              name              :              '              MyController              '              });                      

The binding tags tin be accessed via binding.tagMap or bounden.tagNames.

Binding tags play an import role in discovering artifacts with matching tags. The filterByTag helper function and context.findByTag method tin be used to friction match/detect bindings past tag. The search criteria tin can exist i of the followings:

  1. A tag name, such as controller
  2. A tag name wildcard or regular expression, such equally controller.* or /controller/
  3. An object contains tag proper noun/value pairs, such as {name: 'my-controller', type: 'controller'}. In add-on to exact lucifer, the value for a tag proper noun can exist a office that determines if a given tag value matches. For example,

                                      import                  {                  ANY_TAG_VALUE                  ,                  // Match whatever value if it exists                  filterByTag                  ,                  includesTagValue                  ,                  // Friction match tag value equally an array that includes the item                  TagValueMatcher                  ,                  }                  from                  '                  @loopback/core                  '                  ;                  // Friction match a binding with a named service                  ctx                  .                  find                  (                  filterByTag                  ({                  proper noun                  :                  ANY_TAG_VALUE                  ,                  service                  :                  '                  service                  '                  }));                  // Match a bounden equally an extension for `my-extension-bespeak`                  ctx                  .                  notice                  (                  filterByTag                  ({                  extensionFor                  :                  includesTagValue                  (                  '                  my-extension-signal                  '                  )}),                  );                  // Lucifer a bounden with weight > 100                  const                  weightMatcher                  :                  TagValueMatcher                  =                  tagValue                  =>                  tagValue                  >                  100                  ;                  ctx                  .                  detect                  (                  filterByTag                  ({                  weight                  :                  weightMatcher                  }));                              

Chain multiple steps

The Binding fluent APIs allow us to chain multiple steps as follows:

                          context              .              bind              (              '              my-fundamental              '              ).              to              (              '              my-value              '              ).              tag              (              '              my-tag              '              );                      

Use a template function

Information technology's common that we desire to configure certain bindings with the same attributes such as tags and telescopic. To allow such setting, use binding.use():

                          consign              const              serverTemplate              =              (              bounden              :              Binding              )              =>              binding              .              inScope              (              BindingScope              .              SINGLETON              ).              tag              (              '              server              '              );                      
                          const              serverBinding              =              new              Binding              <              RestServer              >              (              '              servers.RestServer1              '              );              serverBinding              .              use              (              serverTemplate              );                      

Configure bounden attributes for a class

Classes can be discovered and spring to the application context during boot. In improver to conventions, it's often desirable to allow certain bounden attributes, such every bit telescopic and tags, to be specified every bit metadata for the class. When the class is jump, these attributes are honored to create a binding. You can use @injectable decorator to configure how to bind a class.

                          import              {              injectable              ,              BindingScope              }              from              '              @loopback/core              '              ;              // @injectable() accepts scope and tags              @              injectable              ({              scope              :              BindingScope              .              SINGLETON              ,              tags              :              [              '              service              '              ],              })              export              class              MyService              {}              // @binding.provider is a shortcut for a provider class              @              injectable              .              provider              ({              tags              :              {              primal              :              '              my-engagement-provider              '              ,              },              })              export              grade              MyDateProvider              implements              Provider              <              Engagement              >              {              value              ()              {              return              new              Date              ();              }              }              @              injectable              ({              tags              :              [              '              controller              '              ,              {              name              :              '              my-controller              '              }],              })              consign              course              MyController              {}              // @injectable() can take one or more binding template functions              @              injectable              (              binding              =>              binding              .              tag              (              '              controller              '              ,              {              name              :              '              your-controller              '              })              export              class              YourController              {}                      

Then a binding can be created past inspecting the grade,

                          import              {              createBindingFromClass              }              from              '              @loopback/cadre              '              ;              const              ctx              =              new              Context              ();              const              binding              =              createBindingFromClass              (              MyService              );              ctx              .              add              (              binding              );                      

Please notation createBindingFromClass also accepts an optional options parameter of BindingFromClassOptions type with the following settings:

  • key: Binding primal, such as controllers.MyController
  • type: Artifact type, such equally server, controller, repository or service
  • name: Artifact name, such every bit my-residue-server and my-controller, default to the name of the bound class
  • namespace: Namespace for the binding key, such as servers and controllers. If cardinal does not be, its value is calculated equally <namespace>.<name>.
  • typeNamespaceMapping: Mapping artifact type to bounden cardinal namespaces, such as:

                                      {                  controller                  :                  '                  controllers                  '                  ,                  repository                  :                  '                  repositories                  '                  }                              
  • defaultNamespace: Default namespace if namespace or namespace tag does not be
  • defaultScope: Default scope if the bounden does not have an explicit scope set. The scope from @injectable of the bound class takes precedence.

The createBindingFromClass tin can be used for iii kinds of classes as the value provider for bindings.

  1. The class for toClass()

                                      @                  injectable                  ({                  tags                  :                  {                  greeting                  :                  '                  a                  '                  }})                  class                  Greeter                  {                  constructor                  (@                  inject                  (                  '                  currentUser                  '                  )                  individual                  user                  :                  string                  )                  {}                  greet                  ()                  {                  render                  `Hello,                                    ${                  this                  .                  user                  }                  `                  ;                  }                  }                  // toClass() is used internally                  // A tag `{type: 'class'}` is added                  const                  binding                  =                  createBindingFromClass                  (                  Greeter                  );                  ctx                  .                  add together                  (                  bounden                  );                              
  2. The form for toProvider()

                          @              injectable              ({              tags              :              {              greeting              :              '              b              '              }})              class              GreetingProvider              implements              Provider              <              cord              >              {              constructor              (@              inject              (              '              currentUser              '              )              private              user              :              cord              )              {}              value              ()              {              return              `Hi,                            ${              this              .              user              }              `              ;              }              }              // toProvider() is used internally              // A tag `{type: 'provider'}` is added              const              binding              =              createBindingFromClass              (              GreetingProvider              );              ctx              .              add              (              binding              );                      
  1. The course for toDynamicValue()
                          @              injectable              ({              tags              :              {              greeting              :              '              c              '              }})              class              DynamicGreetingProvider              {              static              value              (@              inject              (              '              currentUser              '              )              user              :              string              )              {              return              `Hello,                            ${              this              .              user              }              `              ;              }              }              // toDynamicValue() is used internally              // A tag `{type: 'dynamicValueProvider'}` is added              const              binding              =              createBindingFromClass              (              GreetingProvider              );              ctx              .              add              (              binding              );                      

The @injectable is optional for such classes. But it's usually there to provide additional metadata such as telescopic and tags for the binding. Without @injectable, createFromClass merely calls underlying toClass, toProvider, or toDynamicValue based on the class signature.

When to call createBindingFromClass

Classes that are placed in specific directories such every bit : src/datasources, src/controllers, src/services, src/repositories, src/observers, src/interceptors, etc are automatically registered by the boot process, and so it is not necessary to call

                          const              binding              =              createBindingFromClass              (              AClassOrProviderWithBindDecoration              );              ctx              .              add              (              binding              );                      

in your application.

If, on the other hand, your classes are placed in different directories expected past the kick process, then it is necessary to call the code in a higher place in your awarding.

How the Boot Process Calls createBindingFromClass for you

A default LoopBack 4 application uses BootMixin which loads the BootComponent. Information technology declares the main booters for an awarding : application metadata, controllers, repositories, services, datasources, lifecycle observers, interceptors, and model api. The ControllerBooter, for example, calls this.app.controller(controllerClass) for every controller grade discovered in the controllers folder. This method does all the piece of work for you; as shown below:

loopback-side by side/packages/core/src/application.ts

                          controller              (              controllerCtor              :              ControllerClass              ,              name              ?:              string              ):              Binding              {              debug              (              '              Adding controller %s              '              ,              proper name              ??              controllerCtor              .              proper name              );              const              binding              =              createBindingFromClass              (              controllerCtor              ,              {              name              ,              namespace              :              CoreBindings              .              CONTROLLERS              ,              type              :              CoreTags              .              CONTROLLER              ,              defaultScope              :              BindingScope              .              TRANSIENT              ,              });              this              .              add              (              binding              );              return              binding              ;              }                      

Encoding value types in binding keys

String keys for bindings do not assistance enforce the value type. Consider the instance from the previous department:

                          app              .              bind              (              '              hello              '              ).              to              (              '              globe              '              );              panel              .              log              (              app              .              getSync              <              cord              >              (              '              hi              '              ));                      

The code obtaining the bound value is explicitly specifying the type of this value. Such solution is far from ideal:

  1. Consumers have to know the exact proper name of the type that'south associated with each bounden key and also where to import it from.
  2. Consumers must explicitly provide this blazon to the compiler when calling ctx.go in society to benefit from compile-type checks.
  3. Information technology's easy to accidentally provide a wrong type when retrieving the value and get a false sense of security.

The third bespeak is of import because the bugs can be subtle and difficult to spot.

Consider the post-obit REST binding primal:

                          export              const              HOST              =              '              residue.host              '              ;                      

The bounden cardinal does non provide any indication that undefined is a valid value for the HOST binding. Without that noesis, 1 could write the post-obit lawmaking and get it accustomed by TypeScript compiler, simply to larn at runtime that HOST may be also undefined and the code needs to observe the server'south host proper name using a different way.:

                          const              resolve              =              promisify              (              dns              .              resolve              );              const              host              =              await              ctx              .              go              <              string              >              (              RestBindings              .              HOST              );              const              records              =              await              resolve              (              host              );              // etc.                      

To accost this trouble, LoopBack provides a templated wrapper class allowing binding keys to encode the value type too. The HOST binding described higher up tin can be defined as follows:

                          consign              const              HOST              =              new              BindingKey              <              string              |              undefined              >              (              '              rest.host              '              );                      

Context methods like .get() and .getSync() understand this wrapper and use the value type from the bounden key to depict the type of the value they are returning themselves. This allows bounden consumers to omit the expected value type when calling .get() and .getSync().

When we rewrite the declining snippet resolving HOST names to use the new API, the TypeScript compiler immediately tells us about the problem:

                          const              host              =              wait              ctx              .              get              (              RestBindings              .              HOST              );              const              records              =              await              resolve              (              host              );              // Compiler complains:              // - cannot catechumen string | undefined to string              //  - cannot convert undefined to string                      

Binding events

A binding can emit changed events upon changes triggered by methods such every bit tag, inScope, to, and toClass.

The bounden listener part signature is described every bit:

                          /**  * Information for a bounden event  */              export              type              BindingEvent              =              {              /**    * Effect type    */              blazon              :              string              ;              /**    * Source bounden that emits the issue    */              bounden              :              Readonly              <              Binding              <              unknown              >>              ;              /**    * Performance that triggers the event    */              operation              :              string              ;              };              /**  * Outcome listeners for binding events  */              consign              type              BindingEventListener              =              (              /**    * Bounden event    */              event              :              BindingEvent              ,              )              =>              void              ;                      

Now we tin can register a binding listener to exist triggered when tags are changed:

                          const              bindingListener              :              BindingEventListener              =              ({              binding              ,              functioning              })              =>              {              if              (              operation              ===              '              tag              '              )              {              panel              .              log              (              '              Binding tags for %s %j              '              ,              binding              .              primal              ,              binding              .              tagMap              );              }              });              binding              .              on              (              '              changed              '              ,              bindingListener              );