If you have worked at least once on a brownfield project, you know how difficult and frustrating it can be.
You can use old approaches, but you're probably writing code without the Typescript superset. Furthermore, you are losing all the functionalities that a new framework has, which compromises the velocity and the devEx.
We can write the entire solution with another framework. Probably, this won't be a good idea, basically for two reasons:
- Time ⏰
- Costs 💸
The good news is that you can write the new features with the latest framework while keeping the old features. If you have good craftsmanship, you will be able to use the strangler fig pattern by Martin Fowler, promoting a smooth and clean transition.
From brownfield to one in bloom 🌸, but how?
AngularJS issues
We take the example of AngularJS.
The most significant risk of staying on AngularJS is safety, given that Google doesn't provide new security patches.
Another big issue is the two-way-data-binding. Each change in UI is reflected in the model and vice versa.
Each state change synchronizes more variables, and the use of a digest cycle can slow the application.
The next example of how AngularJS makes you suffer is Dependency Injection 💉. In AngularJS, dependencies are injected by the name of an argument.
function MyController($scope, $state) {
// β¦
}
Here, the .toString() method is called, which extracts the argument names and then compares them with the already registered dependencies.
So, when you minify the code, it stops working.
Ultimately, we won't have TypeScript and all its advantages.
Perhaps we have enough points to think about writing with a new framework:
- Safety
- Two way data binding
- DI
- TS
Now, let's see how to integrate React into the AngularJS application.
Scaffolding React
The idea is to create a new folder in the AngularJS project's tree structure called app-new . This will be the entry point of our React application. Here, we can install the latest version of React. Preferably, we will install React with the Vite plugin.
Then, we will have a similar tree:
/
βββ app
β βββ assets
β βββ modules
β βββ package.json
βββ app-new
β βββ public
β βββ src
β βββ package.json
βββ assets
βββ cypress
βββ doc
As you can see, we have two package.json. One for app (AngularJS) and one for app-new (React)
Integration layer between two apps
We will create an Angular component that we will call into the routes.js file. Then, we will define a new route for this component.
.state('react-home', {
url: '/home',
template: '',
data: { requiresAuth: true }
})
Here, we can pass any props for this component.
Then, in the index.js Angular file, we should define a scope variable.
This variable defines a base URL that will be used from React.
scope.NEW_APP_BASE_URL = 'http://localhost:3000';
The controller, instead, should be something like that:
(function () {
const controller = function ($sce) {
const ctrl = this;
ctrl.$onInit = () => {
ctrl.baseUrl = window.NEW_APP_BASE_URL;
ctrl.url =$sce.trustAsResourceUrl(`${ctrl.baseUrl}/app-new/`);
};
};
angular.module('libClient.common').component('appNewWrapper', {
bindings: {},
templateUrl:'/modules/common/component/appNewWrapper/appNewWrapper.html',
controller: ['$sce', controller]
});
})();
and the view
<iframe name="iframe1" frameBorder="0" width="100%" height="100%" scrolling="no"
src="{{$ctrl.url}}" allow="camera; microphone; display-capture;geolocation"></iframe>
Here's a little summary:
Generally, the iframe is not a good idea for several reasons. The basic idea is to use it only for the transition from Angular to React, then remove it. With iframe, if you have SEO on your website, you may be penalized by Google indexing and have a ranking problem, in addition to various security issues.
So, we will have 2 apps launched on different ports.
To call our React components inside the iframe, let's define them in our AppRouter with the correct url that will open the iframe
const AppRouter = () => {
return useRoutes([
{
path: '/home',
element:
},
...
Communication both frameworks
All right! Now we might ask ourselves a rather spontaneous question:
How can I communicate between a part of my app in angular and one in react or navigate through pages?
The answer? 🥁... : window.postMessage()
The window.postMessage() method safely enables cross-origin communication between a page and an iframe embedded within it
So, we create a function called goTo to navigate across the pages.
function goTo(destination: Destination, params?:
NavigationParams): void {
const {parent} = window
parent.postMessage({
type: 'navigation',
state: destination,
params: params ? params : null
}, "*")
}
If we send a message from React to Angular, we use this
window.parent.postMessage(
{
type: 'UPDATE_ID',
id: params.id.toString(),
},
'*'
);
and then in the Angular controller
window.addEventListener('message', (event) => {
if (event.data?.type) {
switch (event.data.type) {
case 'UPDATE_ID':
ctrl.id = event.data.id;
break;
}
}
});
Conclusion
With this approach, we can integrate React into AngularJS and revitalize it. A good next step would be to rewrite the old AngularJS controllers into React components. This way, we have a smooth transition from AngularJS to React. In the end, we can remove our app-new-wrapper and iframe and delete the old app folder. Then, we will have the whole project in the React framework ✨
See ya next time.👋