Bootstrap and composition root
main.ts
src/main.ts is deliberately small but does four non-obvious things:
| Step | Detail |
|---|---|
AG Grid licence |
|
Production mode |
|
Deferred bootstrap |
|
Global error trap |
|
AppModule
src/app/app.module.ts is the composition root. Everything is eager: there
is no loadChildren in the live route tables, so the initial bundle contains the
whole application.
Imports, in the order they matter
AppWelcomeModule
BrowserModule
CancelEmitModule.forRoot() module with a forRoot() singleton provider
HttpModule legacy @angular/http — vestigial
StoreModule.forRoot(APP_REDUCERS, { metaReducers: [storeDebugger] })
EffectsModule.forRoot([ AuthEffects, GlobalSettingsEffects, SupplierEffects,
CrmEffects, UtilsEffects, UnderwritingEffects ])
StoreRouterConnectingModule
StoreDevtoolsModule.instrument() enabled unconditionally, prod included
HttpClientModule
BrowserAnimationsModule
NgHttpLoaderModule global loading spinner
AuthModule, ManageModule, MarketplacesModule, ReportsModule, AdminModule,
SuppliersModule, ProcessesModule, SharedModule, CrmToolsModule
MatSortModule
NgxPermissionsModule.forRoot()
DialogModule
UnderwritingModule
AppRoutesModule ← MUST be last: it owns the wildcard and
the default redirects
|
|
Effects registered at root
Only six effect classes are registered, all in forRoot:
| Class | Responsibility |
|---|---|
|
Google and basic login, token/user hydration, permission loading, logout and storage cleanup, post-login redirect. |
|
Fetching the global-settings collection. |
|
Router-driven supplier loading, the supplier aggregate |
|
CRM timeline loading and note/task/file/email mutations. |
|
Reference-data lookups (timezones, funding periods, states, countries) with in-state deduplication. |
|
Underwriting slice loading. |
BackgroundReportsEffects exists as an empty file — the reducer is registered but
there are no effects. The calculator, layout, marketplace, onboarding and
fetched-requests slices are reducer-only.
Providers and DI substitutions
The provider list is where the abstract-service seams are bound. These are the substitutions to know:
| Token | Implementation | Why it is a token |
|---|---|---|
|
|
Lets the cache strategy be swapped (or stubbed in tests). |
|
|
Built from |
|
|
Reference-data source; mock implementations exist for tests. |
|
|
Global Angular error handling. |
|
|
Order matters — see the interceptor chain. |
|
Themselves, via |
Declared as explicit |
Plain providers include ErrorParserService, ErrorVisualizationService,
ReceivableService, LayoutService, RouteAccessResolver, DatePipe,
ReportsRepositoryService, NgxPermissionsService, DynamicEmbedderService,
SupplierKeyResolver, ErrorLogger, FeatureFlagsGeneralGuard,
SubPermissionsGuard, EvaCommunicationService and VpnTesterService.
A quirk in `AppModule’s constructor
AppModule itself has behaviour: tryAddNoIndex() inspects window.location.href
for any of volusion, pro, lendio, neweggcapital, morecommerce, crm2,
c2fo and, if matched, injects <meta name="robots" content="noindex">. These are
white-label supplier-site names; the goal is to keep white-labelled hosts out of
search indexes.
AppComponent
src/app/app.component.ts (selector app-internal-client) is the outermost
component and carries more responsibility than a typical shell. It extends
SubscriberComponent (the shared base class that collects and unsubscribes
this.subscriptions) and on init:
-
Builds
ThemeConfigfromthemeConfigData(env.envName)— including thecheckForSpecialOccasion()seasonal theming hook. -
Subscribes to the
authslice and callsNgxPermissionsService.loadPermissions(perms)on every change. This is the second place permissions are loaded (the first isAuthEffects.handleFullLogin), which is what makes permissions survive a page reload from persisted auth state. -
Applies a
restricted-accessCSS class todocument.bodywhen the user holdsREST_UW_ACCESS— a whole-app styling switch. -
On every
NavigationEnd: resets scroll, dispatchesLayoutUpdate(urlAfterRedirects)so the layout reducer can decide panel visibility, and notifies the EVA top-bar iframe of the new parent route. -
Subscribes to
incomingMessage$and handles the inbound EVA protocol (toasts, supplier-details store updates, store reads, navigation requests, supplier-menu conditions). See iframe messaging. -
Pushes supplier details out to EVA whenever
selectSupplierDetailsemits. -
Starts the VPN tester if
CHECK_VPNis enabled.
|
Because |
MainLayoutComponent
src/app/shared/layout/main-layout.component.ts is the routed shell that all
feature routes render inside. It is intentionally thin: it subscribes to
LayoutService.customClasses$ and calls detectChanges(). The visual composition
(sidebar, CRM panel, supplier header, outlet) lives in main-layout.html. See
UI composition.
The store debugger meta-reducer
src/app/store-debugger.ts:
export const EnableStoreDebugging = false;
export const showStoreDebugger = () =>
environment.envName &&
environment.envName !== 'staging' &&
environment.envName !== 'prod' &&
EnableStoreDebugging;
It logs every state/action pair to the console when enabled. It is off by default and requires a source edit to turn on — there is no runtime switch. Redux DevTools is the better tool for the same job and is always instrumented.
Bootstrap sequence summary
index.html
└─ main.ts
├─ AG Grid licence
├─ enableProdMode() if production
├─ platformBrowserDynamic().bootstrapModule(AppModule)
│ ├─ AppConfigService built from environment
│ ├─ StoreModule.forRoot → auth reducer rehydrates from
│ │ localStorage['currentUser'] (isAuthenticated derived from it)
│ ├─ utils reducer rehydrates from lscache['utilsState']
│ ├─ supplier reducer restores localStorage['suppliers_last_query']
│ └─ EffectsModule.forRoot → six effect classes subscribe
├─ AppComponent
│ ├─ loadPermissions from persisted auth
│ └─ iframe listeners, layout subscription
└─ Router evaluates the initial URL
└─ RouteAccessResolver → FeatureFlagsGeneralGuard →
NgxPermissionsGuard → resolvers → component
Persistence details — which keys, which TTLs — are in caching and persistence.