What better way to start the new year than with a couple quick lessons on new JavaScript features. I'm in the process of writing a series of short lessons on the new ES2015/ES6/ES7 JavaScript features, such as the immensely helpful classes and arrow functions.
For this course, I've also written a series of quizzes that help you keep on your toes-- simply register for an account, navigate to the course page, and click the button titled "Start Taking this Course". You'll be able to take the quizzes and see your results, along with keep tabs on your reading progress. All of this is completely free and you are not added to mailing lists of any kind (the email address is required so that you can reset your password if needed)!
If you wish to receive notifications when I post new JavaScript lessons, you can always sign up for my JavaScript mailing list below. You'll never get any spam and you'll receive notifications when I post new lessons or have a new JavaScript-related tutorial or tools to show you.
In the past, I've done a lot of work with PHP and the Laravel framework. One of the coolest features about Laravel is its Inversion of Control system, which dynamically injects dependencies into your application at runtime.
This is beneficial for many reasons, including modularity, readability, and testability. With a dependency injected system, you can simply request that your application receives an instance of an object, and the DI container will do all of the work to initialize the object and all of its dependencies.
Dependency injection is a relatively advanced topic, but the benefits outweigh the cost. For example, consider the following scenario:
class Config {
public function __construct() {
}
/**
* Get the configuration item with the specified key
*/
public function get($key) {
// Get the configuration value with the specified key
return $value;
}
}
class Database {
/**
* Construct a new database instance using the specified configuration.
*/
public function __construct(Config $config) {
$this->config = $config;
}
public function connect() {
$host = $this->config->get("db.host");
$name = $this->config->get("db.name");
// ... perform the rest of the database initialization
}
}
This is an extremely trivial example, but as you can see, our database object depends on the configuration. If we simply create a single instance of the database, this isn't a problem-- we can simply create a new configuration object and pass it in the constructor. But, what if our application is composed of multiple controllers, utilities, and snippets of code that all need a database instance? Suddenly, we either have to create a large number of database objects (and consequently, a large number of configuration objects), or pass a single instance around somehow.
This is the premise of dependency injection: DI takes the premise of passing instances around, and manages this behavior for you. If we apply our example above to a dependency injected system, we actually don't ever have to explicitly create a configuration object-- our DI system does this for us.
Laravel's IoC system is great-- it's wired up throughout the framework so that most method calls end up having dependencies injected automatically, and it even uses the type hints in the method signature to determine what dependencies the method has.
For example, Laravel's IoC system would use the following method signature to pass in the current HTTP request object and a database instance, automatically:
public function getUsersController(Request $request, Database $db)
The DI pattern doesn't just apply to PHP and Laravel-- Javascript frameworks such as Angular and Aurelia have their own dependency injection systems, with the latter even using ES7-style decorators. Unfortunately, these systems are tightly coupled with the frameworks, meaning that they aren't very useful for developers that want to use them with Node.JS.
Needlepoint is a new DI framework for Javascript environments that supports the latest ES6 and ES7 features. Everything works with ES6 classes, and the dependency injection parameters are defined using the decorators ES7 proposal.
Need to learn how to use the new ES6 and ES7 features? Check out my in-development course on Modern JavaScript.
There's two key decorators that indicate how a class should be dependency injected. First is the @dependencies
decorator:
@dependencies(DependencyOne, DependencyTwo)
export default class ClassWithDependencies {
Simple pass in the classes to the decorator, and the dependencies will be injected into the constructor:
constructor(instanceOfDependencyOne, instanceOfDependencyTwo)
The second decorator is used to declare a class a singleton:
@singleton
export default class SingletonClass {
The singleton decorator indicates that no more than one instance of the class should be created in the application. If two classes declare a singleton as a dependency, only a single instance will be created which will be injected into the relevant classes that declared the singleton as a dependency.
The best way to introduce the library is to simply illustrate how it works:
/* config.js */
import {singleton} from 'needlepoint';
@singleton
export default class Config {
constructor() {
}
/**
* Get a configuration value for the specified key
*/
get(key) {
return this._data[key];
}
}
/* database.js */
import {dependencies, singleton} from 'needlepoint';
@dependencies(Config)
@singleton
export default class Database {
constructor(config) {
this.config = config;
this.configureDatabase();
}
configureDatabase() {
// ... configure the database with the current configuration instance
}
query(q) {
// ... Perform the specified query
}
}
/* index.js */
import {container} from 'needlepoint';
import Database from './database';
var db = container.resolve(Database);
db.query("SELECT * FROM users");
This example is very similar to the PHP example we previously had-- a database needs a configuration object to configure itself. As you can see, in our index.js
file we have a single call to container.resolve(Database)
which does all of the magic: the database instance is created and passed in an instance of the configuration, with both being singletons. If we were to call container.resolve(Database)
again, we would receive the exact same instance that was created the first time.
The same is true for the configuration object-- only a single instance is created for the entire application, so we might add a new "queue" class that uses the same configuration instance:
import {dependencies, singleton} from 'needlepoint';
@dependencies(Config)
@singleton
export default class Queue {
constructor(config) {
this.config = config;
this.configureQueue();
}
configureQueue() {
// ... connect to the queue and configure it with the currently
// initialized configuration instance.
}
next() {
// ... get the next item from the queue
}
}
Now, we can run container.resolve(Queue)
in our index file and get an instance of the queue class. Of course, since this is the first time that the queue is resolved (either explicitly or as a dependency of another object), it is actually instantiated. However, the constructor is passed in the previously created configuration object-- the same exact instance that was passed to the database.
Of course, this also works with more complex dependency graphs. Image having an application with a dependency graph that looks something like this:
Wiring that up manually and keeping track of everything would be a nightmare (and this isn't even close the complexity of a real application), but with Needlepoint you can simply define each class's dependencies with the decorator.
To find out more about to use Needlepoint and dependency injection in your Javascript applications, you can visit the GitHub repository for the project. The entire thing is open source and written using the latest ES6 features, meaning Babel is required for most Javascript environments.
You can use Needlepoint in your Node.JS applications fairly easily. Simply install it with NPM and ensure you have Babel installed and configured:
npm install --save babel needlepoint
And, in your Javascript:
require('babel/register')({
optional: ['es7.decorators'],
// A lot of NPM modules are precompiled so Babel ignores node_modules/*
// by default, but Needlepoint is NOT pre-compiled so we change the ignore
// rules to ignore everything *except* Needlepoint.
ignore: /node_modules\/(?!needlepoint)/
});
require('app.js');
/* app.js */
import {container} from 'needlepoint';
// Use Needlepoint here
There's still a ton of things left to do, but you can always help out by submitting a pull request or even just filing an issue.
Though I am an Angular JS type of person, I regularly follow other JavaScript frameworks to keep up with the rest of the world. React, along with the Flux design pattern, is a relatively popular alternative to the Angular ecosystem. However, React+Flux is extremely different from other frameworks-- for starters, React isn't actually a full JavaScript framework by itself, and Flux isn't actually a thing: it's a type of architecture. Tero Parviainen wrote an amazing introduction to the Redux+React ecosystem with an overview of how not only the two libraries themselves work, but conceptually how they fit together and why the Flux architecture and immutability makes sense in a web application.
For more information on Needlepoint-- my JavaScript dependency injection system--you can take a look at my introductory blog post or the README file on GitHub.
I have updated Needlepoint to version 1.0.5 in NPM. There's a couple changes, none of which should break if you use an older version and upgrade to this version.
This means you don't have to have an entry for the library in your Babel ignore configuration. My first blog post mentions that your Babel configuration should look something like this:
require('babel/register')({
optional: ['es7.decorators'],
// A lot of NPM modules are precompiled so Babel ignores node_modules/*
// by default, but Needlepoint is NOT pre-compiled so we change the ignore
// rules to ignore everything *except* Needlepoint.
ignore: /node_modules\/(?!needlepoint)/
});
The ignore
property is no longer required.
I also updated the library and instructions to use Babel 6. If you are using Needlepoint with Babel 6, you will have to use the new presets property for your Babel configuration and add the babel-plugin-transform-decorators-legacy
plugin. Babel 6 removed the old ES7 decorator functionality and the contributors are re-writing it. However, the legacy plugin enables the decorator functionality now.
To do so, you must install the plugin's NPM package:
$ npm install --save babel-plugin-transform-decorators-legacy
Then, once you install the plugin package, you can add it to your Babel configuration:
require('babel/register')({
presets: ['es2015', 'stage-0', 'stage-1'],
plugins: ['babel-plugin-transform-decorators-legacy']
});
As you can see in the sample above, I've also included the es2015
, stage-0
, and stage-1
presets for Babel. You can adjust these as needed for your application.