Loopback Typeerror: Cannot Read Property 'tostring' of Undefined
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
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:
- Do you demand to have a new value from the binding for each request?
- 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:
-
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
-
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 ofGreetingController
can invokegreet
with dissimilar arguments, such equallyc1.greet('John')
andc1.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 utilizeSINGLETON
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 instantiateGreetingController
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 ;
-
The owner context
The owner context is the context in which a bounden is registered by
ctx.demark()
orctx.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
-
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 ' );
-
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
forCONTEXT
andTRANSIENT
scopes - Use the
owner context
forSINGLETON
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
andREQUEST
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 befoo
(scope=SERVER) in theserverCtx
and resolution context will beserverCtx
.For
const appVal = await appCtx.get('foo');
, the binding will befoo
(scope=TRANSIENT) in theappCtx
and resolution context will beappCtx
.For
const xyz = await reqCtx.go('xyz');
, the binding will bexyz
(scope=SINGLETON) in theserverCtx
and resolution context will existserverCtx
.For dependency injections, the
current context
will exist theresolution context
of the class binding that declares injections. Theresolution 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 theresolution context
, an error will be reported. - Employ the
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:
- A tag name, such as
controller
- A tag name wildcard or regular expression, such equally
controller.*
or/controller/
-
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
orservice
- name: Artifact name, such every bit
my-residue-server
andmy-controller
, default to the name of the bound class - namespace: Namespace for the binding key, such as
servers
andcontrollers
. Ifcardinal
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.
-
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 );
-
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 );
- 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:
- 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.
- Consumers must explicitly provide this blazon to the compiler when calling ctx.go in society to benefit from compile-type checks.
- 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 );
Source: https://loopback.io/doc/en/lb4/Binding.html
0 Response to "Loopback Typeerror: Cannot Read Property 'tostring' of Undefined"
Post a Comment