AngularJS to Angular Migration in a Month with Claude Code

by Karin Huber

AngularJS to Angular Migration in a Month with Claude Code

The time cockpit web client is twelve years old. Since 2018 it had run two frameworks in the same page, AngularJS and Angular, joined by a bridge that was meant to be temporary. In August 2026 we completed this AngularJS to Angular migration within a month, with Claude Code involved in almost every commit. This article describes what that involved in practice: the five steps that had to happen together, how the work was split between people and two AI models, what changed in build time, bundle size and test coverage, which features shipped in the same release, and what the use of Claude Code would have cost at API list prices. All figures come from the repository, the build output and the Claude Code transcripts. How they were measured is explained at the end of the article.

25
days of work
233
commits, 218 with Claude
45 s
build, down from 373 s
+146%
test cases, 542 to 1,331
€4,250
API equivalent

What changes for you as a time cockpit user

  • The app starts faster. The browser downloads 38% less before time cockpit is ready.
  • New features in the same release: advanced list filters across up to five levels of relations, validation errors shown on the form cell that caused them, hyperlink columns and recently used entries in relation pickers.
  • Your customizations were untouched. Customer-specific extensions and scripts: 0 lines changed. In time cockpit, customizations live outside the product code, see customizable time tracking.
  • More automated tests. The number of automated tests more than doubled.

Every new feature is listed in the release notes for September 2026. The rest of this article is for readers who want to know how the migration was done technically.

The starting point: an AngularJS and Angular hybrid since 2018

time cockpit is a time tracking and project management platform. Companies use it to record working time, plan projects and bill clients. ASP.NET backend, Angular web client, opened in a browser every working day.

One detail shaped the whole migration: time cockpit is model-driven. Customers do not get a fixed set of screens. They define their own entities, validation rules, list views and forms in scripts, and the web client renders them. Forms are assembled at runtime from cells: a date cell, a duration cell, a relation picker, a color cell. Our customizable time tracking page shows what customers build on top of that.

That is what made the migration hard. You cannot rewrite screen by screen and tick them off, because the screens belong to the customers and we have never seen most of them. Every cell type has to keep behaving exactly as it did, for data models we cannot enumerate.

How it became a hybrid

The web client started in July 2014, in AngularJS. In January 2018 we added Angular 5 alongside it using @angular/upgrade, the official bridge that lets Angular and AngularJS components share one page.

The bridge is meant to be temporary. Ours lasted eight and a half years. On top of that, long-term support for AngularJS ended on 31 December 2021. Since then the old half has received no security fixes, and any vulnerability would have been ours to patch. New work went into the Angular half, the AngularJS half was maintained but never finished, and the cost compounded quietly:

  • The AngularJS build output was committed to the repository: 39.71 MB of it, including a vendor.js of 15.5 MB and 378,725 lines, plus an 18.3 MB source map. Every clone downloaded it.
  • Two build systems. Two test runners. Two ways to write a component.
  • ESLint had been silently broken long enough for a substantial violation backlog to accumulate unnoticed.
  • Every new feature began by asking “which half of the app does this go in?”

In August 2026 we dissolved the hybrid. The starting point was the version deployed on 3 August. The last AngularJS code came out on 13 August; the release around it was finished on 3 September.

The AngularJS to Angular migration in five steps

Deleting AngularJS turned out to be the smallest of five things that had to happen together, because each blocked the next: you cannot drop the bridge without upgrading Angular, cannot upgrade Angular without changing the build, cannot change the build without replacing the test runner.

FromTo
Compatibility bridge@angular/upgrade, AngularJS componentspure Angular, one bootstrap
FrameworkAngular 21.2Angular 22.1.2
Monorepo toolingNxplain Angular CLI
Bundlerwebpackesbuild
Test runnerKarma/Jasmine + JestVitest

Riding along: the Kendo UI component suite v24 to v25, Microsoft’s MSAL authentication library v5 to v6, Font Awesome 4 to Font Awesome Pro 7, and the removal of Storybook, @angular/flex-layout and two abandoned notification and hotkey libraries. The dependency list also contained four packages that did nothing at all: i, install, force, and npm itself, listed as a runtime dependency.

Three whole projects left the solution: the AngularJS host application and its two test projects. A fourth, the ASP.NET assembly that used to serve the AngularJS frontend, survives as backend-only code, down from 165 files to 104.

And it still shipped features

A framework migration is easy to justify internally and hard to justify to customers, so this release was meant to contain changes customers would notice too. 34 of the release’s 233 commits are feature work, not migration, and just over a third of the code written in that month came from them.

The largest is advanced list filters: property/operator/value rows built against a list’s underlying entity, traversing up to five levels of relations, saved per user alongside their column and sort settings. It brings the web client to parity with our desktop client, and arrived with a redesigned filter area shared by lists, dashboards and the vacation calendar.

Next largest, across 90 files, is validation. Server-side errors are now shown on the individual form cell that caused them, and only once you have touched that field or tried to save. No more forms turning red before you have typed anything. It was built test-first: two commits of deliberately failing tests before any implementation.

Also new: hyperlink columns with typed filter parameters, so a row in one list can open another list, or a dashboard, pre-filtered by that row’s values; a client-side configuration cache with prefetching, so the first form you open needs no extra round trip; file actions that download or open inline; and a recently used section on relation pickers, which keeps your last five picks per field in browser storage.

Feature work carried its own tests: 62% of what those 34 commits wrote is test code.

How the work was split between Claude models

Almost every commit in the release was produced with Claude Code, but not by one model working alone.

The repository carries an AGENTS.md, instructions for AI coding agents checked in beside the code, which states the division of labor explicitly: one model for exploration, architecture, planning and review; a second, dispatched as a subagent, for mechanical implementation and running tests.

The transcripts confirm that split. Claude Fable ran the main sessions: 7,024 of 9,774 main-session requests. Claude Opus 5 ran the subagents: 13,088 of 16,195. Planning and review sat with one model; execution went to another, in parallel, with a fresh context each time. The token chart further down splits both by version (Fable 5 and 5.1, Opus 4.8 and 5).

The work was organized into 95 named work streams, each a scoped and separately reviewable unit rather than one open-ended instruction: tear down the bridge, remove Nx, migrate component inputs to signals, speed up the end-to-end suite, and 91 more. Across them: 80 sessions, 564 typed prompts, 320 subagent dispatches, 33,915 tool calls.

Most of that scaffolding predates the release. The repository already carried written procedures for its recurring chores, such as how to add a translation or how to test a new cell type, so those never had to be re-explained. Having the rules written down for agents beforehand was the precondition for 233 commits in a month. It is the same idea we described for customizations in Customizable Standard Software Meets AI: an AI agent works well when the system describes itself.

Facing an Angular migration, or introducing AI coding agents in your team?

time cockpit is developed by software architects gmbh. The approach described here, small reviewable work streams, written rules for agents and tests before the switch, is the same one we use in customer projects and teach in our trainings. See custom projects and talks and trainings.

The numbers: commits, lines of code, signals

Commits per day during the AngularJS to Angular migration, peaking at 28 on 6 August

233 commits across 25 days of work, weekends included, 218 of them co-authored by Claude. The peak was 28 in a single day, with a sustained run of 17 to 27 a day through mid-August.

The raw diff is 1,897 files, +1,284,122 / −1,836,172, but most of that is machine-generated. Three blocks dominate: the 438,039 lines of committed AngularJS build output, 1.22 million lines of locale data that were merely reformatted, and dependency lockfiles.

Diffs are a poor way to size a release anyway: edit a line and git records it as one deletion plus one addition, so a heavily reworked file looks like a rewrite. Simpler to count the code that exists before and after, generated files excluded:

Source lines, generated files excludedbeforeafterchange
Angular frontend57,08869,388+12,300
Angular unit tests2,76712,009+9,242
Angular end-to-end tests9,46816,396+6,928
Model-driven test scripts7,44910,682+3,233
.NET backend111,946110,569−1,377
AngularJS frontend4,6600−4,660
Legacy test projects9,6970−9,697
Customizations and tooling32,30732,3070
Total235,382251,351+15,969

Two rows need a word of explanation. “Model-driven test scripts” are test data models written in the same scripting language customers use to define their own entities and forms. “Customizations and tooling” covers customer-specific extensions and build tooling that the migration did not touch.

Two rows go to zero, which was the goal of the migration. The rest is the shape of the trade: the Angular frontend absorbed the work of both halves and grew by 12,300 lines, while 14,357 lines of AngularJS and its test projects disappeared entirely.

The notable line is the split between the two: product code grew by 6,263 lines, test code by 9,706. More new code went into testing the product than into the product.

Net change hides how much was actually written, though. Blame the final code line by line and 48,794 of the lines standing today were written during this month. The gap between that and the +15,969 net is old code that went away. Of those, 18,238, just over a third, came from the new features, the other 30,556 from the migration. The feature share leans hard on tests: 11,373 of its 18,238 lines are test code, against 6,865 of product code.

Angular migration: decorator-based inputs and outputs fell from 266 to 7, signal-based rose from 145 to 485

Modernization shows up most clearly in how components declare their interfaces. Angular has been moving from decorators to signals, a reactive primitive that makes data flow explicit and lets the framework skip work it knows cannot have changed. Decorator-based declarations went from 266 to 7; signal-based ones from 145 to 485.

webpack to esbuild: build time and bundle size

We built both versions on the same machine and measured the output:

before (webpack + gulp)after (esbuild)
Production build373 s + 72 s45 s
Initial download, gzipped2.17 MB1.34 MB
Whole output folder125.54 MB, 2,322 files61.53 MB, 1,006 files

An 8× faster build, and 38% less for a browser to download before the app starts.

Total shipped code barely moved (the application does the same things), but the new bundler splits it far better: 0.7 MB gzipped moved off the critical path into chunks that load on demand. Where the old build shipped one 7.49 MB JavaScript file, the new one splits the same code across 25 files, of which six are needed to start.

The extra 72 seconds in the first column deserves a note: producing the old release meant running a second, entirely separate toolchain to emit the AngularJS bundles the page also loaded. That build no longer exists.

What made the migration safe: tests, pinning, lint budget

Three decisions were decisive.

Tests grew faster than the code.

Test cases during the migration from 542 to 1,331 while product code grew 3.6%

Test cases went from 542 to 1,331: unit tests from 123 to 626, browser-driven end-to-end tests from 419 to 705. Because the product renders customer-defined models, many of those tests work by defining a small synthetic data model and asserting how the UI renders it; that catalog grew from 28 models to 43. Product code grew by 3.6%; the number of test cases grew by 146%. That ratio was the precondition for replacing the framework of a product in daily use.

It caught things, too. Excel export in list view had been silently unwired at some earlier point in the migration and nobody had noticed. So had the start page: for customers whose configured start page is a list, form or import page, opening the app at / no longer took them there. The teardown had removed the AngularJS redirect that handled this and kept a three-route allow-list that had only ever supplemented it. Both are fixed, and neither would have been found by reading the diff.

The upgrade was not allowed to change behavior. Angular 22 changes the default change-detection strategy, a silent, sweeping behavioral change across every component if you simply accept it. So the version bump did not. Every existing component was explicitly pinned to the old behavior, and the upgrade shipped as a pure version change.

Only then was change detection modernized, as separate work, in batches: make one family of components safe under the new strategy, then flip them. Components that could not be flipped yet kept their pin and a comment explaining why: // stays Eager: template reads mutable ApplicationService fields. On 14 August the last 24 pins came out in one commit. Every component now runs the modern strategy, and it got there in eleven reviewable steps rather than one flag flip.

The lint backlog was ratcheted, not bulk-fixed. Rather than one enormous unreviewable formatting commit, the existing violations were frozen as warnings with a per-project budget: 781 for the core library, down to zero for the main application. Errors must stay at zero, and any new warning fails the build. Touch a file, fix its warnings, lower the budget.

What the migration cost in Claude Code tokens

Claude Code output tokens by model for the AngularJS migration: Fable 5 6.65 M, Opus 5 2.37 M, Opus 4.8 0.92 M, Fable 5.1 0.37 M

Across the release, Claude Code processed roughly 4.34 billion tokens. Most of that was not new text, though: 4.23 billion of it, 97.4%, was cache reads, the same project context re-read cheaply request after request rather than recomputed. The more meaningful figure is what was new: roughly 110 million tokens, of which 10.31 million were output, the code, tests and commit messages actually written.

Spread over 25,969 requests and 25 days of work, weekends included, that is roughly 412,000 output tokens a day. What survived review and still stands in the repository is far smaller: about 1,950 lines a day. Most of what a model produces is exploration, scaffolding and discarded attempts. The gap between those two numbers is why the human review step is not optional.

Priced at Anthropic’s public API rates, that traffic comes to about €4,250 ($4,926, converted at the ECB reference rate of 0.8637 on 2 September). Split by price component:

EURshare
Cache reads2,75965%
Cache writes1,11926%
Output3749%
Input30.1%
Total4,255

Nine tenths of the bill is context, the codebase being read and cached, not the code being written. Per unit of delivered work that is roughly €18 a commit, or nine cents per line of code still standing.

One qualification: Claude Code on a subscription plan is not billed per token, so this is not an invoice anyone received. It is what the same traffic would have cost through the API at list price, which is the only figure that can be compared against a contractor rate, a license, or a month of anyone’s time.

What is still open

The hybrid is gone, but the modernization is not finished. Sixteen legacy NgModules still wrap components that could be standalone. The lint backlog is real and is being paid down file by file. And the app still runs Angular’s zone-based change detection; going zoneless is the next step, long parked on a third-party grid component and now due a re-check.

A month bought us one framework instead of two, one build system, one test runner, a build that finishes in 45 seconds instead of six minutes, a repository 39.71 MB lighter, and a release with visible changes for customers, because the filters and the validation shipped alongside it. Eight and a half years of “we’ll finish the migration later”, then an AngularJS to Angular migration closed out in a month. What remains is ordinary maintenance work.

It is also the part that is easy to underestimate when a company considers building its own time tracking software with AI. The first version is the cheap part. Keeping it current for twelve years, through framework changes like this one, is where the real cost lives. Why we recommend adapting standard software over a rewrite is covered on our page on building or customizing your own time tracking software.

Frequently asked questions about AngularJS migration

What is the difference between AngularJS and Angular?

AngularJS is version 1.x, released in 2010, a JavaScript framework built on scopes and two-way data binding. Angular, from version 2 in 2016, is a rewrite in TypeScript with a component architecture, today with signals and standalone components. Support for AngularJS ended on 31 December 2021, while Angular still ships a new major version every six months. The @angular/upgrade bridge lets both frameworks run in one page, intended as a transition, not a permanent state.

Is it still worth migrating from AngularJS to Angular?

Yes, for two reasons. AngularJS has received no security fixes since 2022. And a hybrid costs continuously: in our case two build systems, two test runners, 39.71 MB of committed build output and, for every feature, the question of which half it belongs in. After the migration the application builds 8× faster, the browser downloads 38% less, and there is one bootstrap, one framework and one test runner.

How long does an AngularJS to Angular migration take with AI assistance?

For us, 25 days of work including weekends, for a hybrid of about 4,660 lines of AngularJS, two test projects and a web client with 57,000 lines of Angular code, including the switch from webpack to esbuild and from Karma and Jest to Vitest. The duration depends less on the amount of AngularJS code than on how well the existing application’s behavior is covered by tests.

What does an AngularJS migration with Claude Code cost?

The API equivalent of the processed tokens came to about €4,250 for 233 commits, roughly €18 per commit. Nine tenths of that is reading and caching the project context, not writing code. On top comes the human time for planning, review and testing, which makes up the larger part of the actual cost.

How do you migrate without changing behavior for users?

Three rules proved themselves: tests grow faster than product code, in our case from 542 to 1,331 test cases. Framework upgrades change no behavior, every component is first pinned to the old behavior and only then modernized in small, separately reviewable steps. And legacy such as lint violations is paid down with a budget, step by step, instead of in one huge unreviewable commit.

How these numbers were measured: line counts are a straight count of the files present on each version (not a diff), with generated, vendored and locale files excluded. The feature/migration split comes from git blame over the final code, counting only lines that survived and attributing each to the commit that last wrote it; the 233 commits were classified as feature or migration work by hand, so that half of it is a judgment. Token figures are aggregated from this repository’s local Claude Code transcripts (81 main-session and 660 subagent files), deduplicated by request id, restricted to 4 August to 3 September, and excluding the sessions that wrote this article. Build and bundle figures come from building both versions on the same machine and measuring the output.

Karin Huber
Karin Huber
Founder, CEO

Karin is co-founder of time cockpit and responsible for the design and implementation of its user interface.

More about the team