Behind the Scenes of an Angular Upgrade
Upgrading Angular in a sophisticated application is far more than a routine version bump; it represents a deliberate architectural choice with far-reaching consequences for your entire codebase. This case study walks through our experience executing a multi-version migration for a modular B2B healthcare platform.
Below, we examine the planning phase, execution strategy, and post-migration stabilization efforts that allowed us to keep development moving forward while safeguarding product quality. For teams weighing a similar migration or questioning whether the timing is right, this account offers practical reference points for making well-grounded decisions.
Context: The Project and Its Industry
Our work centered on a B2B web platform serving the healthcare sector, functioning as a bridge between hospitals, clinics, and medical equipment suppliers.
At the outset of our migration in 2023, the application was built on Angular 14, which had already reached end-of-life status. Our objective was to move to Angular 17, the newest stable release available at that point, to secure sustainable maintenance and unlock capabilities like Signals, the Standalone API, and the modern control flow syntax.
The frontend was overseen by four dedicated frontend specialists and two full-stack contributors, all operating within a broad, modular repository. As feature work continued in tandem, our migration plan needed to be deterministic, minimize risk, and support incremental validation and deployment.
This strategy proved its worth when we subsequently moved to Angular 19 with little resistance, demonstrating that the groundwork laid during this migration continues to yield returns.
Why We Moved Away from Angular 14
Embracing Angular 17 was a calculated move toward a more responsive, dependable, and forward-compatible offering. We had pressed Angular 14 to its limits, and remaining on an outdated foundation would have constrained our progress.
From a commercial standpoint, the advantages were evident: accelerated feature rollouts, reduced dev cycles, fewer regressions, and improved day-to-day efficiency for the entire team. The technical payoffs included measurable performance gains, cleaner scalability, and a robust groundwork for what is to come. Onboarding became simpler for new engineers, and future dependency updates carried noticeably less risk.
These outcomes were driven by several pivotal enhancements shipped across Angular versions 15 through 17:
- Signals (introduced in Angular 16): delivered more deterministic and efficient reactivity, proving indispensable in intricate sections of the application.
- Standalone API (stable as of Angular 15): enabled us to streamline our module organization and cut down on verbosity.
- New control flow syntax (launched with Angular 17): produced more concise templates that are simpler to interpret.
- Performance gains: quicker builds powered by esbuild, coupled with first-class SSR support, expanded our toolkit for optimizing application speed.
📘 For additional context on Angular 14-20 improvements touching productivity, user experience, developer experience, and performance, feel free to download our free ebook, the “Ultimate Guide to Angular Evolution”.
Obstacles We Encountered
Though the transition to Angular 17 produced solid outcomes, the journey was certainly not without its share of complications. Like any significant refactoring effort, we had to confront a series of technical and operational hurdles.
Where the project stood
The application was rooted in Angular 14 with Angular Material handling the user interface layer. Its footprint was substantial and inherently modular, spanning various business functions—meaning a localized alteration had the potential to trigger unforeseen consequences elsewhere.
Complexity of the existing architecture
The system's structure introduced its own set of complications. Our setup leveraged Nx for monorepo management, NgRx for complex state orchestration, Storybook for isolated component testing, and a bespoke design system extending Angular Material. This was a potent combination, yet one that called for measured deliberation. Each significant jump had to be vetted for compatibility issues across a sprawling matrix of components and services.
Impediments during the migration
A primary obstacle was the sheer scale of the codebase. Every adjustment demanded careful verification against numerous business domains and modules to avoid introducing subtle regressions.
Substantial work went into aligning Angular Material with the new MDC-based component architecture introduced in version 15. Given the extensive customizations applied to our UI, the automated migration tools were not a reliable path. Our approach was to defer this particular task until after the Angular 15 upgrade to mitigate risks, then progressively convert the Material components while keeping the system stable and development unmoved by disruptive updates.
Furthermore, we contended with a range of third-party package incompatibilities. Numerous libraries had not yet aligned with the newest Angular versions, which necessitated manual interventions—whether through direct updates, applied patches, or code adjustments to accommodate breaking changes. This specific phase demanded heightened vigilance and comprehensive testing to confirm that no functionality was inadvertently compromised.
Executing the Upgrade
Given the jump across multiple major releases (Angular 14 through 17), a single, sweeping migration was deemed too risky from the outset. Our strategy was to proceed with an **incremental upgrade**, moving **one major version at a time**. This approach was designed to contain potential problems, make troubleshooting more straightforward, and keep the overall effort under control.
Preparation and Initial Planning
Auditing the changelog and dependencies
Our first step was a thorough examination of the official changelog and the breaking changes for each version in our path (15, 16, and 17). Simultaneously, we audited our **third-party dependencies** for compatibility, identifying which ones needed updates or replacements to work with the target versions.
Timing and resource allocation
Coordinating the upgrade timeline was critical, as several developers were active on the codebase. We deliberately scheduled the migration for a **sprint where the frontend work was ahead of the backend** and **no major features were in active development**. This window allowed us to perform the upgrade without conflicting with the main product roadmap, while other team members could continue with smaller, independent tasks.
Although a single developer could have managed the workload, we allocated two people to the task. This sped up the detection of regressions and the resolution of build failures, visual glitches, or failed unit tests.
———————————-
Related: Planning an Angular Upgrade: Key Steps & Risk Management
———————————–
Preliminary Analysis
A significant portion of our preparation centered on **Angular Material’s shift to MDC-based components**, which we identified as the most challenging aspect of the entire project. Our initial plan was to first get the application onto Angular 15 while retaining the legacy Material components. This allowed us to separate the version bump from the more complex UI component migration.
Our deliberate choice was to **avoid skipping versions**. Following each major update, we conducted extensive testing and monitored for regressions. This disciplined approach made it easier to pinpoint and resolve problems, keeping the process predictable and reliable.
Tools That Facilitated the Migration
Our toolkit was a mix of official Angular resources and Nx-specific features. The Angular Update Guide was instrumental in outlining the breaking changes, deprecations, and recommended actions for each version.
Because our project is built on Nx, we utilized the nx migrate command. This tool streamlined the process of updating dependencies, executing codemods, and applying changes in a controlled and traceable manner.
The Version-by-Version Process
We kicked off the process with the move to Angular 15. Following an initial assessment, we leveraged **Nx migrations** to handle the bulk of the work. Nx efficiently updated the core Angular and NgRx packages, as well as adjusting configuration files and code where required.
A typical upgrade cycle involved the following steps (with steps 3 and 4 automated by Nx in our case):
- Updating the Node.js runtime if the target Angular version required it,
- Upgrading Nx to a compatible version to ensure tooling alignment and access to its migration utilities,
- Updating core packages like @angular/core and @angular/cli,
- Updating NgRx to a compatible version,
- Checking and updating all third-party libraries to versions confirmed to be compatible with the new Angular release,
- Resolving any build errors that surfaced from breaking changes or configuration tweaks,
- Running linters and the test suite to validate code standards and ensure nothing was broken,
- Launching Storybook and the main application for visual inspection and regression testing.
We concluded each cycle with a verification of all package versions to confirm we were on the latest stable releases. We also cross-checked our workflow against the official Angular Update Guide to ensure no steps were missed during the automated process.
We strongly recommend **committing the changes for each phase separately**. This practice simplifies tracking, makes debugging regressions much easier, and helps isolate the specific changes related to each upgrade step. **Nx migrations** are particularly helpful here, as they can stage changes to facilitate logical, commit-friendly groupings.
Handling the Angular Material Transition
With the app on version 15, we turned our attention to the UI layer and began migrating to the new MDC-based Angular Material components. This proved to be the most intricate part of the journey, a challenge likely familiar to teams working with established codebases that rely heavily on Material.
To manage the complexity, we adopted a strategy of **migrating one component at a time**. Many components required minimal effort, allowing those updates to be seamlessly integrated into our standard sprints as low-risk tasks. This incremental method made it simple to verify changes, reduced the likelihood of introducing bugs, and made it easy to spot any deviations in the UI's look or behavior.
Because these updates were self-contained, code reviews were easy, and debugging remained straightforward. This approach enabled us to keep building new features without significant disruption. It was fundamental to modernizing our UI without creating chaos in the development workflow.
After the MDC transition was complete, we resumed the Angular upgrade path, **moving to version 16 and then to 17**, using the same incremental approach that had been successful earlier.
Separating Feature Adoption from Version Bumps
There is a strong temptation to adopt new Angular features during a version upgrade, but it’s vital to treat feature adoption as a distinct undertaking. Most modern Angular features are supported across several recent versions, so there is rarely a pressing need to adopt them immediately.
Mixing feature work with a version upgrade can bloat the number of changed files, making it difficult to differentiate between changes required for the upgrade and those for a new feature. Keeping the scope limited to the update itself simplifies code review and improves traceability. Once the new version is stable, new features can be adopted more deliberately in subsequent, focused tasks.
We embraced an incremental migration strategy to modernize the codebase without halting feature work. When a task involved modifying a component, we would assess whether it was a good opportunity to adopt newer patterns like standalone components, the new control flow, or Signals-based inputs, outputs, and queries—provided it didn't add excessive noise to the feature’s primary purpose.
During quieter periods, we carved out time for dedicated migration sessions, sometimes focusing on an entire module at once. Angular’s built-in schematics were essential here, enabling us to adopt modern APIs efficiently. This combined strategy helped us evolve the codebase while keeping the velocity of feature development high.
Timeline of the Angular Upgrade
The initial upgrade from Angular 14 to 15 was not technically demanding. The majority of the time was dedicated to regression testing to ensure everything worked correctly after the update. For an application of this complexity, this phase can take a few days, depending on codebase size and the number of dependencies. However, the most time-intensive part was the migration to MDC Material components. This required careful work due to our customizations and the scale of the UI.
Once the Material components were up to date and stable, the transitions to Angular 16 and 17 were comparatively quick. Our step-by-step methodology prevented major setbacks and allowed us to track regressions accurately, ensuring the stability of the platform.
The duration of an Angular upgrade is influenced by numerous factors. We have explored this in more detail in our article “How Long Does an Angular Upgrade Take — and How to Keep Feature Delivery on Track.”
If you are planning an upgrade and want a clearer picture of the time and effort it might take for your project, fill out our short survey and we will provide a personalized report to aid your planning.
Outcomes and Impact
The upgrade enabled us to modernize the application without bringing feature development to a halt. This section details the technical and business results of the migration, the benefits realized, and the overall timeframe for the project.
Technical and Business Outcomes
On the technical side, we achieved a clean codebase on Angular 17 that is significantly easier to maintain and extend. This modernization set the stage for more efficient work. In our case, we were later able to adopt standalone components, Signals, and the new control flow, which **made development simpler and improved runtime performance**.
From a business perspective, the upgrade **improved long-term maintainability and lowered technical debt**. With the faster builds enabled by esbuild and improved tooling, development velocity has increased. A modern, stable foundation gives us the confidence to **deliver features more quickly** and helps with **onboarding new developers**. Ultimately, this modernization **cuts maintenance costs and reduces the risk of issues** related to outdated dependencies.
Key Takeaways
A primary lesson was that **planning and preparation pay off significantly**, particularly for multi-version upgrades. We managed to avoid most surprises by breaking the work into smaller, manageable steps, verifying third-party compatibility early in the process, and aligning the timing of the migration with the development cycle.
We also learned that complex elements such as Angular Material should be addressed gradually and ideally tackled early. Migrating to MDC components as a separate phase meant we weren't combining two major sources of change at the same time.
Finally, the deliberate spacing between major upgrades provided critical time to catch regressions and ensure stability, preventing the all-too-common scenario of facing a flood of bugs after a rushed, monolithic upgrade.
Guidance for future version bumps
The work doesn't end when the initial migration is complete — in fact, that's where the real momentum begins. After moving the application to Angular 17, updates to newer releases became noticeably faster and less painful. We were able to adopt fresh versions shortly after they shipped, which reduced downtime and ensured the platform kept benefiting from the newest capabilities.
Several practices proved essential to making this process work smoothly:
- Invest in planning: know the full scope of what will change before you start, and map out likely trouble spots ahead of time.
- Break the work into manageable increments: divide the migration into discrete chunks, each with its own verification step, so you can locate and fix issues as they arise rather than hunting through a massive diff.
- Keep feature work out of the upgrade: don’t mix new functionality with the migration. This keeps both efforts cleaner and reduces the risk of subtle bugs creeping in.
- Stay on a regular cadence: incremental version updates keep the project secure and easy to maintain. If you let multiple releases pile up, you'll be back in the same situation — facing a big, risky jump that is far more disruptive and time-consuming than it needed to be.
These habits make the upgrade path predictable and lower the stress of each successive move.
Closing thoughts
Migrating Angular in a substantial codebase is never trivial, but with deliberate planning and a staged approach, it is completely achievable. By being methodical about cross-version transitions and handling the tricky parts head-on, we managed to modernize the platform without sacrificing stability. The result was not just better performance and cleaner code structure, but also a much easier path to adopting future framework versions with confidence and speed.
If you're struggling with a migration, unsure how to begin, or just want a fresh perspective on your situation, get in touch. Angular is our specialty, and we're ready to help — whether you need strategic advice or hands-on execution, we'll guide you through with confidence and clarity. Book a free 30-minute meeting and let's talk about your project.
