Wanna see something cool? Check out Angular Spotify 🎧

Upgrade to Angular 20 from Angular 13 - Part 4: Angular 17 with Claude Code

We are back. In Part 3, we upgraded Jira Clone from Angular 15 to 16 using Claude Code, and the whole thing was mostly just dependency bumps. I mentioned at the end that Angular 17 would be the interesting one. It was.

Angular 17 is not just another version bump. This is the release where standalone components become the default, the new control flow syntax (@if, @for, @switch) replaces structural directives, and there is an entirely new build system available. For the first time in this series, we need actual code changes, not just package.json updates.

1. Approach

Same as Part 3. I gave Claude the previous blog posts and the implementation plan from the Angular 16 upgrade, and asked it to generate a new plan for Angular 17.

Convert to standalone components

The difference this time is that the plan had two phases:

  1. Phase 1: Dependency upgrades - the same proven pattern from Parts 1-3
  2. Phase 2: Code migration - standalone components, standalone bootstrap, and new control flow syntax

Claude produced a plan covering everything from the initial ng update all the way to creating the PR. The full plan is available in the repo.

2. Phase 1: Dependency upgrades

This part was familiar. Same pattern as before:

  1. Run ng update @angular/core@17 @angular/cli@17 --force
  2. Resolve each dependency conflict one at a time
  3. Commit after each step

One nice thing: ng update automatically bumped TypeScript to 5.4.5 and zone.js to 0.14.10 this time, so two tasks from the plan were already done.

The remaining dependencies followed the usual flow:

Package Before (v16) After (v17)
@angular/core ^16.2.12 ^17.3.12
@angular/cli ^16.2.16 ^17.3.17
typescript ~5.1.6 ~5.4.5
zone.js ~0.13.3 ~0.14.10
ng-zorro-antd ^16.2.2 ^17.4.1
ngx-quill ^22.1.1 ^24.0.5
@angular/cdk ^16.2.14 ^17.3.10
@angular-eslint/* 16.3.1 17.5.3
@typescript-eslint/* 5.11.0 6.21.0

For ngx-quill, it was important to stay on v24.x and not jump to v25+. Version 25 requires Quill v2, which is a completely different API. Version 24 supports Angular 17 while keeping Quill v1. That saved us from a much bigger migration.

After resolving all dependencies, npm install ran cleanly without --force. Build passed on the first try.

3. Phase 2: The real work

This is where it got interesting. Angular 17 provides CLI schematics to automate the three major code migrations:

3.1 Convert to standalone components

npx ng generate @angular/core:standalone --mode convert-to-standalone

This schematic went through all components and directives, added standalone: true to each one, and moved the necessary imports from their parent NgModules into each component’s own imports array.

Convert to standalone components

3.2 Prune NgModules

npx ng generate @angular/core:standalone --mode prune-ng-modules

This removed NgModule classes that were no longer needed. In our case, only SnowModule was fully pruned. The other modules like ProjectModule and WorkInProgressModule were kept because they still serve as entry points for lazy-loaded routes.

There was one issue here. The schematic deleted SnowModule but forgot to add SnowComponent to the imports of AppModule. The build failed with 'j-snow' is not a known element. Quick fix, added the import manually.

Prune NgModules

3.3 Migrate to standalone bootstrap

npx ng generate @angular/core:standalone --mode standalone-bootstrap

This was the big one. It converted main.ts from:

platformBrowserDynamic().bootstrapModule(AppModule)

to:

bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(BrowserModule, AppRoutingModule, ...),
    provideAnimations(),
    provideHttpClient(withInterceptorsFromDi()),
    // Sentry, Akita, and other providers
  ]
})

And then deleted AppModule entirely. The schematic handled most of it correctly, but missed the import * as Sentry statement that was previously in app.module.ts. Another quick manual fix.

Migrate to standalone bootstrap

3.4 Migrate control flow syntax

npx ng generate @angular/core:control-flow

This converted all *ngIf and *ngFor directives to the new @if and @for syntax across the template files. For example:

<!-- Before -->
<div *ngIf="isLoading; else content">Loading...</div>

<!-- After -->
@if (isLoading) {
  <div>Loading...</div>
} @else {
  ...
}

The @for syntax requires a track expression, which the schematic handled automatically. No track $index fallbacks needed.

One thing the new control flow exposed: stricter type checking. Two interface classes (IssuePriorityIcon and IssueTypeWithIcon) had their value field typed as string when the methods consuming them expected enum types (IssuePriority and IssueType). The old *ngFor was lenient about this. The new @for was not. Fixed by updating the type definitions to use the correct enum types.

4. ESLint cleanup

The @angular-eslint v17 removed the ng-cli-compat config that the project was using. Replaced it with @angular-eslint/recommended and @typescript-eslint/recommended. Also had to upgrade @typescript-eslint/* from 5.11.0 to 6.21.0 because the old version did not support TypeScript 5.4.

ESLint cleanup

5. Result

Angular 17 migration result

Application compiles and serves without errors.

6. Source code

https://github.com/trungvose/jira-clone-angular/pull/110

7. What I learned

The Angular CLI schematics for standalone migration and control flow are surprisingly good. They handled 90% of the work automatically. The remaining 10% were edge cases: a missing import here, a stricter type check there. Nothing that took more than a minute to fix.

The two-phase approach worked well. Phase 1 (dependency upgrades) followed the exact same pattern from the previous three parts. Phase 2 (code migration) was new territory, but the schematics did the heavy lifting.

What surprised me was how much code changed. If I had done this manually, it would have been a full day of tedious find-and-replace work. The schematics turned it into a few commands.

One thing worth noting: the Storybook migration (v6 to v7) was intentionally skipped. Storybook 6.x still works with Angular 17, just with some peer dependency warnings. The v6 to v7 upgrade is a separate, non-trivial migration that deserves its own PR.

8. What’s next

We are at Angular 17. The remaining path is 17 -> 18 -> 19 -> 20. Angular 18 should be relatively smooth since the major architectural changes (standalone, control flow) are already done. The esbuild migration is something we will need to tackle eventually, but that can wait.

See you in Part 5.

Published 12 Mar 2026

Read more

 — Upgrade to Angular 20 from Angular 13 - Part 3: Angular 16 with Claude Code
 — I added synced lyrics to Angular Spotify without writing a single line of code
 — Google TypeScript Style Guide
 — Typescript types vs interface
 — Upgrade to Angular 20 from Angular 13 - Part 2: Angular 15