@ylcnfrht/ioc is modeled on Microsoft.Extensions.DependencyInjection: a ServiceCollection you register into, a
ServiceProvider you resolve from, scopes, the same three lifetimes and the same validation options. If you know
ASP.NET Core’s container, you know this one; the differences come from JavaScript having no runtime types and from
resolution being async.
Dependencies are listed, not reflected. TypeScript types are erased at runtime, so the container can’t read
constructor parameter types the way .NET does. You pass one token per parameter, in order. In return the compiler checks
the list against the constructor, with no decorators or reflect-metadata:
Registers a scoped service: one instance per scope. Same forms as
addSingleton
.
addScoped(
classReports
Reports, [
constLogger:InjectionToken<Logger>
Logger,
constClock:InjectionToken<Clock>
Clock]); // wrong order: compile error
Error ts(2769) ― No overload matches this call.
Overload 3 of 5, '(token: Token<Reports>, implementation: Newable<NoInfer<Reports>, []>): ServiceCollection', gave the following error.
Argument of type '(InjectionToken<Clock> | InjectionToken<Logger>)[]' is not assignable to parameter of type 'Newable<NoInfer<Reports>, []>'.
Type '(InjectionToken<Clock> | InjectionToken<Logger>)[]' provides no match for the signature 'new (): NoInfer<Reports>'.
Interfaces need tokens.IFoo doesn’t exist at runtime. Use createToken<IFoo>('IFoo'), a class (abstract classes
work), a string or a symbol. See Tokens.
Resolution is async. Every get* method returns a Promise, because factories and onInit may be async. Resolve
at the edges (a request handler, main) and pass instances down through constructors, as in .NET.
Circular dependencies work. .NET throws on a cycle. Here, A → B → A resolves for every lifetime: B gets a proxy
of A that becomes usable once A’s constructor returns. Using the partner inside a constructor still throws a
CircularDependencyError. See Circular dependencies.
No open generics. There is no AddSingleton(typeof(IRepository<>), typeof(Repository<>)). Register each closed
type with its own token, or use a factory.
No constructor selection. One constructor per class, one dependency list per registration. Optional dependencies
can be resolved in a factory with getService, which returns undefined when nothing is registered.
Transients from the root are disposed with the root. As in .NET, a disposable transient resolved from the root
provider is kept until the root is disposed. Resolve disposable transients from a scope.