Integrating Angular and ExtJS
One issue that almost all development teams face is how to effectively migrate from one framework or technology to another. In an ideal world, we would simply migrate to the new framework or technology in a single release. However, for non-trivial applications, this is a time-consuming exercise that can often be fraught with complex technical challenges.
Here at Opsview we have a large single-page application (SPA) that could not be effectively migrated to a new framework or technology without significant time investment.
The current application is written in ExtJS, a legacy framework by today's standards. After evaluating other SPA frameworks a few months ago, we decided to migrate the existing application to Angular.
We also decided that a 'big bang' approach would not fit with our needs as it would require a large amount of upfront development before we could ship the end result to our customers. We started looking at ways we could migrate the existing codebase incrementally and start to move individual components over to the new framework. The criteria were simple:
- Avoid having to maintain user interface (UI) components in Angular and ExtJS
- Develop any new components in Angular
- Refine the existing user experience (UX) flow
The latest version of Angular includes a feature called Angular Elements which enables interoperability between frameworks. In short, this enables UI components to be written in Angular and reused in other applications in a framework agnostic way.
- The creation of custom HTML elements
- Handling lifecycle events for UI components
- Attaching an encapsulated 'shadow DOM' to components to ensure styling and JS do not bleed out of the current component
Angular Elements is a feature that is essentially a bridge between Angular components and Web Components. This enables the developer to create one or more Angular components that can be reused in another application. The resulting UI components are then compiled to Web Components so they can be reused elsewhere. This is great as it means we don't need to ship the whole Angular library - we can just include the parts that we need. Typically, this means we can just include the Web Components themselves along with the change detection strategies.
We also needed to define a build process. Specifically, how can we get our Angular UI components into our ExtJS application?
There were lots of options here. An Angular production build typically produces 4 JS files which are essentially the core JS needed to render our Web Components. Additionally, we need to include the CSS styles which can be included via a link tag or output in JS.
For ease of implementation, we decided that it would make the most sense to concatenate all JS files into a single script so we had a single JS file that we could reference in our legacy application. We also decided to include the styles directly in JS so we didn’t have to include both JS and CSS files.
Once we had done this, we weren't quite done. Some Angular UI components may load assets asynchronously such as translation files, fonts or images. So, we needed to add some Apache rules to ensure assets were served correctly.
Migrating components to the new framework
Once we had established a solution, the next question was which components were suitable to be reused in our legacy application. We could opt to go for a high-level approach where we implement individual buttons, etc. Or we could go for a low-level approach where we implement more complex self-contained components.
Technically it's possible to pass input or output events to Angular components that are reused in the legacy application. However, this approach does not scale well as it becomes very complex to pass data between the legacy application and Angular UI components as the data is ultimately affected by the change detection strategies in both frameworks.
We decided to opt to reuse a single navigation component that can be reused in both frameworks. The component itself references several other UI components that are used to generate the top navigation that you see in Opsview today.
There are several reasons why we choose to use the navigation component:
- It's a completely self-contained component, so we were able to introduce some of the new UX improvements without affecting other styling of the legacy application.
- We use redux architecture in our Angular application. It would not have been possible to continue to use this architecture if we passed data from the legacy application to the Angular components. The reason for this is simply because we can’t guarantee that data within the ExtJS application is immutable, which would have broken our Redux architecture. So, we needed to use an Angular component that didn't use any input or output parameters.
There were a couple of challenges we encountered along the way.
The legacy application is a server and client-side application. This is in direct contrast to Angular, which is essentially a client-side framework that is not directly coupled to the server that it communicates with.
Typically, the legacy application is aware of environmental data such as the current user as soon as the page loads. This means that components that are reliant on user data can be rendered immediately and synchronously without any visible delay to the user.
Angular has no knowledge of this environmental data. Instead, Angular UI components are intended to retrieve data asynchronously from an API endpoint. This lead to some scenarios where a given UI component was visible on the page but was not yet populated with data.
In Angular, the CSS for all components can easily be either encapsulated or namespaced. This ensures that any styles do not bleed out of specific components and affect existing components in the legacy application.
In ExtJS however, the concept of global CSS still exists. So, it’s possible for the styling of a component in the legacy application to affect Angular UI components. We needed to add some minor CSS tweaks to ensure that Angular UI components were not adversely affected by global CSS from the legacy application.
In the future, we intend on creating a standalone Angular application that will sit alongside the existing legacy ExtJS application as we migrate it to the new framework.
This will require lots of testing as the application will essentially be built in 2 modes - one that runs inside the legacy application and one that runs independently. We're already seeing the benefits of being able to develop new components solely in Angular and going forwards we’re planning on developing more UI components that can be used in both applications.