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 componentKey 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.
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)
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-registrationType 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:
Container Hierarchy - How containers inherit and override registrations
Service Lifetimes - Singleton vs Transient vs Scoped behavior
Injection Methods - resolve() vs @inject vs static inject
Registration Flow - Auto-registration vs manual registration
Resolver Pipeline - How resolvers modify dependency resolution
Property vs Constructor - Comparing injection styles
Interface Tokens - How
DI.createInterface()works
For more details, see the complete DI Documentation.
Last updated
Was this helpful?