Angular MFE Advanced Concepts | Patterns Beyond the Basics
The setup guides get a host and a couple of remotes talking to each other. Running this in production for real raises a different set of questions β this page covers the ones that come up next.
π Cross-MFE Communication
Section titled βπ Cross-MFE CommunicationβRemotes shouldnβt import each other directly (that reintroduces the tight coupling Micro Frontends are meant to avoid). Two patterns that keep them decoupled:
Custom events, for simple, infrequent signals:
// Publishing MFEwindow.dispatchEvent(new CustomEvent('cart:updated', { detail: { itemCount: 3 } }));
// Listening MFEwindow.addEventListener('cart:updated', (e: CustomEvent) => { this.itemCount = e.detail.itemCount;});A shared, federated state service, for anything more than an occasional signal:
// shared-state/src/cart.store.ts β exposed via Module Federation like a componentimport { BehaviorSubject } from 'rxjs';
class CartStore { private itemCount$ = new BehaviorSubject<number>(0); readonly count$ = this.itemCount$.asObservable();
setCount(n: number) { this.itemCount$.next(n); }}
export const cartStore = new CartStore();Expose cartStore as a federated module and have both host and remotes import the same instance β because itβs marked singleton: true in shared, every MFE gets the same object, not a copy.
π Independent Deployment
Section titled βπ Independent DeploymentβThe entire point of this architecture is that mfe-transaction-history should ship without redeploying the host or any other remote. In practice that requires:
- Contract stability: the exposed componentβs
@Input/@Outputsurface is a public API between teams β changing it without coordination breaks the host silently at runtime, not at compile time (since the host and remote build independently). - A staging remote per environment: promote a remote through dev β staging β prod manifests (see Remote Configuration) independently of the hostβs own release cycle.
- CI that builds and deploys each MFE separately β a monorepo with a single pipeline that always builds everything defeats the purpose; each remote needs its own trigger.
π§ͺ Testing Strategies
Section titled βπ§ͺ Testing Strategiesβ| Level | What it covers | How |
|---|---|---|
| Unit | Component/service logic inside one MFE | Standard Angular testing, no federation involved |
| Integration | The MFEβs exposed component in isolation | Run the remote standalone (ng serve), test against its own routes |
| Contract | The exposed componentβs @Input/@Output shape hasnβt silently changed | A lightweight test in the host repo that loads the real deployed remote and asserts on its public interface |
| End-to-end | Host + all remotes composed together, real user flows | Playwright/Cypress against a fully deployed environment, not local dev servers |
The contract-test layer is the one teams skip and then regret β itβs the only test that would actually catch βthe remoteβs exposed component changed its @Input nameβ before a user does.
β‘ Performance Considerations
Section titled ββ‘ Performance Considerationsβ- Shared dependencies load once: with
shareAll({ singleton: true }), Angular/RxJS/etc. are fetched once regardless of how many remotes use them β verify this is actually happening (check the Network tab for duplicate framework bundles) rather than assuming the config is correct. - Lazy-load remotes on route activation, not eagerly on host bootstrap β a remote nobody navigates to in a given session shouldnβt cost that session anything.
- Preload on hover/intent for remotes on a likely next-click (e.g., a nav link) to hide the remoteβs load latency without paying for it universally.
Next steps
Section titled βNext stepsβ- Revisit Remote Configuration if you havenβt set up per-environment manifests yet
- See Documentation Guide for keeping this all discoverable by the next engineer who touches it