Back to Blog

From iframe to Full Federation in Angular Microfrontends (Part 2)

From iframe to Full Federation in Angular Microfrontends (Part 2)

This is the continuation of the previous post:

In Part 1, the host app embedded child apps using iframes for simplicity. In this Part 2, the same workspace is upgraded to a true remote-loading model using Angular Native Federation.

The target outcome was:

  1. Keep one host app and two remotes.
  2. Start all three apps together.
  3. Load remotes directly as federated modules instead of iframe embedding.
  4. Stabilize runtime behavior and developer experience.

1. Baseline and branch context

The workspace already had:

  • Host app on port 4200
  • child1 on port 4300
  • child2 on port 4400
  • A start:all script using concurrently

Part 1 proved the architecture and local workflow. Part 2 focuses on real runtime composition.


2. Replace iframe integration with remote component loading

The host component was updated to dynamically load remote components using specifiers such as:

import('remote1/Component')
import('remote2/Component')

The host template switched from <iframe> to NgComponentOutlet, so the remote component is rendered directly inside the host UI.

This is the core change from URL embedding to module-level federation.


3. Add federation startup flow in host

The host startup was split so federation initializes first and Angular bootstraps after that:

import { initFederation } from '@angular-architects/native-federation';

initFederation('/federation.manifest.json')
  .catch((err) => console.error(err))
  .then(() => import('./bootstrap'))
  .catch((err) => console.error(err));

Why this matters: if federation is not initialized before app bootstrap, imports like remote1/Component remain unresolved.


4. Use an explicit federation manifest

A host manifest was added:

{
  "remote1": "http://localhost:4300/remoteEntry.json",
  "remote2": "http://localhost:4400/remoteEntry.json"
}

This removed ambiguity and made remote resolution deterministic in local development.


5. Configure remotes to expose ./Component

Both child apps were configured with their own federation.config.js and expose entries:

exposes: {
  './Component': './projects/child1/src/app/app.ts',
}

(and equivalent for child2).

Host import specifiers and remote expose keys must match exactly.


6. Resolve Angular package compatibility issues

A major blocker was a version drift where federation tooling pulled @angular/build for Angular 21 while the workspace was Angular 20.

Fix applied:

  • Align federation package versions to Angular 20-compatible releases.
  • Reinstall dependencies.
  • Verify tree consistency with npm ls @angular/build.

Without this, host/remotes fail before runtime resolution even starts.


7. Stabilize first-click rendering behavior

Another subtle issue: remote loading looked like it worked only on every second click.

Root cause: async import updated a plain class property while the app used zoneless change detection. UI updates were not always reflected on the same interaction.

Fix applied:

  • Store the loaded remote component in an Angular signal.
  • Read the signal in template bindings.

After this, the first click consistently renders the selected remote.


8. Incognito result explained

During debugging, the app worked in incognito but failed in normal browsing mode.

Reason:

  • A browser extension content script (contentscript.js) was injecting into the page and throwing unrelated DOM errors.
  • Incognito disabled that extension context, so app behavior became normal.

Conclusion: this was an environment-level browser extension issue, not a federation code issue.


9. Final local run and expected behavior

npm install
npm run start:all

Expected outcome:

  1. Host loads at http://localhost:4200
  2. Remote 1 and Remote 2 entries are reachable
  3. Clicking a card loads the remote component immediately in host
  4. No iframe is needed for composition

10. What changed from Part 1 to Part 2

Part 1:

  • Simple URL embedding
  • Fast demo setup
  • Strong runtime isolation

Part 2:

  • Runtime module composition
  • Shared dependency/version coordination becomes critical
  • Startup sequencing and manifest wiring are essential

Both are valid at different project stages.


11. Practical takeaway

If you are starting fresh, use iframe composition to quickly prove UX and team boundaries. Then move to federation once you need tighter integration, shared runtime contracts, and direct component/module reuse.

That staged approach makes adoption smoother and reduces early debugging overhead.

Reference Github Repository: https://github.com/arkomandal/angular-microfrontend-federation-full-fledged