Migrating from AngularJS to React with Web Components

Hash Tech Team
Hash Tech
Published in
5 min readMay 10, 2021
Example of refactored page

Today at Hash we have a SPA built on Angularjs 1.8. Internally called Hashboard, it’s a platform to help our clients manage their business and it’s made of a series of dashboard and admin panels.

Due to the white-label nature of our business model and because we’re an increasingly growth startup, Hahsboard should be highly configurable. Because of some past technical decisions we had to take some hard-coded approaches in order to meet clients’ necessities, but this approach doesn’t scale.

Beyond technical and architectural issues and Hashboard’s scalability, other needs began to arise at Hash, such as the need for a visual identity that’s integrated between different products, higher adaptability to understand our client’s specific needs and business rules, among others. All that pointed to a fact: Hashboard needed a refactor.

Refactoring AngularJS into React

Thinking about how to meet all the needs listed above (to have a up-to-date stack while also gaining development traction and with ease of hiring in mind) we considered many technologies and approaches. Analyzing the market and considering our team members’s background we chose to use React and TypeScript for the refactor process.

There are many motivations to do so, but the major ones were React’s reverse compatible model, its community, long term support and safety and our developers’ background. TypeScript also made sense due to the app’s complexity. The benefits of type safety in code reliability and good developer tools also made this choice one that makes sense for us.

However, we couldn’t stop the current operation to rebuild something from scratch and at the same time we didn’t want to keep evolving the legacy codebase whenever possible. In other words, we had to refactor the Hashboard “from inside out”, developing new features with React following the new architecture, but running it within the existing app, this strategy is also known as Strangler pattern. But how to do it on a complex app and between technologies with such different mindsets?

Web Components

There are many ways to refactor in this context. We could for example use something like ngReact to make a wrapper around React’s components so they could be used in Angular. That’s an interesting approach because it allows easy communication between both worlds. However, it could create an even more chaotic situation, with both worlds sharing too much state, while also resulting a more complex scenario for development.

There are many other approaches to refactoring but our choice was to work with Web Components. One of it’s advantages are custom elements, part of the specification that defines the capability to create new DOM elements. To do such, we chose the remount tool that, in short, builds your React application in the Web Component spot, because it’s registered as a HTML component.

After setting your web component, you can use it anywhere as a regular HTML element:

We’ built a monorepo using Lerna for the new packages and left the legacy app in a separate directory but in the same repository. This way both worlds are strictly separated. While generating and publishing the new world’s build we can add it as dependency of our legacy app and simply use the new custom element as any other element.

We opted to use Lerna because Hashboard scope covers almost every company department. So it makes sense for us to work with dedicated pages that can have owners from other teams, therefore avoiding a monolithic frontend in which everyone needs to be involved. Basically, they become micro frontends.

Despite still being possible to share states through web component’s “props”, there’s a clearer separation between the Angular universe and React. But even so, we opted for creating components of whole pages instead of reusable components. This way we incremented even more the legacy separation because there’s no need for sharing state. Of course there are exceptions such as session sharing. In those cases, due to them being rare and also to avoid tight coupling, we use localStorage.

Details to be aware of

So far we’ve seen that this approach offers many benefits and can be used on pretty much any frontend applications’ refactoring strategy regardless of its frameworks. But as always with technology, there are trade-offs.

Working with the new components through a React dependency generates friction. To test a new component running on the legacy app it’s necessary to use something like yarn link which in turn can have its own complications.

Besides, the application probably will keep relying on the legacy for things like routting (if it’s managed by the client). To bypass this, it’s necessary to reverse the situation with React using the Web Components made from the Angular — which in our case was impractical.

Still on the routing case, things such as navigation menus are more complicated to migrate depending on how they were implemented. In general, the more structural parts of the application end up being the last ones to be refactored in this strategy.

Should you choose to do as we did, i.e, refactor whole pages when a new feature should be implemented on it, you’ll have to justify why some simple things will demand more effort to develop. That’s why it’s important that all stakeholders be aligned about the refactoring before starting it. If the extra time isn’t feasible, a different strategy may be more fitting.

Come face challenges with us

Considering the listed trade-offs, this approach was satisfactory in Hash’s context. As a result,frontend teams had more autonomy, Hashboard’s evolution happened in a simpler and more scalable manner and we could migrate to a new architecture without many hustles.

To keep following this project’s development and read about any other news and strategies of our tech team, just keep an eye here at Tech Blog.

And to be a part of our team and help us make projects like this, check out Hash’s job opportunities by clicking here.

Authors

Lucas Fujicava, Paulo Vitor, Filipe Marins

Translation

Guilherme Not, Marco Worms

--

--