Skip to content

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.

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 MFE
window.dispatchEvent(new CustomEvent('cart:updated', { detail: { itemCount: 3 } }));
// Listening MFE
window.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 component
import { 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.

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/@Output surface 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.
LevelWhat it coversHow
UnitComponent/service logic inside one MFEStandard Angular testing, no federation involved
IntegrationThe MFE’s exposed component in isolationRun the remote standalone (ng serve), test against its own routes
ContractThe exposed component’s @Input/@Output shape hasn’t silently changedA lightweight test in the host repo that loads the real deployed remote and asserts on its public interface
End-to-endHost + all remotes composed together, real user flowsPlaywright/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.

  • 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.