Seamlessly addressing style debt in a large Angular application using themes and feature flags
The patient chart in One Medical’s Electronic Health Record system is our most feature-heavy in-house single page application. It is currently undergoing an upgrade from AngularJS to Angular, with a migration strategy that involves rewriting product features in angular and downgrading them to AngularJS at runtime. Throughout this transition, we aggressively tackled various flavors of tech debt including state management complexities, nested forms, routing and UX issues, etc. We also setup an internal component library and documented these reusable components in storybook. Somewhere in the mix of all these things, the related style debt did not receive much attention.
About half way through the migration, the design team requested a move to a standard grid system and a different font and scale which would support a greater design system endeavor. Naturally, such a challenge midway through an epic migration project seemed like just the right amount of fun to tackle. So began our journey into addressing style debt.
Technical Challenge & Opportunity
The opportunity to address the style debt was an interesting but vague one. On the surface level, the SASS implementation in the code base seemed reasonable. There were SASS variables, extensions, helper mixins, etc. On the angular side of things, we had components that simplified parts of the application into smaller pieces and encapsulated the UI features. The effort for this project wasn’t an easy one to gauge, but an initial spike to dive into the css to learn more helped plan out the project.
Contrary to the recommended best practices of angular components, the style in the application was held together in a global css styling approach of pre-angular applications. Not only did this make the styles and DOM structure fragile, it was tough to change existing style and DOM structure without the commit size growing and the confidence level in the quality dropping. Part of the risk factor was also the lack of a full suite of e2e tests to cover all feature interactions in the system.
The style debt wasn’t as bad as a Pandora’s box but there were some 👻👻👻surprises along the way which would prevent future scaling of the application style into areas of responsive layout and font sizing. Some of these were:
- Massive amounts of nested height calculations
- Non-standard body font size
- Global line heights to determine padding
- Mix of px, rem, percentage and rem calculated values
- Global line heights to determine padding were some of the ones which would prevent future scaling of the application style into areas of responsive layout and font sizing.
Was it worth fixing some of these issues or all of them? How were we going to ship this code for alpha testers without breaking changes to others? The more difficult question was, how do we create reviewable PRs, ensure quality, and iteratively ship code that changes the layout and style plumbing of the application? While this style debt cleanup wasn’t part of the original request, it was an opportunity to address a bigger issue in the application.
How do we create reviewable PRs, ensure quality, and iteratively ship code that changes the layout and style plumbing of the application?
Chosen Solution
One thing that has been clear in our migration efforts is that feature flagged delivery coupled with a period of alpha testing with users in production has resulted in smoother releases of complex features. We decided that shipping incremental changes behind features flags to achieve the font and grid system (henceforth interface) update was the way to go.
One thing that has been clear in our migration efforts is that feature flagged delivery coupled with a period of alpha testing with users in production has resulted in smoother releases of complex features.
We started with the idea of themes to determine which interface would be visible. Cory Rylan’s article on Theming Angular apps with CSS Custom Properties was a great starting point. Using CSS variables instead of sass variables to hot swap themes was a requirement (for debugging and comparing themes) and a bonus for future customizability. We extended the service to maintain an RxJS BehaviorSubject and to subscribe to an NgRX store to relay the latest theme.
We built upon the theme strategy by using a custom directive to listen to the theme defined in the service and apply custom classes and properties to a DOM level element.
This allowed us to use a combination of inherited styles as well as inject styles into components that used angular style encapsulation. Now we had a recipe to use to deliver incremental changes across the application!
TL;DR
By using a combination of themes, directives, layout components, feature flags, and CSS variables, we were able to systematically apply theme-based rendering of components. This allowed us to deprecated the old styles into an older theme and use a new theme to define the newer styles. We additionally introduced layout components to reduce the cognitive overhead of large components that tackled a container component’s responsibility as well as DOM structure and style responsibility.
Code Sample
To showcase our solution, we’ve setup a stackblitz project that shows the theme service and directive working together to swap the interface.
Future State
The theme switching infrastructure put in place has allowed us to complete the Font and Grid Upgrade project. The old styles that we have deprecated can be deleted safely once we deprecate the theme in the angular code. The infrastructure has received good feedback so far and the design team has already inquired about experimenting with colors and accessibility projects using this solution! To be honest, the solution was for getting us to Dark Mode (which was the main goal all along 😎).
Special thanks to Josh De Gouveia in helping me bounce ideas off for this blog and for code pairing with me through the Font & Grid Upgrade project.
Thanks to my Angular migration team coworkers at One Medical, Pamela Ocampo & Timothy Kutnick, for keeping that bar high 🏋️♀️, and to our designer Brittany Staten for all design related support!