How do components inject other components in EK9?
← Dependency Injection · Ref: Q232
Components inject other components using the '!' suffix on fields, exactly like programs inject components. The compiler resolves these transitive chains at compile time.
FIELD INJECTION IN COMPONENTS
A concrete component can declare injection fields:
ConcreteService is AbstractService repo as Repository! override doWork() <- rtn as String: repo.find(1)
The '!' on 'repo' tells the compiler this field is injected, not constructed locally.
TRANSITIVE CHAINS
Multi-layer dependency chains are common:
Repository depends on nothing Service depends on Repository Controller depends on Service
The compiler traces the entire chain and verifies every link is registered.
REGISTRATION ORDER
Register in dependency order (leaves first, roots last):
register ConcreteRepo() as Repository register ConcreteService() as Service register ConcreteController() as Controller
ALL COMPILE-TIME
Every field injection in every component is verified before code runs. The compiler sees the full dependency graph across all components and programs.
See Q228 for registration ordering. See Q111 for component basics. See Q227 for compile-time validation.
Example
defines module qa.di.chains defines component // === LAYER 1: Repository (no dependencies) === Repository as abstract find() as abstract -> id as Integer <- result as String? default operator ? InMemoryRepo is Repository override find() -> id as Integer <- result as String: "Record-" + $id default operator ? // === LAYER 2: Service (depends on Repository) === DataService as abstract process() as abstract -> id as Integer <- output as String? default operator ? DefaultDataService is DataService repo as Repository! override process() -> id as Integer <- output <- String() raw <- repo.find(id) output: "Processed: " + raw default operator ? // === LAYER 3: Controller (depends on Service) === ApiController as abstract handle() as abstract -> id as Integer <- response as String? default operator ? DefaultApiController is ApiController service as DataService! override handle() -> id as Integer <- response <- String() result <- service.process(id) response: "Response: " + result default operator ? defines application ChainedApp // Register in dependency order: leaves first register InMemoryRepo() as Repository register DefaultDataService() as DataService register DefaultApiController() as ApiController defines program ComponentChainDemo() with application of ChainedApp stdout <- Stdout() // === THREE-LAYER INJECTION CHAIN === controller as ApiController! response <- controller.handle(42) stdout.println(response) stdout.println("Repository -> Service -> Controller chain resolved at compile time")
Common mistakes
E08150 — Injection fields must use abstract types. Concrete types like InMemoryRepo cannot be injection targets because DI resolves through abstract-to-concrete registration. See ek9 -h E08150 for details.
Incorrect:
repo as InMemoryRepo!
Correct:
repo as Repository!
Other ways to ask this
- Can EK9 components depend on other components?
- How do transitive dependency chains work in EK9?
- How does field injection work between components?
Coming from another language?
Java Spring: @Autowired field injection between @Component classes, resolved at runtime via container. Guice: @Inject field injection, resolved at Injector creation. .NET: constructor injection between registered services. Python: no built-in component injection. Go: manual struct composition. Rust: no built-in DI. EK9: '!' field injection between components, transitive chains resolved at compile time, registration order enforced.
Keywords: transitive, injection, component, field, depend, service, controller, layer, chain, repository, inject