diff --git a/pages/en/lb4/Context.md b/pages/en/lb4/Context.md index 6b5e8c0ae..35200749c 100644 --- a/pages/en/lb4/Context.md +++ b/pages/en/lb4/Context.md @@ -26,10 +26,23 @@ builtin/in-memory storage mechanism). - You have full access to updated/real-time application+request state at all times. -LoopBack's context system allows an unlimited amount of Context instances, -each of which may have a parent Context. +## How to create a context? + +A context can be created with an optional parent and an optional name. If the +name is not provided, a UUID will be generated as the value. Context instances +can be chained using the `parent` to form a hierarchy. For example, the code +below creates a chain of three contexts: `reqCtx -> serverCtx -> rootCtx`. + +```ts +import {Context} from '@loopback/context'; +const rootCtx = new Context('root-ctx'); // No parent +const serverCtx = new Context(rootCtx, 'server-ctx'); // rootCtx as the parent +const reqCtx = new Context(serverCtx); // No explicit name, a UUID will be generated +``` -Typically, however, an application will have three "levels" of context: application-level, server-level and request-level. +LoopBack's context system allows an unlimited amount of Context instances, +each of which may have a parent Context. However, an application typically +has three "levels" of context: application-level, server-level and request-level. ## Application-level context (global) @@ -42,6 +55,7 @@ Here is a simple example: ```js const Application = require('@loopback/core').Application; +// Please note `Application` extends from `Context` const app = new Application(); // `app` is a "Context" class MyController { ... } app.controller(MyController); @@ -145,13 +159,13 @@ section. However, when using classes, LoopBack provides a better way to get at stuff in the context via the `@inject` decorator: -```js -const Application = require('@loopback/core'); +```ts +import {inject} from '@loopback/context'; +import {Application} from '@loopback/core'; const app = new Application(); const app.bind('defaultName').to('John'); class HelloController { - // consider this.ctx here constructor(@inject('defaultName') name) { this.name = name; } diff --git a/pages/en/lb4/Decorators.md b/pages/en/lb4/Decorators.md index 7104a29db..f0e7a071d 100644 --- a/pages/en/lb4/Decorators.md +++ b/pages/en/lb4/Decorators.md @@ -294,6 +294,23 @@ Syntax: `@inject.tag(tag: string | RegExp)`. // `store.locations` is now `['San Francisco', 'San Jose']` ``` +- `@inject.context`: inject the current context + +Syntax: `@inject.context()`. + +```ts + class MyComponent { + constructor(@inject.context() public ctx: Context) {} + } + + const ctx = new Context(); + ctx.bind('my-component').toClass(MyComponent); + const component: MyComponent = ctx.getSync('my-component'); + // `component.ctx` should be the same as `ctx` +``` + +**NOTE**: It's recommended to use `@inject` with specific keys for dependency injection if possible. Use `@inject.context` only when the code need to access the current context object for advanced use cases. + For more information, see the [Dependency Injection](Dependency-Injection.htm) section under [LoopBack Core Concepts](Concepts.htm) ## Authentication Decorator diff --git a/pages/en/lb4/Dependency-injection.md b/pages/en/lb4/Dependency-injection.md index fa20e5da7..716037544 100644 --- a/pages/en/lb4/Dependency-injection.md +++ b/pages/en/lb4/Dependency-injection.md @@ -140,28 +140,109 @@ class InfoController { } ``` +## Optional dependencies + +Sometimes the dependencies are optional. For example, the logging level for a Logger provider can have a default value if it is not set (bound to the context). + +To resolve an optional dependency, set `optional` flag to true: + +```ts +const ctx = new Context(); +await ctx.get('optional-key', {optional: true}); // Return `undefined` instead of throwing an error +``` + +Here is another example showing optional dependency injection using properties with default values: + +```ts +// Optional property injection +export class LoggerProvider implements Provider { + // Log writer is an optional dependency and it falls back to `logToConsole` + @inject('log.writer', {optional: true}) + private logWriter: LogWriterFn = logToConsole; + + // Log level is an optional dependency with a default value `WARN` + @inject('log.level', {optional: true}) + private logLevel: string = 'WARN'; +} +``` + +Optional dependencies can also be used with constructor and method injections. For example: + +```ts +// Optional constructor injection +export class LoggerProvider implements Provider { + constructor( + // Log writer is an optional dependency and it falls back to `logToConsole` + @inject('log.writer', {optional: true}) + private logWriter: LogWriterFn = logToConsole, + + // Log level is an optional dependency with a default value `WARN` + @inject('log.level', {optional: true}) + private logLevel: string = 'WARN', + ) {} +} +``` + +```ts +// Optional method injection +export class MyController { + // prefix is optional + greet(@inject('hello.prefix', {optional: true}) prefix: string = 'Hello') { + return `${prefix}, world!`; + } +} +``` + ## Circular dependencies LoopBack can detect circular dependencies and report the path which leads to the problem. For example, ```ts -const context = new Context(); - interface XInterface {} - interface YInterface {} +import {Context, inject} from '@loopback/context'; - class XClass implements XInterface { - @inject('y') public y: YInterface; - } +interface Developer { + // Each developer belongs to a team + team: Team; +} - class YClass implements YInterface { - @inject('x') public x: XInterface; - } +interface Team { + // Each team works on a project + project: Project; +} + +interface Project { + // Each project has a lead developer + lead: Developer; +} - context.bind('x').toClass(XClass); - context.bind('y').toClass(YClass); - // An error will be thrown below - Circular dependency detected on path 'x --> y --> x' - const x: XInterface = context.getSync('x'); +class DeveloperImpl implements Developer { + constructor(@inject('team') public team: Team) {} +} + +class TeamImpl implements Team { + constructor(@inject('project') public project: Project) {} +} + +class ProjectImpl implements Project { + constructor(@inject('lead') public lead: Developer) {} +} + +const context = new Context(); + +context.bind('lead').toClass(DeveloperImpl); +context.bind('team').toClass(TeamImpl); +context.bind('project').toClass(ProjectImpl); + +try { + // The following call will fail + context.getSync('lead'); +} catch (e) { + console.error(e.toString()); + // Error: Circular dependency detected: lead --> @DeveloperImpl.constructor[0] + // --> team --> @TeamImpl.constructor[0] --> project --> @ProjectImpl.constructor[0] + // --> lead +} ``` ## Additional resources