Skip to content
This repository was archived by the owner on Feb 2, 2018. It is now read-only.

Commit 4e80aba

Browse files
committed
Feedback Applied.
1 parent dee4a46 commit 4e80aba

File tree

5 files changed

+110
-59
lines changed

5 files changed

+110
-59
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
// License text available at https://opensource.org/licenses/MIT
55

66
export * from './mixins/logger.mixin';
7+
export * from './types';

src/mixins/README.md

Lines changed: 84 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,122 @@
11
# Mixins
22

3-
This directory contains mixins for `Application`.
3+
This directory contains source files for the mixins exported by this extension.
44

55
## Overview
66

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.
88

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.
1010

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));
1615
```
1716

18-
Mixins can be nested, as such you can do the following:
19-
```
20-
cosnt newClass = MixinClass1(MixinClass2(BaseClass))
21-
```
2217

23-
### LoggerMixin
18+
## Getting Started
2419

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`.
2621

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()`.
3024
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 {
3728
log(...args: any[]) {
3829
console.log('log :', ...args);
3930
}
4031

41-
info(...args: any[]) {
32+
error(...args: any[]) {
4233
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');
4436
}
37+
}
38+
```
4539

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);
4968
}
69+
}
70+
```
5071

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+
}
54101
}
55-
};
102+
}
56103
```
57104

58-
Once a Logger has been bound, you can retrieve it by using [Dependency Inject](http://loopback.io/doc/en/lb4/Dependency-injection.html)
59105

60-
#### More Examples for binding a Logger**
106+
## Examples for using LoggerMixin
61107
```
62108
// Using the app's .logger() function.
63109
class LoggingApplication extends LoggerMixin(Application) {
64-
constructor() {
65-
super();
110+
constructor(...args: any[]) {
111+
super(...args);
66112
const app = this;
67113
}
68114
69115
app.logger(ColorLogger);
70116
}
71117
72-
// Binding a Logger provided by a components
73-
class MyComponent {
118+
// Binding a Logger provided by a component
119+
class MyComponent implements Component{
74120
loggers: [ColorLogger];
75121
}
76122

src/mixins/logger.mixin.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// tslint:disable:no-any
77

88
import {Constructor} from '@loopback/context';
9+
import {Logger} from '../types';
910

1011
/**
1112
* A mixin class for Application that creates a .logger()
@@ -47,7 +48,7 @@ export function LoggerMixin<T extends Constructor<any>>(superClass: T) {
4748
* app.logger(Logger);
4849
* ```
4950
*/
50-
logger(logClass: Constructor<any>) {
51+
logger(logClass: Constructor<Logger>) {
5152
const loggerKey = `loggers.${logClass.name}`;
5253
this.bind(loggerKey).toClass(logClass);
5354
}
@@ -73,7 +74,7 @@ export function LoggerMixin<T extends Constructor<any>>(superClass: T) {
7374
*/
7475
component(component: Constructor<any>) {
7576
super.component(component);
76-
this.mountComponentLogger(component);
77+
this.mountComponentLoggers(component);
7778
}
7879

7980
/**
@@ -83,7 +84,7 @@ export function LoggerMixin<T extends Constructor<any>>(superClass: T) {
8384
*
8485
* @param component The component to mount Logger's of
8586
*/
86-
mountComponentLogger(component: Constructor<any>) {
87+
mountComponentLoggers(component: Constructor<any>) {
8788
const componentKey = `components.${component.name}`;
8889
const compInstance = this.getSync(componentKey);
8990

src/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright IBM Corp. 2013,2017. All Rights Reserved.
2+
// Node module: loopback-next-extension-starter
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
// Types and interfaces exposed by the extension go here
7+
8+
// tslint:disable-next-line:no-any
9+
export type LogArgs = any[];
10+
11+
export interface Logger {
12+
log(...args: LogArgs): void;
13+
error(...args: LogArgs): void;
14+
15+
info?(...args: LogArgs): void;
16+
warn?(...args: LogArgs): void;
17+
}

test/unit/logger-mixin.test.ts renamed to test/unit/mixins/logger.mixin.test.ts

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// License text available at https://opensource.org/licenses/MIT
55

66
import {expect} from '@loopback/testlab';
7-
import {LoggerMixin} from '../..';
7+
import {LoggerMixin, LogArgs, Logger} from '../../..';
88
import {Application, Component} from '@loopback/core';
99
import {Constructor} from '@loopback/context';
1010

@@ -61,26 +61,12 @@ describe('LoggerMixin', () => {
6161

6262
class AppWithLogger extends LoggerMixin(Application) {}
6363

64-
class MyLogger {
65-
// tslint:disable-next-line:no-any
66-
log(...args: any[]) {
64+
class MyLogger implements Logger {
65+
log(...args: LogArgs) {
6766
console.log('log :', ...args);
6867
}
6968

70-
// tslint:disable-next-line:no-any
71-
info(...args: any[]) {
72-
const data = args.join(' ');
73-
console.log('\x1b[32m info : ' + data + '\x1b[0m');
74-
}
75-
76-
// tslint:disable-next-line:no-any
77-
warn(...args: any[]) {
78-
const data = args.join(' ');
79-
console.log('\x1b[33m warn : ' + data + '\x1b[0m');
80-
}
81-
82-
// tslint:disable-next-line:no-any
83-
error(...args: any[]) {
69+
error(...args: LogArgs) {
8470
const data = args.join(' ');
8571
console.log('\x1b[31m error: ' + data + '\x1b[0m');
8672
}

0 commit comments

Comments
 (0)