Bootstrap and composition root

main.ts

src/main.ts is deliberately small but does four non-obvious things:

Step Detail

AG Grid licence

LicenseManager.setLicenseKey(…​) is called before bootstrap with a licence string embedded in source. The embedded key’s ExpiryDate is in the past (June 2021), which is why the console shows an AG Grid enterprise licence warning; grid functionality still works.

Production mode

enableProdMode() when environment.production is true.

Deferred bootstrap

main() is exported and invoked either immediately (if document.readyState === 'complete') or on DOMContentLoaded — not the usual unconditional call, because the app is embedded in pages that may already be loaded.

Global error trap

window.onerror forwards uncaught errors to Segment as a WINDOW ERROR track event, wrapped in try/catch so that a missing analytics object cannot break the handler. This is separate from Angular’s ErrorHandler — see error handling.

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

AppRoutesModule is last in the import list by design — the comment in the source says so. It declares path: '*' (→ PageNotFoundComponent) and the '' redirect to /welcome. Importing a module that declares routes *after it would make those routes unreachable.

Effects registered at root

Only six effect classes are registered, all in forRoot:

Class Responsibility

AuthEffects

Google and basic login, token/user hydration, permission loading, logout and storage cleanup, post-login redirect.

GlobalSettingsEffects

Fetching the global-settings collection.

SupplierEffects

Router-driven supplier loading, the supplier aggregate forkJoin, marketplaces, payment configs, compliance documents, lifecycle toggles, card providers and the Airwallex flows.

CrmEffects

CRM timeline loading and note/task/file/email mutations.

UtilsEffects

Reference-data lookups (timezones, funding periods, states, countries) with in-state deduplication.

UnderwritingEffects

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

HttpCacheAbstract

HttpCacheLocalStorage

Lets the cache strategy be swapped (or stubbed in tests).

AppConfigService

useFactory: initAppConfig

Built from environment + themeConfigData(environment.envName) with a hardcoded '0.0.1' version argument. The user-visible version comes from environment.VERSION / VersionService, not from this argument.

LookupAbstractService

LookupRepository

Reference-data source; mock implementations exist for tests.

ErrorHandler

AppErrorHandler

Global Angular error handling.

HTTP_INTERCEPTORS

AuthInterceptor, then FetchedRequestsInterceptor (both multi: true)

Order matters — see the interceptor chain.

PaymentConfigService, DepositAccountService, UsersService

Themselves, via useClass

Declared as explicit { provide: X, useClass: X } so that a spec can override them by token without touching the class.

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:

  1. Builds ThemeConfig from themeConfigData(env.envName) — including the checkForSpecialOccasion() seasonal theming hook.

  2. Subscribes to the auth slice and calls NgxPermissionsService.loadPermissions(perms) on every change. This is the second place permissions are loaded (the first is AuthEffects.handleFullLogin), which is what makes permissions survive a page reload from persisted auth state.

  3. Applies a restricted-access CSS class to document.body when the user holds REST_UW_ACCESS — a whole-app styling switch.

  4. On every NavigationEnd: resets scroll, dispatches LayoutUpdate(urlAfterRedirects) so the layout reducer can decide panel visibility, and notifies the EVA top-bar iframe of the new parent route.

  5. Subscribes to incomingMessage$ and handles the inbound EVA protocol (toasts, supplier-details store updates, store reads, navigation requests, supplier-menu conditions). See iframe messaging.

  6. Pushes supplier details out to EVA whenever selectSupplierDetails emits.

  7. Starts the VPN tester if CHECK_VPN is enabled.

Because AppComponent both reads the store and talks to iframes, it is the single place where the EVA seam and the NgRx state meet. Changes to the supplier-details message contract have to be made here and in EvaCommunicationService together.

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.