Progressively migrates from a traditional server-side website to Angular

Post Editor

We are migrating a traditional server-side website to angular step by step. I want to share some experiences of what we have tried in this article.

5 min read
post

Progressively migrates from a traditional server-side website to Angular

We are migrating a traditional server-side website to angular step by step. I want to share some experiences of what we have tried in this article.

post
post
5 min read

I am working on a PoC project that progressively migrates a traditional server-side website to angular. I want to share some experiences of what we have tried. Hope this article can save you time if you are doing similar things.

Our legacy website contains many pages, we can't replace all of them in a release. Instead, we want legacy UI and new UI (angular) to coexist for several releases and replace the content step by step.

We have tried two options:
1. Angular is used as part of the statically generated server site (traditional).
2. Angular is used as a shell application (container for the static site) whereas parts of the static site are shown inside iframes.

For the convenience of explaining the problem, the rest of the article will use this layout to demonstrate.

Option 1 - Angular in traditional website
Link to this section

Our first thought is to replace part of a web page with angular in the server rendered (traditional) website. Basically, we are going to replace the "Main Area". And we have tried two methods.

Method 1: Bootstrap Angular application on demand in certain parts of the server-rendered website
Link to this section

Let's take a simple example. Here is the static html file for the layout shown above.

<>Copy
<body> <div class="header"><span>Header</span></div> <div class="container"> <div class="left-drawer"> <span>Left Drawer</span> </div> <div id="main" class="main-area"> <div class="action-bar"> <div onclick="navigateTo('app-page1')" class="action-item">Page1</div> <div onclick="navigateTo('app-page2')" class="action-item">Page2</div> </div> <div class="text-area"> <span>Main Area</span> </div> </div> </div> </body>

Two buttons in the "Main Area" will help to navigate to the angular page. The "navigateTo" function appends an Angular related HTML tag and there's also some code that adds lots of scripts to bootstrap Angular.

<>Copy
function navigateTo(tagName) { var mainArea = document.getElementById("main"); var newTag = document.createElement(tagName); mainArea.parentNode.appendChild(newTag); ... }

The main task of this function is to create the angular component html tag and add the script tag to bootstrap angular. This can also be done on the server (rendering HTML tag) for convenience.

Let's see the angular part. We create a new angular project with two new components "app-page1" and "app-page2". Each of those components will act as a root component on a new page. And modify the app.module.ts as below:

<>Copy
const entryComponents = [AppComponent, Page1Component, Page2Component]; @NgModule({ declarations: [ AppComponent, Page1Component, Page2Component ], imports: [ BrowserModule, AppRoutingModule ], providers: [{provide: APP_BASE_HREF, useValue : '/migration' }], entryComponents: entryComponents, bootstrap: [] }) export class AppModule { constructor(private resolver: ComponentFactoryResolver) { } ngDoBootstrap(appRef: ApplicationRef) { for (const componentDef of entryComponents) { const factory = this.resolver.resolveComponentFactory(componentDef as Type<{}>); if (document.querySelector(factory.selector)) { appRef.bootstrap(factory); break; } } } }

Need to notice two things:

  1. Empty the bootstrap list in NgModule.
  2. Define our own ngDoBootstrap method. In this method, we query the document and bootstrap our target component.

Here is what it looks like:

There is an alternative approach which is based on URL. For example, when the user clicks the “Page2” button, the URL is changed to “/migration/page2”. And the AppModule checks the last url segment and bootstrap the related component:

<>Copy
const entryComponents = [AppComponent, Page1Component, Page2Component]; const rootComponentsMap = { page1: Page1Component, page2: Page2Component }; @NgModule({ ... }) export class AppModule { constructor(private resolver: ComponentFactoryResolver) { } ngDoBootstrap(appRef: ApplicationRef) { const lastSegment = window.location.href.split('/').pop(); if (rootComponentsMap[lastSegment]) { const factory = this.resolver.resolveComponentFactory(rootComponentsMap[lastSegment] as Type<{}>); appRef.bootstrap(factory); } } }

Basing the implementation on the HTML tag is more convenient in my case, because the tag can be added on the server easily.

There is an interesting thing about this method. If I have both 'app-page1' and 'app-page2' HTML tags in the HTML at the same time, and bootstrap both of them in ngDoBootstrap method. What will happen? The answer is both of them are created.

That means we can replace more then one piece in the html, even replace component by component. That is another topic.

Method 2: Bootstrap Angular application once and render components dynamically with lazy loading
Link to this section

Sometimes we will have partial page rendering in legacy websites. I mean the content of "Main Area" is from Ajax response. We want to bootstrap angular once, not every time a user navigates to the new page.

This can be done by using the angular dynamic component loader, we bootstrap angular in the onload event handler for the static page, but render only the root element without any appearance, so it is not really visible to the user. Then use the dynamic component loader to load the related Angular component and make it visible when the user navigates to the new page.

<>Copy
export class AppComponent { pageMap = { page1: Page1Component, page2: Page2Component }; ... loadComponent(pageName) { pageMap = { page1: Page1Component, page2: Page2Component } const componentFactory = this.componentFactoryResolver.resolveComponentFactory(pageMap[pageName]); const viewContainerRef = this.angularHost.viewContainerRef; viewContainerRef.clear(); const componentRef = viewContainerRef.createComponent<PageComponent>(componentFactory); // componentRef.instance.data = parameter if needed; } ... }

Please see the detail of this method in dynamic component loader.

In option 1, we also need a communication mechanism between the legacy web and angular. Since two parts are in the same context, we have many choices, like global window, post message and custom event.

Option 2 - Traditional website in angular
Link to this section

In this option, we rebuild the skeleton with angular (header, left-drawer), create an angular iframe component, and put the legacy website into it.

Legacy web contains header/left-drawer, In the iframe onload event handler, we can hide them. We even modify some layout and style in the iframe onload event handler, to give users a more unified experience. This becomes one of the advantages of this option. Just remember to give a loading style during iframe loading and the adjustment script execution.

In some cases, the legacy page in iframe refresh and the header/left-drawer comes out again. We use MutationObserver to watch the change and hide it again.

<>Copy
watchSomeElementChange(element: any): void { const observer = new MutationObserver(e => { if (e[0].addedNodes) { for (const item of Object.keys(e[0].addedNodes)) { const dom = e[0].addedNodes[item]; if ('header' === dom.id) { this.adjustLegacyPageUI(); } } } }); observer.observe(element, { childList: true }); }

One of the advantages of this option is that two UI can work at the same time. The user can choose what they want.

Since our legacy website and angular are in the same origin in our case, we don't meet many iframe issues. We do handle the problem of "iframe history impacts browser's history". And it's another topic.

Conclusion
Link to this section

Two options have their own pros and cons. We need to consider according to specific needs and actual conditions. In our project, we choose option 2 because of the two advantages mentioned above.

Share

About the author

author_image

I am a front-end developer. Always want to figure out how everything works. Love comics.

author_image

About the author

Penny Liang

I am a front-end developer. Always want to figure out how everything works. Love comics.

About the author

author_image

I am a front-end developer. Always want to figure out how everything works. Love comics.

Looking for a JS job?
Job logo
Senior Full-Stack Developer (Node+Angular)

A-Listware

Ukraine
Remote
$48k - $78k
Job logo
Senior Full stack (Angular+Node)

Monolith

Ukraine
Remote
$60k - $84k
Job logo
AngularJS Developer/.net Core - Remote Contract

InfoMagnus

United States
Remote
$115k - $134k
Job logo
Angular Web Developer

NTT Data Services, Inc.

United States
Remote
$115k - $134k
More jobs
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
AngularpostTracking user interaction area

13 September 2021

8 min read

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
Angularpost
7 September 202122 min read
Designing Angular architecture - Container-Presentation pattern

Designing architecture could be tricky, especially in the agile world, where requirement changes are frequent. So your design has to support that and provides extendibility without the need for serious modification. In such cases, you will find the Container-Presentation pattern instrumental.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more
micro frontendspostTaking micro-frontends to the next level

6 September 2021

25 min read

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more