Resolvers
Resolvers in Aurelia's DI system provide advanced control over instantiating and injecting dependencies. They allow you to modify the resolution behavior for specific dependencies, enabling patterns like lazy loading, optional dependencies, and factory-based instantiation.
Built-in Resolvers
Aurelia offers several built-in resolvers that can be used to customize dependency injection:
LazyPurpose: Injects a function that lazily retrieves the dependency when invoked.
Usage:
import { inject, Lazy } from 'aurelia-framework'; import { HttpClient } from 'aurelia-fetch-client'; @inject(Lazy.of(HttpClient)) export class CustomerDetail { constructor(getHttpClient) { this.getHttpClient = getHttpClient; } fetchData() { const httpClient = this.getHttpClient(); // Use httpClient... } }Explanation: Instead of injecting an instance of
HttpClientdirectly, a functiongetHttpClientis injected. This function can be called to retrieve theHttpClientinstance when needed.
AllPurpose: Injects an array of all registered instances matching the provided key.
Usage:
import { inject, All } from 'aurelia-framework'; import { Plugin } from './plugin'; @inject(All.of(Plugin)) export class PluginManager { constructor(plugins) { this.plugins = plugins; } initialize() { this.plugins.forEach(plugin => plugin.init()); } }Explanation: Injects all instances registered under the
Pluginkey as an array, allowing you to manage multiple plugins collectively.
OptionalPurpose: Injects an instance if it exists; otherwise, injects
null.Usage:
import { inject, Optional } from 'aurelia-framework'; import { LoggedInUser } from './user-service'; @inject(Optional.of(LoggedInUser)) export class UserProfile { constructor(user) { this.user = user; } display() { if (this.user) { // Display user information } else { // Prompt for login } } }Explanation: The
LoggedInUserdependency is injected only if it has been previously registered. If not,nullis injected, allowing for optional usage.
ParentPurpose: Starts dependency resolution from the parent container instead of the current one.
Usage:
import { inject, Parent } from 'aurelia-framework'; import { MyCustomElement } from './my-custom-element'; @inject(Parent.of(MyCustomElement)) export class ChildComponent { constructor(parentElement) { this.parentElement = parentElement; } }Explanation: Instead of resolving from the current container, the resolver starts from the parent container, allowing access to dependencies defined higher up in the container hierarchy.
FactoryPurpose: Injects a factory function that can create instances of the dependency, allowing for dynamic data passing.
Usage:
import { inject, Factory } from 'aurelia-framework'; import { CustomClass } from './custom-class'; @inject(Factory.of(CustomClass)) export class Component { constructor(createCustomClass) { this.createCustomClass = createCustomClass; } createInstance(data) { const instance = this.createCustomClass(data); // Use the instance... } }Explanation: A factory function
createCustomClassis injected, which can be called with specific data to create instances ofCustomClasson demand.
NewInstancePurpose: Injects a new dependency instance each time, ignoring any existing instances in the container.
Usage:
import { inject, NewInstance } from 'aurelia-framework'; import { CustomClass } from './custom-class'; @inject(NewInstance.of(CustomClass)) export class AnotherComponent { constructor(customClassInstance) { this.customClassInstance = customClassInstance; } }Explanation: Each time
AnotherComponentis instantiated, a new instance ofCustomClassis created, regardless of any existing instances in the container.
Using Resolvers with TypeScript
When using TypeScript, the @autoinject decorator does not support resolvers directly. Instead, you should use argument decorators to apply resolvers without duplicating the constructor's parameter order.
Example:
Custom Resolvers
In addition to built-in resolvers, Aurelia allows you to create custom resolvers to tailor the dependency resolution process to your needs.
Example:
Usage in Injection:
Best Practices
Ensure Consistency Between
@injectand Constructor Parameters: The dependencies listed in the@injectdecorator must match the constructor parameters in both type and order.Leverage
@autoinjectWhen Possible: Using@autoinjectwith TypeScript can reduce boilerplate and improve clarity by automatically inferring dependencies from constructor parameter types.Use Resolvers Appropriately: Utilize built-in resolvers like
Lazy,All, andOptionalto handle specific dependency injection scenarios effectively.Avoid Overusing Singleton Lifetimes: While singletons are convenient, overusing them can lead to tightly coupled code. Prefer transient lifetimes for dependencies that should have a limited scope.
Explicit Configuration for Critical Services: To prevent unintended behaviors, consider explicitly registering essential services with specific lifetimes.
Utilize Child Containers for Scoped Dependencies: When dealing with components like custom elements or routed views, appropriately leverage child containers to scope dependencies.
Maintain Loose Coupling Between Components: Strive for loose coupling by minimizing direct dependencies between unrelated components, enhancing modularity and testability.
Last updated
Was this helpful?