Streamlining Dependency Injection in TypeScript: A Look at `singleton-factory-ts`

Published: (February 18, 2026 at 05:30 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

The Core Concept: Singleton and SingletonFactory

At the heart of singleton-factory-ts are two main components:

  • An abstract Singleton base class that your services will extend.
  • A SingletonFactory (itself a singleton) responsible for resolving dependencies, managing the cache, and detecting architectural flaws like circular dependencies.

Instead of registering services manually in a container, classes declare their own dependencies using a static getter.

Clean, Declarative Usage

The library’s developer experience is straightforward. Below is a typical definition of singletons and their relationships:

import { Singleton, SingletonClassType } from 'singleton-factory-ts';

// A base singleton with no dependencies
class S1 extends Singleton {
  doSomething() {
    console.log("S1 is working!");
  }
}

// A singleton that depends on S1
class S2 extends Singleton {
  // 1. Declare dependencies declaratively
  static get Dependencies(): [SingletonClassType] {
    return [S1];
  }

  // 2. The factory will automatically inject S1 here
  constructor(protected _s1: S1) {
    super();
  }

  execute() {
    this._s1.doSomething();
  }
}

// 3. Access the instance effortlessly
const s2 = S2.instance;
s2.execute();

When you access S2.instance, the base Singleton class intercepts the call and delegates to the SingletonFactory. The factory reads the Dependencies array, recursively builds (or retrieves) the required instances, and injects them into S2’s constructor.

Under the Hood: What Makes it Robust?

Smart Token Caching

Every class extending Singleton automatically receives an InjectorToken generated with Symbol.for(this.className). The SingletonFactory maintains a Map (_singletonCache) of these tokens. If an instance already exists, it returns the cached version, guaranteeing true singleton behavior across the application.

Circular Dependency Detection

Circular dependencies (e.g., Service A depends on Service B, which depends on Service A) can cause infinite loops. During instantiation, the factory adds the class’s InjectorToken to an _initializingClasses set. If it encounters a token already present in this set while resolving the dependency tree, it throws a descriptive error: Circular dependency detected for token….

Custom Instantiation via create Method

Sometimes the standard new Constructor(...) pattern isn’t sufficient—perhaps you need async operations or custom factory logic. The library includes a respondsToSelector polyfill (inspired by Objective‑C) on Object and Object.prototype. When the SingletonFactory resolves dependencies, it checks whether the class respondsToSelector("create"). If so, it calls Class.create!(...deps) instead of the constructor, providing great flexibility.

Real-World Adoption: @greeneyesai/api-utils

The pattern isn’t just experimental; it’s actively used in production. @greeneyesai/api-utils adopts a variant of this singleton‑factory architecture to manage its internal lifecycle and service dependencies. By leveraging the pattern, the library ensures that core clients, configuration managers, and logging services are instantiated lazily, share state reliably, and enforce dependency contracts without pulling in a heavyweight DI framework.

Conclusion

For developers aiming to decouple TypeScript classes while preserving strict type safety and modularity, singleton-factory-ts offers a concise blueprint. By combining a declarative static Dependencies array with a smart centralized factory, it delivers enterprise‑level DI capabilities in a lightweight, readable package.

Whether you import the library directly or adapt its architectural variant for tools like @greeneyesai/api-utils, the singleton factory pattern remains a powerful addition to any TypeScript developer’s toolkit.

https://github.com/arpad1337/singleton-factory-ts

0 views
Back to Blog

Related posts

Read more »

Procrastination in disguise

Introduction Recently I started exploring TypeScript. The initial excitement of gaining new skills soon gave way to feelings of overwhelm and confusion—especia...