Quick Reference ("How Do I...")

Your task-focused guide to Aurelia 2's dependency injection system.

Table of Contents


Getting Started

How do I inject a service into my component?

Property injection with resolve():

Constructor injection with @inject:

Injection patterns →

When should I use resolve() vs @inject?

Method
Use When
Benefits

resolve()

Prefer property/field injection or want to avoid decorators

Cleaner syntax, works with inheritance, no metadata required

@inject

Prefer explicit constructor injection and immutable dependencies

Constructor clearly documents required services; great for unit tests

static inject

Avoiding decorators entirely

No decorator metadata required

Choosing your style:

  • Use resolve() when you want lightweight property injection, when inheriting from framework base classes, or when decorators/emitDecoratorMetadata are unavailable.

  • Use @inject when you want constructor parameters to stay read-only, when your team prefers explicit signatures, or when you need to support tooling that analyzes constructor arguments.

  • static inject remains available for teams that disable decorators entirely.

Property injection example:

Understanding resolve() →

How do I create a service?

Heads up: default implementations registered inside DI.createInterface are only consulted when the container has registered the token itself. Resolvers such as optional(IUserService) or resolve(all(IUserService)) will return undefined until you run container.register(IUserService). This matches the runtime behavior in packages/kernel/src/di.ts (container.register(MyStr); comment) and avoids surprising allocations when optional dependencies are missing.

Creating services →


Injecting Dependencies

How do I inject multiple services?

Using resolve():

You can also resolve multiple keys in one call and destructure the tuple result:

This uses the runtime helper defined in packages/kernel/src/di.container.ts to pull each token from the currently active container, which keeps the code concise when a class needs several collaborators.

Constructor injection with @inject:

Using static inject:

How do I make a dependency optional?

Constructor injection with @inject:

Optional dependencies →

How do I inject all implementations of a service?

All resolver →

How do I lazy-load a service?

Lazy resolver →


Creating Services

Which registration helper should I use?

Registration exposes more than singleton vs transient (see packages/kernel/src/di.registration.ts). Pick the helper that matches your lifetime and creation strategy:

Helper
What it does
Typical use

Registration.instance(key, value)

Always returns the provided object.

App configuration, external SDK singletons.

Registration.singleton(key, Type)

Lazily creates one instance per container.

Services with shared state (API clients, stores).

Registration.transient(key, Type)

Creates a new instance every time.

Utilities or disposable types.

Registration.callback(key, fn)

Runs the callback on every resolution.

Values that depend on runtime arguments or container state.

Registration.cachedCallback(key, fn)

Runs the callback once per container then caches the result.

Expensive factories that still need manual control of construction.

Registration.aliasTo(original, alias)

Exposes an existing registration under another key.

Provide the same implementation for multiple tokens (mock vs real).

Registration.defer(extension, data)

Defers resource registration until a dedicated registry handles it.

Template preprocessors and conventions (used by the HTML preprocessor for CSS modules).

Combine these helpers with Aurelia.register(...) or container-local register(...) calls wherever you wire up services.

How do I create a simple service?

Service creation →

How do I inject dependencies into my service?

Using resolve() (recommended):

Constructor injection with @inject:

How do I create a service without auto-registration?

Then register manually:

Manual registration →

How do I use a class directly without an interface?

Note: For better testability and decoupling, prefer using DI.createInterface().


Service Lifetimes

What are the different service lifetimes?

Lifetime
Description
Use For

Singleton

One instance per container

Services with shared state, API clients, configuration

Transient

New instance every injection

Stateless utilities, factories, disposable objects

How do I create a singleton service?

Why singleton? Auth state should be shared across the entire application.

Singleton registration →

How do I create a transient service?

Why transient? Each logger should have its own timestamp.

How do I force a new instance regardless of registration?

Or with constructor:

newInstanceOf resolver →


Advanced Injection

How do I inject into properties (not constructor)?

Why property injection?

  • Works with inheritance (constructor calls can be tricky)

  • Cleaner when extending framework classes

  • Modern with resolve()

Property injection →

How do I create a factory for my service?

Factory resolver →

How do I publish my own scoped context?

Use InstanceProvider when you need to expose a value that should be resolved exactly as-is by descendants. Aurelia uses the same primitive to wire controllers, hydration contexts, and router state into child scopes (see packages/runtime-html/src/templating/controller.ts and packages/router/src/route-context.ts).

  • Call registerResolver with the provider so resolve(IFeatureContext) returns the prepared value.

  • The optional third argument (true) tells the container to dispose the provider automatically when the scope goes away.

  • You can replace the value later via provider.prepare(newValue) to update the scoped instance.

How do I scope a service to a component?

Scoped instances →

How do I inject the last registered instance?

Useful when you have multiple registrations and want the override:

Last resolver →

How do I plug in custom factories or transformers?

When the built-in lifetime helpers are not enough, register your own factory for a key. This is how Aurelia supports interface tokens whose concrete type needs extra inputs (see packages/tests/src/1-kernel/di.get.spec.ts for working examples).

  • container.registerFactory(key, factory) ties a token to a custom factory. For interface tokens you can cast the interface to Constructable exactly like the runtime tests do. Inside construct you can call container.getFactory(SomeClass).construct(...) to reuse Aurelia's dependency calculation.

  • container.registerTransformer(key, transformer) lets you wrap or mutate instances after construction—perfect for logging proxies, caching, or feature flags. The container implementation keeps the transformer list per key (packages/kernel/src/di.container.ts:305-330, 664-668).

CachedReportService in the example is any decorator you want to apply—it simply receives the just-created ReportService and returns the wrapped instance you want the container to hand out.

If you simply need one-off construction hooks, prefer Registration.callback/Registration.cachedCallback. Reach for registerFactory only when you need full control over how and when instances are created.


Container Management

How do I configure a container?

DI.createContainer() accepts an optional configuration object that maps directly to the runtime IContainerConfiguration (packages/kernel/src/di.container.ts). Use it to change inheritance and default registration strategy:

  • inheritParentResources copies the parent container’s resource registrations (custom elements, attributes, value converters, etc.) into the child. Shadow DOM features or micro-frontends can opt in so they see exactly what the parent registered without falling back to the app root.

  • defaultResolver controls how plain classes are auto-registered when you first resolve them. DefaultResolver.singleton (the default) caches one instance per container; switching to DefaultResolver.transient ensures every resolve(SomeClass) call returns a fresh instance. If you want to force explicit registrations, use DefaultResolver.none so the container throws whenever you resolve an unknown class (great for large teams that prefer auditability).

Child containers can also pass { inheritParentResources: true } to createChild(...) for one-off scopes that need the same behavior.

How do I create a custom container?

Container creation →

How do I create a child container?

Use cases:

  • Feature modules with their own services

  • Testing with mocked dependencies

  • Multi-tenant applications

Container hierarchy →

How do I register multiple implementations?

How do I register instances directly?

Use cases:

  • Configuration objects

  • External libraries

  • Pre-initialized objects

Registration types →

How do I check or override an existing registration?

The container exposes inspection APIs so you can detect whether something is registered and optionally swap it out at runtime (see IContainer.has/getResolver in packages/kernel/src/di.ts).

  • container.has(key, searchAncestors) lets you check whether a key exists locally or anywhere up the parent chain.

  • container.getResolver(key, /*autoRegister*/ false) gives you the current resolver without triggering auto-registration, so you can inspect or replace it.

  • registerResolver accepts any IResolver (including InstanceProvider) and an optional isDisposable flag to clean up automatically.

How do I deregister a service?

Warning: Use sparingly - can cause issues if other services depend on it.

Deregistering →


Testing

How do I mock services for testing?

How do I test a service with dependencies?

Testing patterns →


Common Patterns

How do I create a configuration service?

Use in services:

How do I create a service with initialization logic?

Initialize on app start:

How do I implement the Service Locator pattern?

Note: Prefer constructor/property injection over service locator for better testability.

How do I create a plugin system?


Troubleshooting

Error: "Cannot resolve key"

Problem: DI container can't find a registration for your service.

Solutions:

  1. Forgot to create interface token:

  1. Interface not imported/registered:

  1. Manual registration missing:

Error: "Cyclic dependency"

Problem: Service A depends on Service B, which depends on Service A.

Solution 1 - Use lazy injection:

Solution 2 - Refactor to break cycle:

resolve() returns undefined

Problem: Using resolve() outside of DI context.

Solutions:

  1. In components: resolve() works anywhere

  2. In plain classes: Must be injected or instantiated by DI

  3. In static methods: Can't use resolve() - use injection

TypeScript errors with resolve()

Problem: Type inference not working with resolve().

Solution: Explicitly specify type:

Service isn't a singleton (getting different instances)

Problem: Service registered as transient or created with new.

Solutions:

  1. Check registration:

  1. Don't create with new:

Constructor injection order mismatch

Problem: Parameters don't match @inject decorator order.


Complete Documentation

Last updated

Was this helpful?