
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
lightweight, small, high level dependency injector with object lifetime management
#injectjs
a lightweight, small, high level dependency injector with support for object lifetime management
This software is still not fully baked, at version 0.4, and missing a wealth of functionalities for what constitutes a fully fledged dependency injector
With that said, I expect to have a fully functional, fully tested, fully decoupled and fully documented version in the next couple of Months. I will greatly appreciate help from early adopters and will gladly embrace feature requests and bug reports uploaded to the issue tracker.
Until now, the only way to inject dependencies into JavaScript objects has been by manually instantiating them and passing them into the object's constructor, property or method. This creates a load of overhead and boilerplate "new keyword" code. Add to that the fact JavaScript is dynamically typed and you have no real way of knowing whether changing the signature of an object's constructor, property or method will cause an error in the rest of your code (unless, of course, you keep track of every place your recently changed object is used and how). You (usually) won't even get a runtime error since JavaScript doesn't complain at all when you pass a function more (or less) arguments than it has parameters. InjectJS aims to get rid of all this. It allows you to wire up your dependencies beforehand and handle instantiation of all your JS objects.
These are all modularization solutions, not dependency injection solutions. They introduce a sorely needed feature into the language, namely, the ability to create modules and not have all your code live in the global namespace, which gets polluted fairly quickly. What they don't (and shouldn't!) do, is provide high level dependency injection and object lifetime management. That's where InjectJS comes in!
Simply download inject.js or inject.min.js from the dist folder and include it into your html page(s), in that order.
You can also install it using npm npm install inject-js
Note: As of version 0.3 InjectJS includes a grunt build process to lint, test, merge and minify all the code into a single inject.js file (and an inject.min.js file, of course). If you want to tinker about with the different aspects of the library simply clone it and look into the src dir.
Note: InjectJS depends on Lodash (https://lodash.com/). You can probably use it with underscore as well, but the library is developed and tested using Lodash, so there are no assurances.
InjectJS comes with a test suite that fully unit-tests the code. to run it, do the following:
src folder and run all tests on all browsers and both dist files.The main idea behind InjectJS is getting rid of the new keyword in your code. This may seem a controversial statement and impossible to achieve, but it's quite possible and will make your code leaner and more maintainable, or at the very least, less frustrating.
Let's start with a Hello World app. Normally you would make a new function called mouth, instantiate somewhere it and then call it:
function Mouth() {
this.say = function (what) {
alert(what);
}
}
var mouth = new Mouth();
mouth.say('hello world!');
There are a few problems with this approach:
Ok, so one global function and one global variable aren't much to worry about, but this is just the hello world example. With InjectJS you start by registering Mouth as a type:
injector.registerType('mouth', function () {
this.say = function (what) {
alert(what);
}
});
Marvellous... (if only markdown had a descriptor for sarcasm). One good thing happened here though. You now have a mouth type that is no longer global! Next step is to use this newly created type somewhere in your code... how about a main method? you know... the one that runs when the app starts, inside $(function() {}) or somewhere similar:
injector.registerMain(function (mouth) {
mouth.say('hello world!');
});
$(function () {
injector.run(); // runs the function registered with registerMain
});
As of now, we've gotten rid of the globals, which isn't much of a feat in and of itself, but still noteworthy. More importantly, we no longer have to manually instantiate Mouth to use it!
So now, what if you want to hide the original Mouth so it is no longer global? Easy, right?
(function () {
function Mouth() {
this.say = function (what) {
alert(what);
}
}
var mouth = new Mouth();
mouth.say('hello world!');
}());
Ok, great, now we've just solved two of the three initial problems there were with this code (remember you're still manually instantiating). No need for InjectJS then. So let's complicate things a bit...
To see the rest of this example, please head to foo-bar-party. to see a more "real world" use case scenario, check out the code at https://github.com/nstraub/snake (still unpolished, but makes heavy use of the functionalities currently available in this framework)`
The current version, 0.4, has the following features:
To sum it up, it provides basic dependency injection capabilities and the ability to use these dependencies in a test environment.
So here are the next planned releases:
Note: for a more comprehensive explanation of object lifetime you can visit the Wikipedia article
The main selling point of a DI framework is it manages your objects' lifetime - namely when objects get instantiated and when they get destroyed - so you don't have to. As far as I know, JavaScript doesn't have a DI framework that provides this feature, which is why this library is being developed.
Every object in your application gets instantiated at some point, and destroyed at some other. there are objects that last for the entire lifecycle of the app and others that get instantiated each and every time you need to use it. In between are a few other lifetimes that restrict the scope of the object they define. Here's a list of lifetimes supported by inject-js, in order of their scope restriction:
The most typical lifetime used in JS is singleton. Think of a module in ES6, an angular service or the jQuery library. Only one instance of the defined type is created throughout the entire page lifecycle.
The state lifetime is like a singleton, but for a specific state of your application. By default, inject-js recognizes the hashchange event as a change of state, and clears all state objects when the hash portion of a site is changed. You can change this behaviour by registering the clearState method to a custom event and clear the default behaviour by calling the removeDefaultListener method.
An example of this lifetime would be a data-set. Suppose you have a blog with different tags. The homepage shows a data-set of all your blog entries and whe the user clicks on a tag, the data-set gets replaced with a list scoped to the particular selection.
Every object has a dependency graph, i.e. a hierarchy of objects it depends on to perform its desired task. The root lifetime allows you to specify dependencies that will be created once for the entire graph of an object which is not a dependency of another object, and will be reused for every dependency that requires an instance of it.
Like root but specified at an arbitrary point in the dependency graph.
This lifetime creates an instance of the object every time it is injected into another object.
The syntax for this framework takes from and expands the syntax used by AngularJS's injector. you register a function with a name as first argument, and then an array of dependencies with the last element being the actual function you want to register as the dependency.
Allows you to register a dependency which can be instantiated by the injector and injected into other registered dependencies and the main function.
signatures
registerType(name, type, [lifetime = 'transient'], [provider]) : void registerType(name, type: [2-*], [lifetime = 'transient'], [provider]) : void
Parameters
Note: type MUST be an array if you plan to minify your code
type will be passed through the given provider before being injected. It is called passive provider because you don't need to actively inject it anywhere.Note: for now, if you plan on using the default,
transientlifetime and want to pass in a passive provider, you either need to explicitly specify'transient'or pass innullas your third parameter.
example
injector.registerType('my-test-type', function () {
this.hello = 'world'
}, 'singleton'); // only one instance will ever be created.
injector.registerType('message-logger', function (message) {
this.print = function () {
console.log('hello, ' + message.hello)
}
}); will always create a new instance on injection
Allows you to register a provider which can be instantiated by the injector and injected into other registered dependencies and the main function. Currently the only difference between this function and registerType is providers are run directly, and aren't newed. in the future, they'll receive info on the context under which they're being executed, as well as any extra parameters passed when they're called.
signatures
registerProvider(name, provider) : void registerProvider(name, provider: [2-*]) : void
Parameters
Note: type MUST be an array if you plan to minify your code
Allows you to register the provider that gets invoked when injector.run() is called. shorthand for injector.registerProvider('main', [dependencies... function () {}]);
signatures
registerMain(provider) : void registerMain(provider: [2-*]) : void
Parameters
Note: type MUST be an array if you plan to minify your code
As an alternative to array notation, you can specify your dependencies as an array in Type.$inject
example
var type = function (dependency) {
this.hello = dependency
}
type.$inject = ['dependency']
injector.registerType('my-test-type', type, 'singleton');
gets a registered type or provider. Not recommended for use other than to replace the new keyword while refactoring legacy code
signatures get(name, [context], [ad hoc dependencies]) : object
parameters
this on the invoked provider.Note:
contextis only relevant if you're planning on invoking a provider. when instantiating a type, this parameter has no use (unless the type specifies a passive provider, in which case said provider will run usingcontextasthis).
Note: type MUST be an array if you plan to minify your code
example
var logger = injector.get('message-logger');
logger.print(); // logs 'hello, world' to the console
injects all dependencies into the requested type and returns a provider for said type. Useful when instantiating the same object multiple times, for currying event callbacks, and for testing.
signatures inject(name) : object
parameters
Note: name MUST be an array if you are passing in an anonymous dependency and plan to minify your code
examples
var logger_provider = injector.inject('message-logger'),
first_logger = logger_provider(),
second_logger = logger_provider(); // much faster to instantiate, since the provider has already been built and is ready to use
first_logger.print(); // logs 'hello, world' to the console
second_logger.print(); // also logs 'hello, world' to the console
$('#some-element').click(injector.inject(function (logger) {
// this code will be run when the event is called, and logger will be available to log anything that has something to do with the event
// for now, the event object won't be available, so its use is limited in this context. This functionality will be introduced in 0.2
}));
Jasmine test:
injector.registerType('logger', function () {
this.log = function (message) {
if (dump) {
dump(message);
} else {
console.log(message);
}
}
}, 'singleton');
describe 'you can inject dependencies here and not worry about setup and teardown', () ->
beforeEach(injector.inject(function (logger) {
logger.log('starting test')
}));
afterEach(injector.inject(function (logger) {
logger.log('ending test')
}));
runs the main function.
signatures
run([context], [ad hoc dependencies]) : void
parameters
this on the main provider.Note:
contextis only relevant if you're planning on invoking a provider. when instantiating a type, this parameter has no use (unless the type specifies a passive provider, in which case said provider will run usingcontextasthis).
throws error when no main function is registered
gets the type of requested dependency. Useful for checking instanceof and testing constructors for singleton lifetime types (other uses should be avoided).
signature
getType(name) : Type|null
parameters
returns
the type or null if no type is found.
De-registers hashchange event for clearing state lifetime types.
Note: by default, InjectJS assumes your application is changing state (i.e. moving to another area of the application) every time the hash portion of the URL is changed. While this may be the default behaviour for most web sites, and is encouraged by most routers (like
Backbone'sroutes,simrou,AngularJS ng-route's default behaviour), more and more sites are adopting HTML5 pushstate, and there are quite a few SPA's which don't use the hash at all. for those sites, you should call this method and then register a handler for your particular state change that callsinjector.clearState
signature
injector.removeDefaultListener()
Clears all state lifetime instantiated types.
signature
injector.clearState()
InjectJS currently provides some features to aid in unit testing (tested with jasmine only)
registers a type as a fake.
signatures signatures are identical to registerType
removes a fake from the fakes collection.
signature
injector.removeFake(name)
parameters
removes all fakes from the fakes collection. Identical to injector.fakes = {}
signature
injector.flushFakes()
identical to inject, but injects dependencies lazily (inject is eager)
example
injector.registerType('logger', function () {
this.log = function (message) {
if (dump) {
dump(message);
} else {
console.log(message);
}
}
}, 'singleton');
describe('running tests with a harness', function () {
beforeAll(function () {
injector.registerFake('logger', function () {
this.log = sinon.spy();
});
});
afterAll(function () {
delete injector.fakes.logger;
describe('using inject'), injector.inject(function (logger) {
it('passes the actual logger type', function () {
expect(logger instanceof injector.getType('logger')).toBe true //fails because getType returns the fake
});
});
describe('using harness'), injector.harness(function (logger) {
it('passes the fake logger type', function () {
expect(logger instanceof injector.getType('logger')).toBe true //passes because logger is the fake type
});
});
});
FAQs
lightweight, small, high level dependency injector with object lifetime management
We found that inject-js demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.