This is an active WIP post. It will get updated as I work through things.
If you missed my previous post, I detail the current State of the Union at Aver where we have over 18 distinct AngularJS 1.6 Single Page Web Applications which make up a single product/site.
UPDATE: Since starting this process, It has been announced that AngularJS development was being sunset and no new feature development would happen. This further pushes me toward migrating to more modern front-end framework and getting us off of AngularJS.
With Us having so many AngularJS applications, and AngularJS 1.6 feeling pretty dated at this point, it's got me thinking..
- So what do we do with future developments, do we still continue using Angular 1.x or do we slowly start migrating to a newer framework?
- What if that framework goes obsolete tomorrow and we have to migrate again to a newer one?
- Is it really feasible to redo the entire app with newer framework?
- Is there a way I can keep my current AngularJs 1.x stack and slowly add new feature releases using newer libraries/frameworks?
- How do I pass data between AngularJs and the new library that I use?
So... I'm in the midst of experimenting with moving from AngularJS@1.6 to ReactJS. By that, I don't mean migrating all of our front-end-micro-services and apps over, but rather coming up with a strategy where our legacy AngularJS apps live side-by-side with the newer ReactJS apps. The plan is that we are able to re-use as much shared code as possible with minimal re-write and in the end, our Users won't be able to tell the difference, except faster page load times!
I have 2 paths I'm currently working on in parallel:
- UI-Framework Re-Write - Re-create our basic UI framework using ReactJS in place of AngularJS. This means an updated Front-End-Seed-App. The end goal would be that all new applications would sit on top of this stack and we'd migrate and re-use as much as possible from the angular stack.
- Strangler Migration - Find a way to start writing react components right into our angular code today. This would allow us to stop writing AngularJS components almost entirely in favor of ReactJS components. Slowly they'd take over more and more responsibility, starting at the fringes and working in towards the core, until all parts of the Angular app become React. This has the added benefit of helping to ramp up the Dev Team to the new techniques in working with React.
I have to say, both paths are going swimingly so far!
Path 1 - Creating a new React Seed Project
Here are the libraries and parts involved:
- create-react-app - Unfortunately I resorted to running
ejectalmost immediately because I was running into issues and it was incredibly frustrating not having access to see and modify things about my webpack configuration. Here are some of those things:
- Less Loader - I have worked with LESS for so long and I wanted to continue using it for this project. Unfortunately my webpack builds were failing because I didn't have a LESS loader configuration in place. I have since then learned about styled components and in retrospect It would have been pretty simple to continue without less.
- I needed my react apps & the webpack dev server to run on URLs other than root. If you can remember from my previous post we run 18+ SPAs on a single domain. So my react app would need to run at
http://localhost/react-app/for instance. This was causing me issues since I needed to be able to set the
output.publicPathmy the webpack configuration.
- I can't remember why but something wasn't working so I needed to swap out an alternate client for Webpack dev server so I swapped:
- Bootstrap CSS - We have a custom bootstrap css library that I was able to plug in with minimal work. This was great b/c it meant that our React App would look and feel like our AngularJS Apps.
- Bootstrap-React - Pretty similar to the
Angular-Bootstraplibrary we're using in the legacy stuff. It basically takes all of the components that have interactions that would typically require JQuery (Typeaheads, tooltips, etc) if you were using stock bootstrap, and wraps them in whatever library you're using. In this case React & Angular respectively.
- React-Router - A basic page router
- React-Redux - Using this to replace our $rootScope and full-application state.
- First up I got the create-react-app setup and implemented
React-Routerworking to serve 2 distinct pages.
- I then ported over the basic UI layouts so the header and sidebar looked and felt identical to the AngularJS apps and setup the page structure so that they were used on all of the child routes/pages. These components were completely static at this point though.
- I then tried to wire up the click events on the hamburger icon in the header that would trigger the side-bar opening. I immediately ran into the need for global application state so I implemented React-Redux. I'm sure I could have got this particular one to work without Redux but I knew I'd eventually need an app-wide state so I went for it.
- I then got the entire app incorporated into a docker container, integrated w/ our local dev tools, and deployed locally alongside all of our existing apps. At this point I could click between angular & react applications and it felt like it was truly a part of things. This also enabled me to make calls to our APIs at
http://localhost/api/your-api-here/methodwithout having to deal w/ CORS issues.
- Since I could now make APIs I wired up the header to pull for the current logged in user, the applications available, and notifications. Basically all of the dynamic content that appears in the header.
There's still so much to do and figure out before this app will achieve parity compared to the features and services that our AngularJS seed app offers but it's well on it's way and the basic application structure is in place.
This will grow and get more complicated as I go, but for now that's what I've got.
Path 2 - Plugging ReactJS components into AngularJS
Alongside path 1, I've been also looking into writing React components that could plug right into the AngularJS Apps. This will be super important as part of the migration. For instance when I get the header completely re-written in React, I'm not going to want to try and maintain 2 different headers simultaneously. Instead I could re-write it in react and then implement that single React header in both the React & AngularJS Apps. This would allow me to delete the old AngularJS-only version.
Following this idea I was able to pretty quickly replace an old AngularJS page with a ReactComponent page and I'll show that here.
Here's what the original page definition file structure looked like.
The files are described as follows:
- project-list.html - The HTML Template file which will mostly be re-written because it contains a bunch of angular directives/filters/services like
ng-repeatbut the basic HTML structure is all still valid.
- project-list.js - The Angular route definition and page controller. We use the controller for all of the template interactions but try to not put any business logic here or any API calls.
- project-list-logic.js - The Angular Service which handles all of the business logic for the project-list page
- project-list_test.js - The Jasmine/Karma test file for the page component
- project-list.less - The styles specific for this page.
Here's roughly what the controller looked like prior to the re-write. This is just an AngularJS Module definition with a config function that sets up a route to a new controller definition:
And here's where I'm at now. I knew I'd need to keep some of the Angular parts of this, such as the angular route definition so I started there.
ProjectList.js - The replacement top-level piece which connects my React component into AngularJS.
ProjectListContainer.js - The React Container Component definition.
ProjectList.js - The UL list
ProjectListLi.js - Each LI representing a project
The real magic here to take note of is the
react2angular portion of things. It allows me to create an Angular component which is really a ReactJS component and more importantly it allows me to inject angular services into the react component which end up as properties on
this.props. This was great for being able to call and re-use all of my existing AngularJS code including the logic file that I was using on the legacy version of the page.
I plan on taking this idea and building on it to allow me to write react apps on top of our AngularJS Seed app that we already have. This will allow me to get the all rolling on React before Step1 from above is completed.
What's the catch?
There's always a catch. Currently I've run into two issues with the react2angular library.
- Transclusion is not supported. One of the first components I tried to rewrite was our sidebar which takes advantage of transclusion. It was a bummer to get road blocked so early but since we rarely use transclusion I decided its still worth moving forward on even if we have to support 2 versions of the side-bar, which rarely gets updated.
- I'm having trouble nesting angular components inside of my react components. I know it's starting to sound like Library-Component-Inception.. What I wanted to do was what I did above, but then throw a tag like
<my-angular-component/>into my React JSX. I though, since the component definition was included that I'd be able to drop that directed/component tag in and it would get populated w/ the backing component. This wasn't the case. :(
I'll be trying to keep this page updated with any notes or fun stuff I find along the way and document the progress along the way.