npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

iocify

v0.1.1

Published

Inversion of Control for JS

Downloads

4

Readme

iocify - dependency injection

A lightweight, extensible dependency injection container for JavaScript powered by Aurelia's dependency-injection and aurelia-property-injection.

This is a bundled version for our needs and is fully compatible with Babel and it's legacy decorators.

Installation

$ npm install iocify --save

Note: You may need to install also babel-polyfill.

Injection

Currently we are supporting two types of injection - constructor & property injection.

Constructor injection


class Logger {}
class Service {}

@inject(Logger, Service)
class App {
  constructor(logger, service) {
    this.logger = logger;
    this.service = service;
  }
}

let container = new Container();
let app = container.get(App);


// app.logger instanceof Logger -> true
// app.service instanceof Service -> true

Property injection

class Logger {}
class Service {}

class App {
  
  @inject(Logger)
  logger = null;
  
  @inject(Service)
  service = null;
}

let container = new Container();
let app = container.get(App);


// app.logger instanceof Logger -> true
// app.service instanceof Service -> true

Object Lifetime, Child Containers and Default Behavior

Each object created by the dependency injection container has a "lifetime". There are three lifetime behaviors that are typical:

  • Container Singleton - A singleton class, A, is instantiated when it is first needed by the DI container. The container then holds a reference to class A's instance so that even if no other objects reference it, the container will keep it in memory. When any other class needs to inject A, the container will return the exact same instance. Thus, the instance of A has its lifetime connected to the container instance. It will not be garbage collected until the container itself is disposed and no other classes hold a reference to it.
  • Application Singleton - It's possible to have child DI containers created from parent containers. Each of these child containers inherits the services of the parent, but can override them with their own registrations. Every application has a root DI container from which all classes and child containers are created. An application singleton is just like a container singleton, except that the instance is referenced by the root DI container in the application. This means that the root and all child containers will return the same singleton instance, provided that a child container doesn't explicitly override it with its own registration.
  • Transient - Any DI container can create transient instances. These instances are created each time they are needed. The container holds no references to them and always creates a new instance for each request.

Any class can be registered in a container as singleton or transient. What does this process look like? Let's look at a couple of examples to see how things work in practice.

Example 1 - Root Container Resolution

Imagine that we have a single instance of Container called root. If a developer invokes root.get(A) to resolve an instance of A, the root will first check to see if it has a Resolver for A. If one is found, the Resolver is used to get the instance, which is then returned to the developer. If one is not found, the container will auto-register a Resolver for A. This resolver is configured with a singleton lifetime behavior. Immediately after auto-registration, the Resolver is used to get the instance of A which is returned to the developer. Subsequent calls to root.get(A) will now immediately find a Resolver for A which will return the singleton instance.

Example 2 - Child Container Resolution

Now, imagine that we have a Container named root and we call root.createChild() to create a child container named child. Then, we invoke child.get(A) to resolve an instance of A. What will happen? First, child checks for a Resolver for A. If none is found, then it calls get(A) on its parent which is the root container from which it was created. root then checks to see if it has a Resolver. If not, it auto-registers A in root and then immediately calls the Resolver to get an instance of A.

Example 3 - Child Container Resolution with Override

Let's start with an instance of Container named root. We will then call root.createChild() to create a child container named child. Next we will call child.createChild() to create a grandchild container from it named grandchild. Finally, we'll call child.registerSingleton(A, A). What happens when we call grandchild.get(A)? First, grandchild checks for a Resolver. Since it doesn't find one, it delegates to its parent which is the child from which it was created. child then checks for a Resolver. Since child.registerSingleton(A, A) was called on child this means that child will have a Resolver for A. At this point child's resolver is used to get an instance of A which is returned to the developer.

As you can see from these examples, the Container basically walks its hierarchy until it either finds a Resolver or reaches the root. If no Resolver is found in the root, it auto-registers the class as a singleton in the root. This means that all auto-registered classes are application-wide singletons, unless they are overriden by a child container.

Explicit Configuration

For the most part, DI will do what you want with object lifetime. However, you may desire to change the behavior of individual classes for the specific needs of your application. This is easy to do by either directly using the Container API or by decorating your class with a Registration.

The Container Registration API

The usual way to configure a class's lifetime is to use the Container API directly. Typically, you will want to do this configuration up-front in your application's main configure method.

Here's a survey of the registration APIs you have available through a Container instance:

  • container.registerSingleton(key: any, fn?: Function): void - This method allows you to register a class as a singleton. This is the default, as discussed above, so there's rarely a reason to call this method. It is provided in the API for completeness. When calling, provide the key that will be used to look up the singleton and the class which should be used. It's common for the key and class to be the same. If they are the same, then only the key needs to be provided. Here are some examples:
    • container.registerSingleton(History, BrowserHistory);
    • container.registerSingleton(HttpClient);
  • container.registerTransient(key: any, fn?: Function): void - This method allows you to register a class as transient. This means that every time the container is asked for the key, it will return a brand new instance of the class. As with the singleton behavior, the key is requried but the class is optional. If left off, the key will be treated as the class to be instantiated. Here's an example of using transient registration:
    • container.registerTransient(LinkHandler, DefaultLinkHandler);
  • container.registerInstance(key: any, instance?: any): void - If you already have an existing instance, you can add that to the container with this method. You just need to pick a key that the instance will be retrievable by. If no key is provided then the key becomes the instance.
  • container.registerHandler(key: any, handler: (container?: Container, key?: any, resolver?: Resolver) => any): void - In addition to simply declaring behaviors, you can also provide a custom function (a handler) that will respond any time the container is queried for the key. This custom handler has access to the container instance, the key and the internal resolver which stores the handler. This enables just about any sort of custom lifetime to be implemented by supplying a custom function. Here's an example:
    • container.registerHandler('Foo', () => new Bar());

Info: Registration Keys All registration APIs take a key. This key is typically the class itself (for convenience). However, the key can be any type, including strings and objects. This is possible because DI implementation uses a Map object to correlate a key to a Resolver. When using class-oriented registration APIs, if the key is not a class, you must provide the class to be created as the second argument to the API call.

Registration Decorators

As an alternative to explicitly registering types with the container, you can rely on auto-registration, but specify the auto-registration behavior you desire, overriding the default container-root-singleton behavior. To provide auto-registration behavior, you simply decorate your type with an auto-registration decorator. What follows is a basic explanation of built-in registration decorators:

  • transient() - Simply decorate your class with transient() and when it's requested from the container, a new instance will be created for each request.
  • singleton(overrideChild?:boolean) - Normally, types are auto-registered as singletons in the root container. So, why do we provide this decorator? This decorator allows you to specify true as an argument to indicate that the singleton should be registered not in the root container, but in the immediate container to which the initial request was issued.

Warning: Registration Decorator Usage At present, the Decorators spec allows for decorators to use parens or not depending on whether or not the decorator requires arguments. This means that decorator invocation is dependent on how the decorator was implemented internally, which can be confusing from time to time. As a result of the way that the registration decorators are implemented, you must use them with parens.

Resolvers

As mentioned above, the DI container uses Resolvers internally to provide all instances. When explicitly configuring the container, you are actually specifying what Resolver should be associated with a particular lookup key.

You can define the type of Resolver via the following decorators:

  • injct(key)
  • lazy(key)
  • optional(key)

inject(key)

Inject the dependency from the container.

lazy(key)

Specifies the dependency should be lazy loaded. It will inject a method that returns the desired instance.

class Logger {}

class App {
  @lazy(Logger)
  getLogger = null;
}

let container = new Container();
let app = container.get(App);

// app.getLogger() instanceof Logger -> true

optional(key)

Specifies the dependency as optional. Tries to find the desired instance in your container and returns it when found. Otherwise null will be used as the value.

class Logger {}
class Service {}

class App {
  
  @optional(Logger)
  logger = null;
  
  @optional(Service)
  service = null;
}

let container = new Container();
constainer.registerSingleton(Logger);
let app = container.get(App);


// app.logger instanceof Logger -> true
// app.service === null -> true

License

MIT