Enable OneCX app to run in standalone
The goal of this guide is to provide step by step instructions on how to enable standalone mode for an existing OneCX application and allowing it to run without a shell.
Steps
The goal of this cookbook is to refactor an existing OneCX compatible app so that it can not only run inside the OneCX platform but also as a standalone application. All steps have been tested in an app generated with Angular CLI and migrated to be compatible with OneCX but should also work in other OneCX compatible Angular apps (e.g. apps generated with NX).
To achieve the goal of running an application in standalone, a second set of entrypoint files has to be created. Similarly to the applications remote module in remote scenarios, the standalone entrypoint files will be used to add necessary imports and providers, and route requests to the correct feature modules. A detailed overview of which files have to be created can be found in later sections of this document.
Install @onecx/standalone-shell
In order to run a OneCX app in standalone, it’s necessary to mock/provide some of the functionality that would usually be provided by the OneCX platform. To make this easy, @onecx/standalone-shell
provides all the necessary code wrapped in a importable Angular module. Before using the module, the necessary dependency has to be added to the app. The dependency has to be installed using the version that is also used for other @onecx libraries in the project. The minimum required version of @onecx/standalone-shell
is 5.31.0
.
npm i @onecx/standalone-shell
Rename existing entrypoint files
To avoid confusion, the projects ./src/main.ts
and ./src/bootstrap.ts
files should be renamed to remote-main.ts
and remote-bootstrap.ts
.
Make sure to also adjust references to ./src/main.ts
and ./src/bootstrap.ts
(e.g. in tsconfig.app.json
and webpack.config.js
).
If remote-main.ts
and remote-bootstrap.ts
already exist in the project, no further action is required.
Import StandaloneShellModule
and modify app.component.html + app.component.ts
When running an app in standalone, ./src/app/app.module.ts
, ./src/app/app.component.html
and ./src/app/app.component.ts
are treated as the application entrypoints. To add standalone capabilities to the application, the StandaloneShellModule
provided by @onecx/standalone-shell
has to be added to the imports array of ./src/app/app.module.ts
. Make sure to add the import as the last entry of the imports array.
Afterwards the entire application has to be wrapped in a standalone-shell-viewport component provided as part of the imported StandaloneShellModule
. To achieve this goal the contents of ./src/app/app.component.html
have to be replaced with the following code:
<ocx-standalone-shell-viewport></ocx-standalone-shell-viewport>
Afterwards it should be ensured that the applications remote entrypoint (e.g. ./src/app/app-entrypoint.component.ts
) and the applications standalone entrypoint (e.g. ./src/app/app.component.ts
) have different selector names. The selector name can be found inside the @Component
annotation that is placed before the component class in the components TypeScript file.
Add necessary imports and providers to app.module.ts
Since ./src/app/app.module.ts
is not loaded in remote scenarios and the projects remote module is skipped in standalone mode it’s crucial that all critical imports and providers are present in both modules. To achieve this goal it’s recommended to create two exported variables in the remote module file before the actual module class. The variable commonImports
should contain an array of imports that need to be present in both modules. Similarly the variable commonProviders
should contain an array of providers that need to be present in both modules. After creating those variables they should be added to the imports
and providers
array in both modules. By doing this, the applications remote module is treated as the single source of truth, which helps with preventing import and provider errors.
// Example providers and imports
import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { CommonModule } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
export const commonProviders = [
provideHttpClient(withInterceptorsFromDi()),
...
]
export const commonImports = [
BrowserModule,
BrowserAnimationsModule,
CommonModule,
...
]
export class RemoteModule implements DoBootstrap {
...
}
Use AppComponent
selector in index.html
To correctly render the standalone entrypoint, it has to be referenced in index.html
. Therefore the element tag placed inside the body of ./src/index.html
must match the selector name of the applications standalone entrypoint (e.g. ./src/app/app.component.ts
), which can be obtained as mentioned above.
Create separate main.ts
and bootstrap.ts
for standalone
To correctly bootstrap and render the standalone version of an application, it’s necessary to create separate main and bootstrap files alongside the already existing files used for microfrontend bootstrapping (e.g. ./src/remote-main.ts
and ./src/remote-bootstrap.ts
).
The newly created bootstrap file must contain the following code:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { AppModule } from './app/app.module'
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err))
The newly created main file should import the bootstrap files and handle potential errors:
import('./bootstrap').catch((err) => console.error(err))
After creating the files, the newly created main files must be referenced in the files
array in tsconfig.app.json
so that it’s included in the applications bundle. Additionally it should be verified that the build.options.main
property in the applications configuration file (e.g. angular.json
or project.json
) points to the newly created main file.
Optional: Set up @ngx-translate
translation loading
To ensure correct translation loading in an application using @ngx-translate
a few more modifications have to be applied to ./src/app/app.module.ts
.
-
The package
@ngx-translate/core
has to be installed in the project. -
The routes passed to
RouterModule.forRoot()
must be wrapped withaddInitializeModuleGuard
from@onecx/angular-integration-interface
. This ensures correct translation loading for each route. Note that the route configuration might be placed in a separate routing module (e.g../src/app/app-routing.module.ts
) or in thecommonImports
you created in your applications remote module.imports: [ ... RouterModule.forRoot(addInitializeModuleGuard(routes)), ... ],
-
TranslateModule.forRoot()
has to be configured in the app modules imports array or thecommonImports
array that is being shared betweenapp.module.ts
and the application’s remote module.createTranslateLoader
has to be imported from@onecx/angular-utils
and ensures that the correct translation files is loaded on app load. If@onecx/angular-utils
is not yet installed in the project, it has to be installed first. Note that the propertyisolate
should not be necessary in a pure standalone setup and is only required when adding theTranslateModule
configuration to acommonImports
array.imports: [ ... TranslateModule.forRoot({ isolate: false, loader: { provide: TranslateLoader, useFactory: createTranslateLoader, deps: [HttpClient] } }), ... ],
-
The path to the applications translations is provided automatically by the previously imported
StandaloneShellModule
. If any errors occur during translation loading, the network tab of the browsers developer tools can be used to verify that the application’s translation files are placed in the correct location.
Disclaimer: If any of the applications feature modules use ngx-translate features, such as the translate
pipe, TranslateModule
might have to be added to the feature modules imports array.
Verify that the app is running in standalone
After following the above mentioned steps the app should be accessible on its configured URL (e.g. http://localhost:4200).