Running a legacy AngularJS app inside a micro-frontend host
A legacy AngularJS or jQuery application can live inside a modern host as one more micro-frontend. Users get a single product, and the old code is retired screen by screen.
Why do this
A large legacy app cannot be replaced in one release. Putting it inside a modern host gives you a single product from day one: one login, one navigation, one URL space. New screens are built in the modern stack. Old screens keep working in the legacy bundle until their turn comes.
This approach combines well with a hybrid migration inside the legacy area, described in how to migrate AngularJS to Angular without downtime.
Choose the level of isolation first
| Option | Isolation | Integration | Use it when |
|---|---|---|---|
| Mount in the host's DOM | Low: shared globals and CSS | Best: one document, shared routing | The legacy app is reasonably well behaved |
| Custom element wrapper | Low to medium | Good, with a clean mount interface | You want one mounting contract for all areas |
| Iframe | High | Weakest: separate document, messaging needed | The app depends on global CSS, global variables or conflicting library versions |
The comparison is in iframes vs web components vs federation.
Mounting and unmounting
AngularJS apps normally start through the ng-app attribute when the page loads. Inside a host, the app must start when the user enters a legacy route and stop when the user leaves.
- Start manually. Remove
ng-appand callangular.bootstrapon an element that the host provides. - Expose a small interface. A
mount(element)function and anunmount()function, or a custom element whose connected and disconnected callbacks do the same. If the platform uses single-spa, thesingle-spa-angularjshelper provides these lifecycles. - Clean up on unmount. Destroy the root scope, remove the DOM and remove the listeners the app added to
windowordocument. AngularJS was never designed to be stopped, so expect leaks: timers, global event handlers, jQuery plugins attached to the body.
Test the cycle of entering and leaving the legacy area many times. Memory that grows on each visit is the typical symptom.
Sharing the session
The host owns the login. The legacy app must stop having its own.
- Remove the legacy login screen and its redirect logic.
- Pass the session through the mount interface or a small API from the host: current user, permissions and a way to get a valid token.
- Add an
$httpinterceptor that asks the host for the token on each request, so token refresh stays in one place. - On a 401 response, call the host's sign-out or re-authentication function. The legacy app must not redirect on its own.
Details in shared authentication.
Routing handoff
Two routers now watch the same URL. One rule keeps them apart: the host owns the top-level path, and the legacy app owns everything under one prefix.
- Give the legacy app a prefix such as
/legacy/or the name of its business area. - Configure the legacy router (ngRoute or UI-Router) so that all its routes live under that prefix. Review the fallback route: an
otherwiserule that redirects to the legacy home will hijack URLs that belong to the host. - If the legacy app uses hash-based URLs, keeping the hash inside the prefix is the easiest way to coexist. HTML5 mode is cleaner, and it needs a correct base path.
See routing across micro-frontends.
Containing the CSS
Legacy stylesheets are usually global: an old Bootstrap version, resets, rules on element names. They will change the look of the host and of other remotes.
- Load the legacy stylesheet when the legacy area mounts and remove it when it unmounts.
- Be careful with Shadow DOM. Many jQuery plugins and old libraries query the document directly and break inside a shadow root.
Lazy loading the legacy bundle
The legacy bundle is often large and brings its own copies of jQuery and AngularJS. Users who never open a legacy screen should not pay for it.
Load it on demand. The host's route for the legacy prefix injects the scripts, waits for them and then calls mount.
Retiring it screen by screen
- Keep a list of legacy routes with their usage. Delete unused screens instead of migrating them.
- Build the replacement screen in a modern remote.
- Change the host's route table so the URL points to the new screen. Add a redirect if the URL changes.
- Remove the route, the controller and the template from the legacy bundle.
- Track the number of legacy routes left. When it reaches zero, delete the bundle, the mount code and the prefix.
The risk in this model is comfort. Once the legacy app works inside the host, the pressure to retire it drops, and unsupported code stays in production. Set a target date for each group of screens and report the remaining route count regularly.
More in this section
- Team ownership, boundaries and Conway's law
- The real cost of micro-frontends
- Common micro-frontend mistakes and how to avoid them
- Micro-frontend readiness checklist
Planning a migration or a micro-frontend platform?
In a 20-minute call you describe your situation and I tell you how I'd approach it. If it isn't worth doing yet, I'll say so.
Message me on LinkedIn