Use case: You want to load plugins from a remote server at startup instead of bundling them, so you can update or add plugins without rebuilding the host application.
Prerequisite: Add plugins for rich fields, Persist data via REST API
"dependencies": {
"@softarc/native-federation-runtime": "3.3.6",
"@angular-architects/native-federation": "20.1.7",
"es-module-shims": "2.6.2"
}// federation.config.js
const { withNativeFederation, shareAll } =
require('@angular-architects/native-federation/config');
module.exports = withNativeFederation({
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
skip: ['chart.js/auto', 'primeng/chart', 'primeicons']
});shareAll ensures Angular and ng-xtend libraries are
shared as singletons between the host and remote plugins.
main.ts must call initFederation() before
Angular can start:
import { initFederation } from '@angular-architects/native-federation';
initFederation()
.catch(err => console.error(err))
.then(_ => import('./bootstrap'))
.catch(err => console.error(err));bootstrap.ts contains the normal Angular bootstrap:
import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app/app';
import { appConfig } from './app/app.config';
bootstrapApplication(App, appConfig);@Injectable({ providedIn: 'root' })
export class ConfigManagerService {
configLoaded = signal(false);
errorMsg = signal<string | null>(null);
protected resolverService = inject(XtResolverService);
loadConfig(pluginConfig, types) {
this.loadPlugins(pluginConfig).then(() => {
this.resolverService.registerTypes(types);
this.configLoaded.set(true);
}).catch(err => this.errorMsg.set(err.toString()));
}
async loadPlugins(pluginInfos) {
for (const { url } of pluginInfos) {
await this.resolverService.loadPlugin(url);
}
}
}Plugins must be loaded before types are registered,
because type declarations reference plugin-provided names like
'country'.
Appexport class App {
protected configService = inject(ConfigManagerService);
constructor() {
registerDefaultPlugin(this.resolverService);
this.configService.loadConfig(
[{ plugin: 'International Plugin',
url: 'https://cdn.example.com/intl-plugin/remoteEntry.json' },
{ plugin: 'Finance Plugin',
url: 'https://cdn.example.com/finance-plugin/remoteEntry.json' }],
{ 'Example Book': { bookName: 'string', nationality: 'country',
bought: { price: 'money-amount' }, read: 'boolean' } }
);
}
}@if (config.configLoaded()) {
<xt-render displayMode="LIST_VIEW" valueType="Example Book" ...>
} @else if (config.errorMsg()) {
<h2>Error loading plugins</h2>
<span>{{config.errorMsg()}}</span>
} @else {
<h2>Loading plugins...</h2>
}The component can use linkedSignal for the form so it
only builds after config is loaded:
bookForm = linkedSignal(() =>
this.config.configLoaded()
? this.calculateBookForm()
: this.formBuilder.group({})
);Native Federation loads the remote plugin’s Webpack Module Federation
manifest at runtime, fetches the plugin chunks on demand, and integrates
them into the Angular module graph. The plugin’s registration function
runs automatically when loaded, calling
resolverService.loadPlugin() which wraps the remote
module’s entry point.