Dependency Injection Library - Rename Entered Values

I would like to introduce lodash by name, something like this:

let val = function(lodash){ // lodash will be injected, simply by using require('lodash'); }; 

but I will say that I want to rename the import, I want to do something like this:

 let val = function({lodash:_}){ }; 

or

 let val = function(lodash as _){ }; 

is there any way to do this with ES6 / ES7 / ES8 or TypeScript?

Note that this DI structure works more than just requires ("x") ... first it will try to enter other values, if nothing exists, then it will try to require a value.

Note also that the requirements here are that when you call val.toString (), then "lodash" will be treated as the name of the argument. But _ instead of lodash will be displayed at runtime inside the function body. This is because to enter lodash we call fn.toString () to get the argument names.

+7
javascript dependency-injection ecmascript-next typescript
source share
4 answers

Update

Here's a link to the npm di-proxy package (inspired by this answer) with 100% code coverage and memoization support to increase performance compatible with Node.js >=6.0.0 .

Old answer

Here is an amazing solution that I found out messing around with object destruction and Proxy :

 /* MIT License */ /* Copyright 2017 Patrick Roberts */ // dependency injection utility function inject(callbackfn) { const handler = { get(target, name) { /* this is just a demo, swap these two lines for actual injection */ // return require(name); return { name }; } }; const proxy = new Proxy({}, handler); return (...args) => callbackfn.call(this, proxy, ...args); } // usage // wrap function declaration with inject() const val = inject(function ({ lodash: _, 'socket.io': sio, jquery: $, express, fs }, other, args) { // already have access to lodash, no need to even require() here console.log(_); console.log(sio); console.log($); console.log(express); console.log(fs); console.log(other, args); }); // execute wrapped function with automatic injection val('other', 'args'); 
 .as-console-wrapper { max-height: 100% !important; } 

How it works

Passing parameters to a function by restructuring the object calls the getter methods for each property in the object literal to determine the values ​​when the function is executed.

If the destructible object is initialized as a Proxy , you can intercept every getter call with a link to an attempt to use the property name to be resolved, and return the value that you decide to solve. In this case, the resolution should be require(name) , which the module introduces, simply specifying it as the name of the property in the parameter of the function object.

Below is a link to a demo version where you can see how it works in Node.js.

Try it online!

Here is the code in this demo for reference only, because it demonstrates object destruction to a greater extent:

 /* MIT License */ /* Copyright 2017 Patrick Roberts */ // dependency injection utility function inject(callbackfn) { const handler = { get(target, name) { return require(name); } }; const proxy = new Proxy({}, handler); return (...args) => callbackfn.call(this, proxy, ...args); } // usage // wrap function declaration with inject() const val = inject(function ({ fs: { readFile: fsRead, writeFile: fsWrite }, child_process: { fork: cpF, spawn: cpS, exec: cpE }, events: { EventEmitter } }, other, args) { // already have access to modules, no need to require() here console.log('fs:', { fsRead, fsWrite }); console.log('child_process:', { fork: cpF, spawn: cpS, exec: cpE }); console.log('EventEmitter:', EventEmitter); console.log(other, args); }); // execute wrapped function with automatic injection val('other', 'args'); 

As stated above, I published a complete npm package that implements this concept. I recommend that you check this if you like this syntax and want something more advanced and tested than this very simple example.

+5
source share

JavaScript has no syntax that supports this mapping. Even if a custom function parser was written to provide the desired behavior for destructed parameters, such as function({lodash:_}) ... , it would not work for overloaded functions, which is a serious drawback. The easiest way to handle this is

 function foo(lodash){ const _ = lodash; ... } 

And this obviously will not work for invalid variable names like lodash.pick .

A common practice for DI recipes for this is to provide annotations. All annotations described can be combined together. They are especially implemented in Angular DI . Angular injector is available for standalone use (including Node) as injection-js library.

Annotation property

Thus, the signature of the function and the list of dependencies should not match. This recipe can be seen in action in AngularJS.

The property contains a list of DI tokens. They can be dependency names that will be loaded using require or something else.

 // may be more convenient when it a string const ANNOTATION = Symbol(); ... foo[ANNOTATION] = ['lodash']; function foo(_) { ... } bar[ANNOTATION] = ['lodash']; function bar() { // doesn't need a param in signature const _ = arguments[0]; ... } 

And DI runs like

 const fnArgs = require('fn-args'); const annotation = foo[ANNOTATION] || fnArgs(foo); foo(...annotation.map(depName => require(depName)); 

This annotation style allows you to use function definitions because lifting allows you to place a note over the function signature for convenience.

Array Annotations

The signature of the function and the list of dependencies must not match. This recipe can be seen in AngularJS.

When a function is represented as an array, this means that it is an annotated function, and its parameters should be treated as annotations, and the latter itself.

 const foo = [ 'lodash', function foo(_) { ... } ]; ... const fn = foo[foo.length - 1]; const annotation = foo.slice(0, foo.length - 1); foo(...annotation.map(depName => require(depName)); 

TypeScript annotation

This recipe can be seen in Angular (2 and above) and relies on TypeScript types. Types can be extracted from the constructor signature and used for DI. Possible are the Reflect metadata clause and TypeScript emitDecoratorMetadata .

Released constructor types are saved as metadata for the respective classes and can be obtained using the Reflect API to resolve dependencies. This is a DI class , since decorators are only supported in classes, it works best with DI containers:

 import 'core-js/es7/reflect'; abstract class Dep {} function di(target) { /* can be noop to emit metadata */ } @di class Foo { constructor(dep: Dep) { ... } } ... const diContainer = { Dep: require('lodash') }; const annotations = Reflect.getMetadata('design:paramtypes', Foo); new (Foo.bind(Foo, ...annotations.map(dep => diContainer [dep]))(); 

This will create workable JS code, but will create type problems since the Lodash object is not an instance of the Dep token class. This method is primarily effective for class dependencies that are introduced into classes.

For non-classic DI, backup to other annotations is required.

+3
source share

I did something that might work for you, but you can always change it and use a general idea.

It is written with ES6 features, but you can easily remove them.

 let di = function() { const argumentsLength = arguments.length; //you must call this func with at least a callback if (argumentsLength === 0) return; //this will be called with odd amount of variables, //pairs of key and assignment, and the callback //means: 1,3,5,7.... amount of args if (argumentsLength%2 === 0) throw "mismatch of args"; //here we will assing the variables to "this" for (key in arguments) { //skip the callback if(key===argumentsLength-1) continue; //skip the "key", it will be used in the next round if(key%2===0) continue; const keyToSet = arguments[key-1]; const valToSet = arguments[key]; this[keyToSet] = valToSet; } arguments[argumentsLength-1].apply(this); } di("name", {a:"IwillBeName"}, "whatever", "IwillBeWhatever", () => { console.log(whatever); console.log(name); }); 

on the bottom line, you call func "di" go through these arguments:

 di("_", lodash, callback); 

now inside the callback code you can reference "lodash" to "_"

0
source share

Given the answers, I still find that Angular 1.x (and RequireJS) is the most efficient, although perhaps not the easiest to use:

 let = createSomething('id', ['lodash', function(_){ }]); 
0
source share

All Articles