Angular Federation
@angular-architects/native-federation
Setup
Micro FE
Shell
localhost:4200
Home
localhost:4201
Login
localhost:4202
// federation.config.js
const { withNativeFederation, shareAll } =
require('@angular-architects/native-federation/config');
module.exports = withNativeFederation({
name: 'home',
exposes: {
'./Component': './apps/home/src/app/app.component.ts',
},
shared: {
...shareAll(
{
singleton: true,
strictVersion: true,
requiredVersion: 'auto'
}
),
},
skip: [
'rxjs/ajax',
'rxjs/fetch',
'rxjs/testing',
'rxjs/webSocket',
// Add further packages you don't need at runtime
]
// Please read our FAQ about sharing libs:
// https://shorturl.at/jmzH0
});
Home FE
Home
localhost:4201
...
// federation.config.js
const {
withNativeFederation,
shareAll,
} = require('@angular-architects/native-federation/config');
module.exports = withNativeFederation({
name: 'login',
exposes: {
'./routes': './apps/login/src/app/app.routes.ts',
},
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
}),
},
skip: [
'rxjs/ajax',
'rxjs/fetch',
'rxjs/testing',
'rxjs/webSocket',
// Add further packages you don't need at runtime
],
// Please read our FAQ about sharing libs:
// https://shorturl.at/jmzH0
});
Login FE
Login
localhost:4202
...
// federation.config.js
const {
withNativeFederation,
shareAll,
} = require('@angular-architects/native-federation/config');
module.exports = withNativeFederation({
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
}),
},
skip: [
'rxjs/ajax',
'rxjs/fetch',
'rxjs/testing',
'rxjs/webSocket',
// Add further packages you don't need at runtime
],
// Please read our FAQ about sharing libs:
// https://shorturl.at/jmzH0
});
Shell FE (Host)
Shell
localhost:4200
...
// public/federation.manifest.json
{
"home": "http://localhost:4201/remoteEntry.json",
"login": "http://localhost:4202/remoteEntry.json"
}
Application Flow
Forgot password
Home
localhost:4201
Login
localhost:4202
App
Login
Forgot Password
Forgot Password Success
Shell
localhost:4200
App
Shell -> Home
Shell -> Login
Login ---> Forgot Password
Login ---> Forgot Password Success
Shell <- Login
Shell routes
import { loadRemoteModule } from '@angular-architects/native-federation';
import { Route } from '@angular/router';
export const appRoutes: Route[] = [
{
path: '',
loadComponent: () =>
loadRemoteModule('home', './Component')
.then((m) => m.AppComponent),
},
{
path: 'login',
loadChildren: () =>
loadRemoteModule('login', './routes')
.then((m) => m.appRoutes),
},
];
Shell -> Home
Shell -> Login
Login routes
import { Route } from '@angular/router';
import { LoginComponent } from './login/login.component';
export const appRoutes: Route[] = [
{
path: '',
component: LoginComponent,
},
{
path: 'forgot-password',
loadComponent: () =>
import('./forgot-password/forgot-password.component').then(
(m) => m.ForgotPasswordComponent
),
},
{
path: 'forget-password-success',
loadComponent: () =>
import(
'./forgot-password-success/forgot-password-success.component'
).then((m) => m.ForgotPasswordSuccessComponent),
},
];
Login ---> Forgot Password
Login ---> Forgot Password Success
Forgot Password
...
@Component({
selector: 'app-forgot-password',
standalone: true,
imports: [CommonModule, SfcModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './forgot-password.component.html',
styleUrl: './forgot-password.component.css',
})
export class ForgotPasswordComponent {
private router = inject(Router);
private activatedRoute = inject(ActivatedRoute);
back() {
this.router.navigate(['../'], { relativeTo: this.activatedRoute });
}
submit() {
this.router.navigate(['../forget-password-success'], {
relativeTo: this.activatedRoute,
});
}
}
Back to login sibling
Continue to forgot-password-success sibling
Application State
Login
Home
localhost:4201
Login
localhost:4202
App
Login
Shell
localhost:4200
App
AuthService
isLoggedIn$
isLoggedIn$
login(username, password)
user$
Login
...
// Automatically shared since it's a Nx Lib
import { AuthService } from '@frontend/auth';
@Component({
selector: 'app-login',
standalone: true,
imports: [CommonModule, SfcModule, RouterModule],
templateUrl: './login.component.html',
styleUrl: './login.component.css',
})
export class LoginComponent {
private authService = inject(AuthService);
private router = inject(Router);
private activatedRoute = inject(ActivatedRoute);
constructor() {
this.authService.isLoggedIn$
.pipe(
filter((loggedIn) => loggedIn === true),
takeUntilDestroyed()
)
.subscribe(() => {
this.router.navigate(['../'], { relativeTo: this.activatedRoute });
});
}
login(username: string, password: string) {
this.authService.login(username, password);
}
}
Back to shell
Shared service
Web Components
Lit
...
import { html, unsafeCSS } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('sbb-teaser-tile')
export class TeaserTile extends SbbLitElement {
static styles = [unsafeCSS(styles)];
@property({ type: String }) title!: string;
@property({ type: String }) content!: string;
@property({ type: String }) direction: 'top' | 'left' | 'right' = 'right';
@property({ type: String }) linkText?: string;
@property({ type: String }) src?: string;
@property({ type: String }) alt?: string;
@property({ type: Object }) routingOptions!: SbbRoutingOptions;
render() {
return html`
...
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'sbb-teaser-tile': TeaserTile;
}
}
Define 'sbb-teaser-tile' automatically in the customElements registry
Potential solution 1
Define 'sbb-teaser-tile' automatically in the customElements registry
// federation.config.js
const { withNativeFederation, shareAll } =
require('@angular-architects/native-federation/config');
module.exports = withNativeFederation({
name: 'home',
exposes: {
'./Component': './apps/home/src/app/app.component.ts',
},
shared: {
...shareAll(
{
singleton: true,
strictVersion: true,
requiredVersion: 'auto'
}
),
},
skip: [
'rxjs/ajax',
'rxjs/fetch',
'rxjs/testing',
'rxjs/webSocket',
// Add further packages you don't need at runtime
]
// Please read our FAQ about sharing libs:
// https://shorturl.at/jmzH0
});
Figure out if federation can prevent loading Lit components multiple times.
Potential solution 2
Current setup
Home
localhost:4201
Login
localhost:4202
App
Login
Shell
localhost:4200
App
CDN
localhost:9080
<script type="module" data-sbb="teaser-tile">
import { TeaserTile } from 'http://localhost:9080/core/teaser-tile/index.js';
customElements.define('sbb-teaser-tile', TeaserTile);
</script>
If the following script is not present, add it:
Angular Federation
By rachnerd
Angular Federation
- 58