|
1 | 1 | # Mixins |
2 | 2 |
|
3 | | -This directory contains mixins for `Application`. |
| 3 | +This directory contains source files for the mixins exported by this extension. |
4 | 4 |
|
5 | 5 | ## Overview |
6 | 6 |
|
7 | | -A mixin is a class which take the input of a base class and adds / modifies existing functions / status values and returns a new class which can be instantiated. The type of the returned class will still be the base class. Mixins let you extend the BaseClass by adding more functions to it, overriding existing ones or modifiying their behavior. |
| 7 | +Sometimes it's helpful to write partial classes and then combining them together to build more powerful classes. This pattern is called Mixins (mixing in partial classes) and is supported by LoopBack 4. |
8 | 8 |
|
9 | | -## Basic Usage |
| 9 | +LoopBack 4 supports mixins at an `Application` level. Your partial class can then be mixed into the `Application` class. A mixin class can modify or override existing methods of the class or add new ones! It is also possible to mixin multiple classes together as needed. |
10 | 10 |
|
11 | | -The general idea behind using a mixin is to pass in the BaseClass to the Mixin and create a new class. |
12 | | - |
13 | | -**Example** |
14 | | -``` |
15 | | -cosnt newClass = MixinClass(BaseClass); |
| 11 | +### High level example |
| 12 | +```ts |
| 13 | +const MixedClass = MyMixinClass(Application); |
| 14 | +const MultipleMixedClassesClass = MyMixinClass(MyMixinClass2(Application)); |
16 | 15 | ``` |
17 | 16 |
|
18 | | -Mixins can be nested, as such you can do the following: |
19 | | -``` |
20 | | -cosnt newClass = MixinClass1(MixinClass2(BaseClass)) |
21 | | -``` |
22 | 17 |
|
23 | | -### LoggerMixin |
| 18 | +## Getting Started |
24 | 19 |
|
25 | | -LoggerMixin adds capabilities to a LoopBack Next Application by adding a `.logger()` function that is used to bind a Logger class to `Context` automatically. The binding key will be `loggers.${Class.name}` where `Class.name` is the name of the Logger class being bound. Components are also able to provide their own Logger implementation which will be bound via `.component()` automatically when using this Mixin. |
| 20 | +For hello-extensions we write a simple Mixin that allows the `Application` class to bind a `Logger` class from ApplicationOptions, Components, or `.logger()` method that is mixed in. `Logger` instances are bound to the key `loggers.${Logger.name}`. Once a Logger has been bound, the user can retrieve it by using [Dependency Injection](http://loopback.io/doc/en/lb4/Dependency-injection.html) and the key for the `Logger`. |
26 | 21 |
|
27 | | -**Example** |
28 | | -``` |
29 | | -const LoggingApplication = LoggerMixin(Application); // we mixin the Application class from @loopback/core |
| 22 | +### What is a Logger? |
| 23 | +> A Logger class is provides a mechanism for logging messages of varying priority by providing an implementation for `Logger.info()` & `Logger.error()`. An example of a Logger is `console` which has `console.log()` and `console.error()`. |
30 | 24 |
|
31 | | -const app = new LoggingApplication({ |
32 | | - loggers: [ColorLogger] // we can provide an array of loggers to bind automatically at startup |
33 | | -}); |
34 | | -
|
35 | | -// Example Logger |
36 | | -class ColorLogger { |
| 25 | +#### An example Logger |
| 26 | +```ts |
| 27 | +class ColorLogger implements Logger { |
37 | 28 | log(...args: any[]) { |
38 | 29 | console.log('log :', ...args); |
39 | 30 | } |
40 | 31 |
|
41 | | - info(...args: any[]) { |
| 32 | + error(...args: any[]) { |
42 | 33 | const data = args.join(' '); |
43 | | - console.log('\x1b[32m info : ' + data + '\x1b[0m'); |
| 34 | + // log in red color |
| 35 | + console.log('\x1b[31m error: ' + data + '\x1b[0m'); |
44 | 36 | } |
| 37 | +} |
| 38 | +``` |
45 | 39 |
|
46 | | - warn(...args: any[]) { |
47 | | - const data = args.join(' '); |
48 | | - console.log('\x1b[33m warn : ' + data + '\x1b[0m'); |
| 40 | +## LoggerMixin |
| 41 | +A complete & functional implementation can be found in `logger.mixin.ts`. *Here are some key things to keep in mind when writing your own Mixin*. |
| 42 | + |
| 43 | +### constructor() |
| 44 | +A Mixin constructor must take an array of any type as it's argument. This would represent `ApplicationOptions` for our base class `Application` as well as any properties we would like for our Mixin. |
| 45 | + |
| 46 | +It is also important for the constructor to call `super(args)` so `Application` continues to work as expected. |
| 47 | +```ts |
| 48 | +constructor(...args: any[]) { |
| 49 | + super(args); |
| 50 | +}``` |
| 51 | +
|
| 52 | +### Binding via `ApplicationOptions` |
| 53 | +As mentioned earlier, since our `args` represents `ApplicationOptions`, we can make it possible for users to pass in their `Logger` implementations in a `loggers` array on `ApplicationOptions`. We can then read the array and automatically bind these for the user. |
| 54 | +
|
| 55 | +#### Example user experience |
| 56 | +```ts |
| 57 | +new LoggerMixin(Application)({ |
| 58 | + loggers: [ColorLogger], |
| 59 | +}); |
| 60 | +``` |
| 61 | + |
| 62 | +#### Example Implementation |
| 63 | +To implement this, we would check `this.options` to see if it has a `loggers` array and if so, bind it by calling the `.logger()` method. (More on that below). |
| 64 | +```ts |
| 65 | +if (this.options.loggers) { |
| 66 | + for (const logger of this.options.loggers) { |
| 67 | + this.logger(logger); |
49 | 68 | } |
| 69 | +} |
| 70 | +``` |
50 | 71 |
|
51 | | - error(...args: any[]) { |
52 | | - const data = args.join(' '); |
53 | | - console.log('\x1b[31m error: ' + data + '\x1b[0m'); |
| 72 | +### Binding via `.logger()` |
| 73 | +As mentioned earlier, we can add a new function to our `Application` class called `.logger()` into which a user would pass in their `Logger` implementation so we can bind it to the `loggers.*` key for them. We just add this new method on our partial Mixin class. |
| 74 | +```ts |
| 75 | +logger(logClass: Logger) { |
| 76 | + const loggerKey = `loggers.${logClass.name}`; |
| 77 | + this.bind(loggerKey).toClass(logClass); |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +### Binding a `Logger` from a `Component` |
| 82 | +Our base class of `Application` already has a method that binds components. We can modify this method to continue binding a `Component` as usual but also binding any `Logger` instances provided by that `Component`. When modifying behavior of an existing method, we can ensure existing behavior by calling the `super.method()`. In our case the method is `.component()`. |
| 83 | +```ts |
| 84 | +component(component: Constructor<any>) { |
| 85 | + super.component(component); // ensures existing behavior from Application |
| 86 | + this.mountComponentLoggers(component); |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +We have now modified `.component()` to do it's thing and then call our method `mountComponentLoggers()`. In this method is where we check for `Logger` implementations declared by the component in a `loggers` array by retrieving the instance of the `Component`. Then if `loggers` array exists, we bind the `Logger` instances as normal (by leveraging our `.logger()` method). |
| 91 | + |
| 92 | +```ts |
| 93 | +mountComponentLoggers(component: Constructor<any>) { |
| 94 | + const componentKey = `components.${component.name}`; |
| 95 | + const compInstance = this.getSync(componentKey); |
| 96 | + |
| 97 | + if (compInstance.loggers) { |
| 98 | + for (const logger of compInstance.loggers) { |
| 99 | + this.logger(logger); |
| 100 | + } |
54 | 101 | } |
55 | | -}; |
| 102 | +} |
56 | 103 | ``` |
57 | 104 |
|
58 | | -Once a Logger has been bound, you can retrieve it by using [Dependency Inject](http://loopback.io/doc/en/lb4/Dependency-injection.html) |
59 | 105 |
|
60 | | -#### More Examples for binding a Logger** |
| 106 | +## Examples for using LoggerMixin |
61 | 107 | ``` |
62 | 108 | // Using the app's .logger() function. |
63 | 109 | class LoggingApplication extends LoggerMixin(Application) { |
64 | | - constructor() { |
65 | | - super(); |
| 110 | + constructor(...args: any[]) { |
| 111 | + super(...args); |
66 | 112 | const app = this; |
67 | 113 | } |
68 | 114 |
|
69 | 115 | app.logger(ColorLogger); |
70 | 116 | } |
71 | 117 |
|
72 | | -// Binding a Logger provided by a components |
73 | | -class MyComponent { |
| 118 | +// Binding a Logger provided by a component |
| 119 | +class MyComponent implements Component{ |
74 | 120 | loggers: [ColorLogger]; |
75 | 121 | } |
76 | 122 |
|
|
0 commit comments