Visual Diagrams

Visual explanations of Aurelia 2's dependency injection system.

Table of Contents


1. Container Hierarchy and Resolution

How containers inherit and override registrations:

ROOT CONTAINER (Application Level)
══════════════════════════════════════════════════════

┌────────────────────────────────────────────────┐
│ Root Container                                 │
│                                                │
│ Registrations:                                 │
│  • ILogger → ConsoleLogger (singleton)        │
│  • IAppConfig → {...} (instance)              │
│  • IApiClient → ApiClient (singleton)         │
└────────────────────┬───────────────────────────┘

          ┌──────────┴──────────┐
          ↓                     ↓
┌─────────────────────┐ ┌──────────────────────┐
│ Feature Container 1 │ │ Feature Container 2  │
│ (Admin Module)      │ │ (User Module)        │
│                     │ │                      │
│ Inherits:           │ │ Inherits:            │
│  • ILogger ✓        │ │  • ILogger ✓         │
│  • IAppConfig ✓     │ │  • IAppConfig ✓      │
│  • IApiClient ✓     │ │  • IApiClient ✓      │
│                     │ │                      │
│ Overrides:          │ │ Adds:                │
│  • ILogger →        │ │  • IUserService →    │
│    AdminLogger      │ │    UserService       │
│                     │ │  • IAuthGuard →      │
│ Adds:               │ │    UserAuthGuard     │
│  • IAdminService →  │ │                      │
│    AdminService     │ │                      │
└─────────────────────┘ └──────────────────────┘
          ↓                     ↓
     ┌─────────┐          ┌──────────┐
     │Component│          │Component │
     │  asks   │          │  asks    │
     │  for    │          │  for     │
     │ ILogger │          │ ILogger  │
     └────┬────┘          └────┬─────┘
          │                    │
          ↓                    ↓
    AdminLogger          ConsoleLogger
  (from Feature 1)        (from Root)


RESOLUTION ALGORITHM
════════════════════════════════════════════════════════

Component requests: IUserService

Step 1: Check current container
┌────────────────────────────────────┐
│ Current: Feature Container 2       │
│ Has IUserService? YES ✓            │
│ → Return UserService instance      │
└────────────────────────────────────┘
         Resolution complete!


Component requests: IApiClient

Step 1: Check current container
┌────────────────────────────────────┐
│ Current: Feature Container 2       │
│ Has IApiClient? NO ✗               │
└──────────────┬─────────────────────┘

Step 2: Check parent container
┌────────────────────────────────────┐
│ Parent: Root Container             │
│ Has IApiClient? YES ✓              │
│ → Return ApiClient instance        │
└────────────────────────────────────┘
         Resolution complete!


Component requests: IUnknownService

Step 1: Check current container
┌────────────────────────────────────┐
│ Current: Feature Container         │
│ Has IUnknownService? NO ✗          │
└──────────────┬─────────────────────┘

Step 2: Check parent container
┌────────────────────────────────────┐
│ Parent: Root Container             │
│ Has IUnknownService? NO ✗          │
└──────────────┬─────────────────────┘

Step 3: No more parents
┌────────────────────────────────────┐
│ ❌ ERROR: Cannot resolve key       │
│    IUnknownService                 │
└────────────────────────────────────┘


CREATING CHILD CONTAINERS
════════════════════════════════════════════════════════

const root = DI.createContainer();
root.register(
  Registration.singleton(ILogger, ConsoleLogger),
  Registration.instance(IConfig, appConfig)
);

const feature = root.createChild();
feature.register(
  // Override logger for this feature
  Registration.singleton(ILogger, FeatureLogger),
  // Add feature-specific service
  Registration.singleton(IFeatureService, FeatureService)
);

// Resolution:
root.get(ILogger);     // → ConsoleLogger
feature.get(ILogger);  // → FeatureLogger (override)
feature.get(IConfig);  // → appConfig (inherited from root)


USE CASES FOR CHILD CONTAINERS
════════════════════════════════════════════════════════

1. Feature Modules
   ├─ Override services for specific features
   └─ Isolate feature-specific dependencies

2. Testing
   ├─ Create test container with mocks
   └─ Don't pollute root container

3. Multi-Tenancy
   ├─ Each tenant gets own container
   └─ Override config/services per tenant

4. Component Scoping
   ├─ Component creates child container
   └─ Scoped services destroyed with component

Key Points:

  • Children inherit all parent registrations

  • Children can override parent registrations

  • Resolution walks up the chain

  • First match wins

Container hierarchy documentation →


2. Service Lifetimes

How different lifetime registrations behave:

Decision Guide:

  • Singleton: When you need shared state or expensive resources

  • Transient: When each consumer needs independent instances

  • Scoped: When you need per-component or per-feature instances

Service lifetimes documentation →


3. Injection Methods Comparison

Three ways to inject dependencies:

Recommendation: Use resolve() for new Aurelia 2 code. It's cleaner, more flexible, and works better with modern JavaScript/TypeScript.

Injection patterns documentation →


4. Registration Flow

How services get registered in the container:

Recommendation: Use DI.createInterface() with auto-registration for services. Use manual registration for configuration, instances, and special cases.

Registration documentation →


5. Resolver Pipeline

How resolvers modify dependency resolution:

Key Points:

  • Resolvers wrap keys to modify behavior

  • Most resolve without resolvers (simplest/fastest)

  • Use resolvers for special cases only

  • Can chain resolvers (but rarely needed)

Resolvers documentation →


6. Property vs Constructor Injection

Comparing the two injection styles:

Recommendation: Use property injection with resolve() for Aurelia 2 projects. It's simpler, cleaner, and works better with modern JavaScript patterns.

Property injection documentation →


7. Interface Token Creation

How DI.createInterface() works:

Key Takeaways:

  • DI.createInterface() creates a Symbol token + auto-registration

  • Type alias copies the shape of the implementation class

  • Auto-registration is lazy (happens on first request)

  • Friendly names improve debugging experience

Creating services documentation →


Summary

These diagrams cover the core architectural concepts of Aurelia 2's dependency injection:

  1. Container Hierarchy - How containers inherit and override registrations

  2. Service Lifetimes - Singleton vs Transient vs Scoped behavior

  3. Injection Methods - resolve() vs @inject vs static inject

  4. Registration Flow - Auto-registration vs manual registration

  5. Resolver Pipeline - How resolvers modify dependency resolution

  6. Property vs Constructor - Comparing injection styles

  7. Interface Tokens - How DI.createInterface() works

For more details, see the complete DI Documentation.

Last updated

Was this helpful?