This directory contains source files for the mixins exported by this extension.
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.
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.
class MyApplication extends MyMixinClass(Application) {
// Your code
}
// Multiple Classes mixed together
class MyApp extends MyMixinClass(MyMixinClass2(Application)) {
// Your code
}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
and the key for the Logger.
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 isconsolewhich hasconsole.log()andconsole.error().
class ColorLogger implements Logger {
log(...args: LogArgs) {
console.log('log :', ...args);
}
error(...args: LogArgs) {
// log in red color
console.log('\x1b[31m error: ', ...args, '\x1b[0m');
}
}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.
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.
It is also important for the constructor to call super(args) so Application
continues to work as expected.
constructor(...args: any[]) {
super(args);
}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.
class MyApp extends LoggerMixin(Application) {
constructor(...args: any[]) {
super(...args);
}
}
const app = new MyApp({
loggers: [ColorLogger],
});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).
if (this.options.loggers) {
for (const logger of this.options.loggers) {
this.logger(logger);
}
}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.
logger(logClass: Logger) {
const loggerKey = `loggers.${logClass.name}`;
this.bind(loggerKey).toClass(logClass);
}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().
component(component: Constructor<any>) {
super.component(component); // ensures existing behavior from Application
this.mountComponentLoggers(component);
}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).
mountComponentLoggers(component: Constructor<any>) {
const componentKey = `components.${component.name}`;
const compInstance = this.getSync(componentKey);
if (compInstance.loggers) {
for (const logger of compInstance.loggers) {
this.logger(logger);
}
}
}Now that we have bound a Logger to our Application via one of the many ways made
possible by LoggerMixin, we need to be able to retrieve it so we can use it.
Let's say we want to use it in a controller. Here's an example to retrieving it
so we can use it.
class MyController {
constructor(@inject('loggers.ColorLogger') protected log: Logger) {}
helloWorld() {
this.log.log('hello log');
this.log.error('hello error');
}
}class LoggingApplication extends LoggerMixin(Application) {
constructor(...args: any[]) {
super(...args);
this.logger(ColorLogger);
}
}class LoggerApplication extends LoggerMixin(Application) {
constructor() {
super({
loggers: [ColorLogger],
});
}
}class LoggingComponent implements Component {
loggers: [ColorLogger];
}
const app = new LoggingApplication();
app.component(LoggingComponent); // Logger from MyComponent will be bound to loggers.ColorLogger