Singleton Since v4.1.0
The singleton helper keeps one instance of a resource per Node process across dev server rebuilds.
Overview
With server rendering, ng serve re-evaluates the whole application and server bundle on every rebuild inside one long-lived Node process. Module-level state starts over, while anything held by node_modules or globalThis survives.
Resources created once per process, such as a syntax highlighter or a database client, are therefore created again on every rebuild while the old ones stay alive. Memory grows and connection pools pile up until the database refuses new connections. Production is unaffected because the process starts once.
- One Instance
- Stores the created value on
globalThisand returns it on every later call with the same key. - No Provider
- A plain function, usable in services, initializers and server code.
Usage
Pass a key and a factory. The factory runs only when no value exists for the key:
import { singleton } from '@/app/core/singleton';
const value = singleton(key, () => create());Core's code block keeps its Shiki highlighter this way. The same works in an app initializer:
let highlighter: HighlighterCore;
export const provideHighlighter = (): EnvironmentProviders =>
makeEnvironmentProviders([
provideAppInitializer(async () => {
highlighter = await singleton('app.highlighter', () =>
createHighlighterCore({
engine: createJavaScriptRegexEngine(),
langs: [() => import('@shikijs/langs/typescript')],
themes: [() => import('@shikijs/themes/catppuccin-latte')],
})
);
}),
{
provide: HIGHLIGHTER,
useFactory: () => highlighter,
},
]);Server Backends
When the Angular server doubles as your backend, wrap database clients and connection pools created in server.ts or in a service:
export const prisma = singleton('app.prisma', () => new PrismaClient());const pool = singleton(
'app.pool',
() => new Pool({ connectionString: process.env['DATABASE_URL'] })
);
export const db = drizzle(pool);Keys
Keys are plain strings shared by the whole process, so prefix them with your application's name. The bui.* prefix is reserved for Core. The first value created for a key wins: after changing a factory, restart the dev server to pick it up.