Complete bootstrap
A full-featured main.ts showing every common ngwr provider. Delete what you don't need.
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideZonelessChangeDetection } from '@angular/core';
import { Plus, Trash2 } from 'lucide';
import { provideWrOverlay } from 'ngwr/overlay';
import { provideWrIcons } from 'ngwr/icon';
import { lucideIcons } from 'ngwr/icon/adapters/lucide';
import { provideWrToastConfig } from 'ngwr/toast';
import { provideWrI18n, provideWrI18nStaticLoader } from 'ngwr/i18n';
import { provideWrDateAdapter } from 'ngwr/date';
import { provideWrDensity } from 'ngwr/density';
import { provideWrConfig } from 'ngwr/config';
import { provideWrTheme } from 'ngwr/theme';
import { AppComponent } from './app/app';
import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, {
providers: [
provideZonelessChangeDetection(),
provideRouter(routes),
// ngwr ---------------------------------------------------------------
provideWrOverlay(),
provideWrIcons(lucideIcons({ plus: Plus, trash: Trash2 })),
provideWrToastConfig({ position: 'top-end', duration: 4000 }),
provideWrTheme({ defaultMode: 'auto' }),
provideWrDensity({ defaultDensity: 'lg' }),
provideWrConfig({ button: { size: 'sm' }, input: { size: 'sm' } }),
provideWrDateAdapter(),
provideWrI18n(),
provideWrI18nStaticLoader({ en: { /* … */ } }),
],
});Overlay
provideWrOverlay() backs every overlay component — dialog, drawer, popover, dropdown, select, tooltip, menus, pickers — with an overlay container and an Overlay instance of NGWR's own. Both tokens fall back to CDK's root instances, so panels still open without it; what you lose is the isolation from other CDK consumers and the --wr-keyboard-inset that lets a mobile sheet clear the on-screen keyboard. Call it once. “Optional” here means the app still works, not that it is free: what the provider references is CDK's overlay module, which is the largest single dependency anything in this catalog reaches for. In an app that renders any overlay component the module is already in the graph and the provider adds close to nothing; in an app that renders none, it is the whole of it — so skipping it as a size decision only pays off in an app with no dialog, no select and no dropdown, which is the app that was never going to call it. provideWrResponsiveOverlays() is a separate function from the same entry point, and it is what turns those panels into bottom sheets below a breakpoint.
import { provideWrOverlay } from 'ngwr/overlay';
// Backs every overlay component — dialog, drawer, popover, tooltip,
// dropdown, select, mention, command-palette, context-menu, and every
// picker. Gives them their own overlay container + Overlay instance, so
// they never share a DOM root with Material / NG-ZORRO. Without it they
// fall back to CDK's shared root container.
providers: [provideWrOverlay()],Responsive overlays
provideWrResponsiveOverlays() — from ngwr/overlay, the same entry point as provideWrOverlay(), not from the component's own — makes dialog, select, dropdown and popover present as a full-width bottom-sheet under the breakpoint, and the command palette go full-screen. A per-component responsive input overrides it either way, so a page can opt one panel out. The sheet's own presentation lives in the ngwr/overlay stylesheet as well — if you load styles per component rather than through the umbrella, @use 'ngwr/overlay' is what makes a sheet look like one. See the mobile guide for the rest of the touch story.
// Same entry point as provideWrOverlay() — NOT ngwr/dialog, and not the
// subpath of whichever component you were reading about when you met it.
import { provideWrOverlay, provideWrResponsiveOverlays } from 'ngwr/overlay';
providers: [
provideWrOverlay(),
provideWrResponsiveOverlays(), // breakpoint defaults to 640
// provideWrResponsiveOverlays({ breakpoint: 768 }),
],
// Per instance, either direction — the input wins over the provider:
// <wr-select [responsive]="false"> stays an anchored dropdown on a phone
// <wr-dropdown-menu responsive> becomes a sheet with no provider at all
//
// If you load styles per component, the sheet's presentation is in this entry
// point too: @use 'ngwr/overlay';Icons
Pass only the icon symbols you actually use — the rest tree-shake out. Scaffold a barrel file with ng g ngwr:icon-set.
import { Check, Plus, Trash2 } from 'lucide';
import { provideWrIcons } from 'ngwr/icon';
import { lucideIcons } from 'ngwr/icon/adapters/lucide';
// Register a tree-shaken icon set. Only the icons you list are bundled —
// the Lucide adapter takes the imported icon data and wraps it as
// `WrIconDef` at runtime, so unused Lucide icons get dropped by the bundler.
providers: [provideWrIcons(lucideIcons({ plus: Plus, trash: Trash2, check: Check }))],Toast
Stack of dismissible notifications, defaulting to the top-end corner. Inject WrToast and call show(...) from anywhere — the provider only sets defaults, so it is optional.
import { provideWrToastConfig } from 'ngwr/toast';
// Defaults for the global toast service. Call `inject(WrToast).show(...)`
// anywhere — the service itself needs no provider.
// position: 'top-start' | 'top' | 'top-end' | 'bottom-start' | 'bottom' | 'bottom-end'
providers: [provideWrToastConfig({ position: 'bottom-end', maxStack: 5 })],Internationalization (i18n)
Catalog-driven translations with double-mustache interpolation, scopes, and a live locale switch. Pair provideWrI18n with either the static loader or provideWrI18nHttpLoader. See the Internationalization primer for the full story.
import { provideWrI18n, provideWrI18nBaseCatalogs, provideWrI18nStaticLoader } from 'ngwr/i18n';
import { wrEn } from 'ngwr/i18n/en';
import { wrRu } from 'ngwr/i18n/ru';
providers: [
provideWrI18n({ defaultLocale: 'en', availableLocales: ['en', 'ru'] }),
// ngwr's strings, looked up key by key underneath yours. Not a
// `{ ...wrEn, ...yours }` spread: it is shallow, so any namespace you share
// with ngwr, e.g. `common`, `validation`, `table`, keeps only one side's keys.
provideWrI18nBaseCatalogs({ en: wrEn, ru: wrRu }),
provideWrI18nStaticLoader({
en: { app: { title: 'My app' } },
ru: { app: { title: 'Мое приложение' } },
}),
],Date adapter
Required by wr-calendar, wr-event-calendar, wr-date-picker (every mode). Native adapter ships zero peer deps; date-fns and Luxon adapters live under separate subpaths.
import { provideWrDateAdapter } from 'ngwr/date';
// Native Date adapter — zero extra deps.
providers: [provideWrDateAdapter()],
// Or date-fns:
import { WrDateFnsAdapter } from 'ngwr/date/adapters/fns';
providers: [provideWrDateAdapter({ adapter: WrDateFnsAdapter })],
// Or Luxon:
import { WrLuxonAdapter } from 'ngwr/date/adapters/luxon';
providers: [provideWrDateAdapter({ adapter: WrLuxonAdapter })],Component defaults
provideWrConfig() sets what a component falls back to when a template says nothing — the shared size scale, the rounded shape on the controls that have one, and <wr-markdown>'s link target and copy button. A bound value always wins, so nothing has to be re-stated to escape it — and a bound false turns a configured true back off, because false is a value rather than an absence. Intent is deliberately not a key: null is how a template says nothing, and it is also how the lib's own chrome says “no colour”.
The three providers do NOT scope alike, and the difference is worth knowing before you reach for a route. provideWrConfig() is read per route: a component resolves it from its own injector, so a lazy area can carry its own defaults. provideWrIcons() works at any level and CHAINS, so a route adds to the set its parent registered rather than replacing it. provideWrToastConfig() is bootstrap-only — WrToast is root-provided and resolves its config once from the root injector, so a route-level call is never read. That one used to be silent; since v14.0.1 it warns in dev mode and names itself.
import { provideWrConfig } from 'ngwr/config';
// App-wide component defaults. Every field is a DEFAULT, not an override: a value
// bound on the element always wins, so a config is never something a template has
// to fight its way out of.
provideWrConfig({
button: { size: 'sm' },
input: { size: 'sm' },
select: { size: 'sm', rounded: true },
checkbox: { size: 'sm' },
});
// <wr-btn>Save</wr-btn> -> small
// <wr-btn size="lg">Save</wr-btn> -> large; the binding wins
// <wr-select [rounded]="false" /> -> square again; `false` is a value, not an absence
// There is deliberately no `color` key. The lib's own chrome binds
// [color]="isCurrent ? 'primary' : null", and `null` means "the template said
// nothing" — a configured intent would repaint every one of those buttons.Density
One knob for app-wide sizing. The provider picks a preset; the per-axis multipliers behind the presets are CSS custom properties, so a value between two presets is a :root override rather than a config field.
import { provideWrDensity } from 'ngwr/density';
// App-wide default density: 'sm' | 'md' (default) | 'lg' | 'touch'.
// The other two fields are storageKey and attribute — a preset is the only
// knob the provider takes.
providers: [provideWrDensity({ defaultDensity: 'sm' })],
// Fine-grained is CSS, not config: a preset is only a set of multipliers, so
// a value between two presets is a stylesheet override, on :root or a subtree.
// :root {
// --wr-density-y: 0.7; /* vertical padding — the lever on height */
// --wr-density-x: 0.9; /* horizontal padding */
// }Theme
Light / dark / auto with optional localStorage persistence. Inject WrTheme to read or toggle at runtime — theme.set('dark'), theme.toggle().
import { provideWrTheme } from 'ngwr/theme';
// 'light' | 'dark' | 'auto' — auto follows prefers-color-scheme.
// Resolved theme is mirrored to <html data-theme="..."> and persisted in
// localStorage under `storageKey`. Inject WrTheme to read or change it
// at runtime.
providers: [
provideWrTheme({
defaultMode: 'auto',
storageKey: 'wr-theme',
attribute: 'data-theme',
}),
],Loading bar
Router-aware top bar. Auto-shows on NavigationStart, hides on NavigationEnd / Cancel. Imperative WrLoadingBar.start() / complete() available for non-router waits.
import { WrLoadingBarComponent } from 'ngwr/loading-bar';
// No provider to register — render the component once in your root template
// and drive it through the injectable `WrLoadingBar` service.
@Component({
selector: 'app-root',
imports: [WrLoadingBarComponent],
template: `<wr-loading-bar color="var(--wr-color-primary)" height="2px" />`,
})
export class AppComponent {
private readonly loading = inject(WrLoadingBar);
}Cookie
SSR-safe typed cookie service. Inject WrCookie and call get(name) / set(name, value, opts) / remove(name).
import { WrCookie } from 'ngwr/cookie';
// No provider to register — inject the service anywhere for typed
// has / get / set / remove / keys / clear on document.cookie, with SSR-safe
// fallbacks.
private readonly cookie = inject(WrCookie);Storage
Swappable engine (local / session / memory / custom) with TTL and a reactive watch signal.
import { provideWrStorage } from 'ngwr/storage';
// Swappable engine + TTL + watch signal. Defaults to localStorage in the
// browser and an in-memory store on the server (and in private mode, where
// setItem throws). Inject `WrStorage` to use it.
// `engine` takes a Storage INSTANCE, or a factory called lazily.
providers: [provideWrStorage({ engine: sessionStorage, prefix: 'app:' })],See also
- GuideServer-side renderingWhat these providers do during a server render — including the two overlay calls that are not no-ops there, and the pre-paint script that closes the light-theme flash.
- GuideMobile & responsiveThe rest of the touch story behind `provideWrResponsiveOverlays()`: sheets, the keyboard inset, and the `touch` density preset.
- GuideReactive formsWhat Angular 22's bridge carries and what it drops, once these providers are in place and you bind a control.