This library depends on @angular-architects/module-federation v14.2.1
Thanks to Manfred Steyer for your series of posts about Module Federation in Webpack 5 and Micro-frontends.
Note: before using, check out a series of posts from Manfred Steyer.
I recommend using the nx monorepository.
If you are using Angular v12 and @angular-architects/module-federation v12.2.0 you should use this library v1.0.2.
- Сonvention
- Configuration
- Configuration options
- Use in Routing
- Use in plugin-based approach
- Use MfeService
-
Micro-frontend string or MFE string means this type of string 'mfe-app/exposed-component'. All elements in this line are designed in kebab-case style. The first half in the example above 'mfe-app/' is the name of the mfe application, the second half is the name of a exposed component or module declared in the ModuleFederationPlugin in webpack.config.js.
-
The key of the exposed element from the ModuleFederationPlugin must match the element's class name.
// webpack.config.js file
new ModuleFederationPlugin({
[...]
exposes: {
'HomeModule': 'apps/login/src/app/home/home.module.ts',
},
[...]
});
// home.module.ts file
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
@NgModule({
declarations: [HomeComponent],
imports: [
CommonModule,
RouterModule.forChild([
{
path: '',
component: HomeComponent,
},
]),
],
})
export class HomeModule {}
- If you use plugin-based approach with MfeOutletDirective then you should expose from ModuleFederationPlugin both component and module, that decalred this component. They should have same name and differ only in the '...Component' or '...Module' prefix, example of home.component.ts:
new ModuleFederationPlugin({
[...]
exposes: {
'HomeModule': 'apps/login/src/app/home/home.module.ts',
'HomeComponent': 'apps/login/src/app/home/home.component.ts',
},
[...]
});
-
Internal сonvention. All micro-frontend designations are in kebab-case style. This means that if you have a micro-frontend named
fallbacks-mfe
, then you must specify the linefallback-mfe/component
in all functions and directives of this library. Same rule for component name, if you have a component namedNotFoundComponent
you must setsfallbacks-mfe/not-found
. -
All functions and directives load micro-frontends with predefined option
type = 'module'
. More about type asmodule
orscript
here and here migrations guid to Angular v13
To configure this library, you should import MfeModule to core.module/app.module once for the entire application:
@NgModule({
imports: [
MfeModule.forRoot({
mfeConfig: {
"dashboard-mfe": "http://localhost:4201/remoteEntry.js",
"loaders-mfe": "http://localhost:4202/remoteEntry.js",
"fallbacks-mfe": "http://localhost:4203/remoteEntry.js"
},
preload: ['loaders-mfe', 'fallbacks-mfe'],
delay: 500,
loader: 'loaders-mfe/spinner',
fallback: 'fallbacks-mfe/not-found',
}),
],
})
export class CoreModule {}
List of all available options:
-
mfeConfig - its map, key is micro-frontend app name and value is remoteEntryUrl string.
remoteEntryUrl - URL where runs micro-frontends.
More about remoteEntryUrl in Micro-frontends world here
-
preload (Optional) - list of micro-frontends, bundles of the specified micro-frontends will be loaded immediately and saved in the cache.
-
delay (Optional) Now only works in plugin-based approach. - The delay between displaying the contents of the bootloader and the micro-frontend. This is to avoid flickering when the micro-frontend loads very quickly. By default 0.
-
loader (Optional) Now only works in plugin-based approach. - Displayed when loading bundle of micro-frontend. Indicated as a micro-frontend string example: 'loader-mfe/spinner'.
-
fallback (Optional) Now only works in plugin-based approach. - Displayed when micro-frontend component loaded with error. Indicated as a micro-frontend string example: 'fallback-mfe/not-found'.
For better UX, add loader and fallback micro-frontends to preload array.
To use micro-frontends in Routing, it is enough to import and use the helper function called loadMfeModule, like in the example below:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { loadMfeModule } from '@dkhrunov/ng-mfe';
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => loadMfeModule('dashboard-mfe/entry'),
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, { initialNavigation: 'enabledBlocking' })],
exports: [RouterModule],
})
export class AppRoutingModule {}
This approach allows us to load micro-frontends directly from HTML.
More about plugin-based approach here
To work correctly with this approach, you must always expose both the module and the component declared in this module.
An example webpack.config.js that exposes the EntryComponent micro-frontend "dashboard-mfe":
return {
...
resolve: {
alias: sharedMappings.getAliases(),
},
plugins: [
new ModuleFederationPlugin({
name: 'dashboard-mfe',
exposes: {
// Expose Module
EntryModule: 'apps/dashboard-mfe/src/app/remote-entry/entry.module.ts',
// Expose Component that declared in EntryModule @NgModule({ declarations: [EntryComponent] });
EntryComponent: 'apps/dashboard-mfe/src/app/remote-entry/entry.component.ts',
},
filename: 'remoteEntry',
shared: share({
'@angular/core': {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
},
'@angular/common': {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
},
'@angular/common/http': {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
},
'@angular/router': {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
},
rxjs: {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
},
'rxjs/operators': {
singleton: true,
strictVersion: true,
requiredVersion: '^7',
},
...sharedMappings.getDescriptors(),
}),
}),
sharedMappings.getPlugin(),
],
};
This architectural approach will use MfeOutletDirective.
- Just display the component "EntryComponent" of micro-frontend "dashboard-mfe":
<ng-container *mfeOutlet="'dashboard-mfe/entry'"></ng-container>
- Display the component with @Input() data binding. For data binding use property
input
:
<ng-container *mfeOutlet="'dashboard-mfe/entry'; inputs: { text: text$ | async };"></ng-container>
If you try to bind a @Input() property that is not in the component, then an error will fall into the console: "Input someInput is not input of SomeComponent."
- Display the component with @Output() data binding. For @Output() binding use property
output
:
<ng-container *mfeOutlet="'dashboard-mfe/entry'; inputs: { text: text$ | async };"></ng-container>
If you try to bind a @Output() property that is not in the component, then an error will fall into the console: "Output someOutput is not output of SomeComponent."
If you try to pass a non-function, then an error will fall into the console: "Output someOutput must be a function."
- To override the default loader delay, confgured in
MfeModule.ForRoot({ ... })
, provide custom number in ms to propertyloaderDelay
:
<ng-container *mfeOutlet="'dashboard-mfe/entry'; loaderDelay: 1000"></ng-container>
- To override the default loader or fallback components, confgured in
MfeModule.ForRoot({ ... })
, provide TemplateRef or micro-frontend string to propertyloader
andfallback
:
In the example below, loader provided as a TemplateRef, and fallback is micro-frontend string.
<ng-container
*mfeOutlet="
'dashboard-mfe/entry';
loader: loader;
fallback: 'fallback-mfe/not-found'
"
>
</ng-container>
<ng-template #loader>
<div>loading...</div>
</ng-template>
- You can also provide a custom injector for a component like this:
<ng-container *mfeOutlet="'dashboard-mfe/entry'; injector: customInjector"></ng-container>
You can load micro-frontend module class and component class by using MfeService.
Under the hood MfeOutletDirective uses MfeService to resolve the micro-frontend component factory.
-
resolveComponentFactory<M, C>(mfe: string, injector?: Injector): Promise<ComponentFactory<C>>
- Resolve the micro-frontend component factory. -
load<M, C>(mfe: string): Promise<LoadedMfe<M, C>>
- Loads the micro-frontend exposed module class and exposed component class. -
loadModule<M>(mfe: string): Promise<Type<M>>
- Loads an exposed micro-frontend module class. -
loadComponent<C>(mfe: string): Promise<Type<C>>
- Loads an exposed micro-frontend component class.