I know you can have multiple angular apps within a single angular workspace as remotes loaded into a shell angular app, but can you have multiple angular apps that are not in the same workspace? We have apps in different repositories, so they are not in the same workspace.
--- HOST APP webpack.config.js ---
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
remotes: {
"appOne": "http://localhost:4201/remoteEntry.js",
"appTwo": "http://localhost:4202/remoteEntry.js",
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
});
--- HOST APP main.ts ---
import { loadRemoteEntry } from '@angular-architects/module-federation';
import('./bootstrap')
.catch(err => console.error(err));
Promise.all([
loadRemoteEntry('http://localhost:4201', 'appOne'),
loadRemoteEntry('http://localhost:4202', 'appTwo'),
])
.catch((err) => console.error('Error loading remote entries', err))
.then(() => import('./bootstrap'))
.catch((err) => console.error(err));
--- HOST APP app-routing.module.ts (routes only) ---
export const APP_ROUTES: Routes = [
{
path: '',
component: HomeComponent,
pathMatch: 'full'
},
{
path: 'app-one',
loadChildren: () => loadRemoteModule({
remoteName: 'appOne',
exposedModule: './appOne',
}).then((m) => m.AppOneModule)
},
{
path: 'app-two',
loadChildren: () => loadRemoteModule({
remoteName: 'appTwo',
exposedModule: './appTwo',
}).then((m) => m.AppTwoModule)
}
];
--- REMOTE APP ONE webpack.config.js ---
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'appOne',
exposes: {
'./appOne': './projects/app-one/src/app/app-one/app-one.module.ts',
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
});
--- REMOTE APP ONE app-one.module.ts ---
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { AppOneComponent } from './app-one.component';
import { AppOneRoutingModule } from './app-one-routing-module';
@NgModule({
declarations: [AppOneComponent],
imports: [CommonModule, AppOneRoutingModule]
})
export class AppOneModule {}
--- REMOTE APP ONE app-one-routing-module.ts ---
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AppOneComponent } from './app-one.component';
const routes: Routes = [{ path: '', component: AppOneComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AppOneRoutingModule {}
Activate module federation in all the angular projects (shell and remotes)
To do that you can use the schematic @angular-architects/module-federation with the command :
ng add @angular-architects/module-federation
This would generate the necessary configuration in the differents files :
- src/bootstrap.ts (bootstrap of AppModule)
- src/main.ts (import bootstrap)
- webpack.config.js (skeleton to use module federation)
- webpack.prod.config.js (skeleton to use module federation)
Shell
The shell app will use the router to lazy load the remotes modules(remote1, remote2)
export const APP_ROUTES: Routes = [
{
path: '',
component: HomeComponent,
pathMatch: 'full'
},
{
path: 'remote1',
loadChildren: () => loadRemoteModule({
remoteName: 'remote1App',
exposeModule: './Remote1',
}).then((m) => Remote1Module)
},
];
So this Remote1Module module is not part of the shell project.
Your main.ts file
will look like :
Promise.all([
loadRemoteEntry('https://url_remote1', 'remote1App'),
loadRemoteEntry('https://url_remote2', 'remote2App'),
])
.catch((err) => console.error('Error loading remote entries', err))
.then(() => import('./bootstrap'))
.catch((err) => console.error(err));
So here you are trying to load the remote app with his url and name. The name must be the reference of the name you are going to use in routes(remoteName: 'remote1App'
).
Microfrontend project(Aka remote)
So let say we are in the remote1 project which have a module named Remote1Module This module contains the functionality we would like to expose in the Shell app. In order to make this work we need to expose Remote1Module in the remote1 project webpack.
module.export = {
...
plugins: [
new ModuleFederationPlugin({
name: 'remote1',
filename: 'remoteEntry.js',
exposes: {
'./remote1App': './src/app/remote1.module.ts'
}
}),
shared: share({
...
})
]
}
This configuration exposes the Remote1Module under the public name remote1App. You can also share some packages with the Shell app by using the shared section.
Trying out
You can run first your remote app and then after your shell but this order is not necessary. I'm suggesting just to avoid the error
Error loading remote entries ...
ng serve remote1App
ng serve shell
All credits to Manfred Steyer (https://www.angulararchitects.io/en/aktuelles/the-microfrontend-revolution-part-2-module-federation-with-angular/)