@norce/admin-angular-adapter
Makes an Angular app mountable in the Norce admin shell, so the app keeps only its pages.
npm install @norce/admin-angular-adapter @norce/admin-contract
It needs Angular ^21.2 (common, core, platform-browser and router) and RxJS ^7.8. It needs no UI library.
The entry
import { defineAngularApp } from '@norce/admin-angular-adapter';
import { providePrimeNG } from 'primeng/config';
import { routes } from './app/routes';
import { AppPreset } from './preset';
export const { contractVersion, mount } = defineAngularApp({
name: 'users',
title: 'Users',
routes,
styles: new URL('./styles.css', import.meta.url),
providers: () => [providePrimeNG({ theme: { preset: AppPreset } })],
});
Each mount gets an application of its own with zoneless change detection, routing below ctx.basePath, and the title and breadcrumb handed to the shell. The breadcrumb is the page's route title, under title on every page but the landing one.
name | The module key. It names the root element and Angular's APP_ID. |
title | The app's title, the first breadcrumb item below the landing page. |
routes | The app's routes. A catch-all ** route is added after them, which the back button's URLs outside the base path land on. |
routerFeatures | Features for provideRouter, such as withComponentInputBinding(). |
styles | The app's stylesheet, resolved against the entry. It is added on mount and removed on unmount. |
providers | A function returning the app's own providers, such as its UI library's. It is called on every mount. |
root | The root component. Without one, the app renders its routes and nothing else. |
onUnmount | Runs after the application is destroyed, for anything the cleanup below does not cover. |
Unmount
On unmount the adapter destroys the application and empties the container. It then puts back the children of head and the class and style attributes of html and body as they were before the mount. That removes theme styles, scroll locks and dark-mode classes that a UI library leaves behind, without knowing its selectors.
It does not cover anything else on the page, such as an element appended to body that the application does not destroy, a global listener, a timer or a socket. Remove those in the component that adds them, or in onUnmount.
The API
hostFetch(ctx, url, init) is fetch for the app's own API. It sends a same-origin /api/… URL to /api{basePath}/…, which the shell's backend proxies to the app's /api/…, so the app keeps the API paths of its standalone build. It adds the tenant header and retries once after ctx.onUnauthorized() when the call answers 401. hostInterceptor does the same for HttpClient, on same-origin calls only. HOST_CONTEXT injects the context the shell handed the mount.
provideHost(ctx, { name, title, routes }) gives the providers defineAngularApp uses, which is useful in TestBed. isWithinBasePath(basePath, pathname) tells whether a URL belongs to the app.
The hosted build
The shell imports the entry as a native ES module. An @angular/build:application target builds it without an index.html, with stable names for the entry and the stylesheet:
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"index": false,
"styles": ["src/styles.css"],
"outputHashing": "media"
}
}
The standalone page
mountStandalone(mount, { basePath }) runs the app on a page of its own, with a stand-in tenant, user and locale. Any other HostContext field can be passed to override the stand-in.
import { mountStandalone } from '@norce/admin-angular-adapter';
import { mount } from '../main';
void mountStandalone(mount, { basePath: '/users' });
The package ships the page's index.html and a stylesheet that loads the shell's theme. The app's own stylesheet is emitted with inject: false, because mount adds it itself:
"standalone": {
"builder": "@angular/build:application",
"options": {
"browser": "src/standalone/main.ts",
"index": "node_modules/@norce/admin-angular-adapter/standalone/index.html",
"styles": [
{ "input": "src/styles.css", "inject": false, "bundleName": "styles" },
{
"input": "node_modules/@norce/admin-angular-adapter/standalone/standalone.css",
"bundleName": "standalone"
}
],
"outputHashing": "media"
}
}
Point ng serve's buildTarget at this target to develop on the standalone page.
UI library themes
The adapter ships no UI-library theme. An app maps the --norce-admin-* properties from @norce/admin-contract into its library itself, for example a PrimeNG preset whose tokens are var(--norce-admin-…) values. The properties switch with light-dark(), so one set of tokens serves both colour schemes.