Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions pages/en/lb4/Context.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
17 changes: 17 additions & 0 deletions pages/en/lb4/Decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
107 changes: 94 additions & 13 deletions pages/en/lb4/Dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Logger> {
// 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<Logger> {
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
Expand Down