Lazy and async
Two adjacent problems: deferring when a service is constructed, and deferring when its code is loaded. injectLazy handles the first, injectAsync the second.
@mmstack/dinpm
#injectLazy
inject() resolves and constructs a dependency right away. injectLazy captures the injector now but waits to build the service until you call the returned getter, then caches it. Good for something heavy that only runs on an action, like an export routine. It stays synchronous.
import { injectLazy } from '@mmstack/di';
private getExportService = injectLazy(HeavyExportService);
export() {
const service = this.getExportService(); // built on first call, cached after
service.doExport();
} It supports Angular's InjectOptions, so { optional: true } widens the getter to T | null.
#injectAsync
injectAsync loads a service's code chunk through a dynamic import() and resolves it from DI on first access, returning a memoized getter. The loader runs at most once. Use it to keep a bundle out of the initial download until an interaction needs it.
import { injectAsync } from '@mmstack/di';
private readonly markdown = injectAsync(() =>
import('./markdown.service').then((m) => m.MarkdownService),
);
async preview(src: string) {
const svc = await this.markdown(); // loads + resolves once, cached
return svc.render(src);
} On v22 and up, prefer Angular's built-in injectAsync when the service is providedIn: 'root'. This version exists for v19 to v21, and for services that are not root-provided: it probes normal DI first, and if that misses and the token is a class, it auto-provides it in a child injector tied to the target injector.
#Prefetching
Pass prefetch to warm the chunk ahead of first use: 'idle', a millisecond deadline, or a custom trigger. It only runs in the browser and is skipped on slow or data-saver connections.
private readonly heavy = injectAsync(
() => import('./heavy.service').then((m) => m.HeavyService),
{ prefetch: 'idle' },
);#provideLazy
provideLazy registers a loader against a token and hands back the same [inject, provide, token] tuple. Declare a lazy dependency in a route's or component's providers, then inject it deep in the tree without that consumer importing the module. Every consumer under the provider boundary shares one instance and one in-flight load.
import { provideLazy } from '@mmstack/di';
const [injectMarkdown, provideMarkdown] = provideLazy<MarkdownService>('Markdown');
// register the loader at a route or component boundary
providers: [
provideMarkdown(() => import('./markdown.service').then((m) => m.MarkdownService)),
];
// consume it below without importing the module
private readonly markdown = injectMarkdown(); // () => Promise<MarkdownService>
async preview(src: string) {
return (await this.markdown()).render(src);
}