The Context
At IVTREE, I was tasked with modernising three internal Angular applications — all stuck on Angular v16, some with code going back to 2020. The goal was to get them to v19 and improve maintainability while keeping the apps running for 100+ daily users.
Step 1: Understand Before You Touch
I spent the first week just reading. Every component, every service, every interceptor. I documented what each module did, what APIs it consumed, and what parts were tightly coupled. The most dangerous code to migrate is code you don't understand.
Skipping the reading phase to "save time" will cost you 3x as much time debugging mysterious regressions later.
Angular v16 → v19: The Key Changes
- Standalone components — NgModules are now optional. Migrating to standalone simplified the codebase significantly
- Signals — Angular's new reactivity primitive replaced several RxJS patterns that were being misused
- Control flow syntax —
@if,@for,@switchreplaced*ngIf,*ngFor - inject() function — Constructor injection replaced with the cleaner inject() approach
// BEFORE (v16)
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
})
export class DashboardComponent {
constructor(private userService: UserService) {}
}
// AFTER (v19 standalone)
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [CommonModule, RouterModule],
templateUrl: './dashboard.component.html',
})
export class DashboardComponent {
private userService = inject(UserService);
}The Hardest Part: RxJS
Every app used RxJS heavily, and much of it incorrectly — subscriptions not being unsubscribed, nested subscribes instead of switchMap, and manual state management that Signals now handles natively.
What I'd Do Differently
Feature flags. Being able to turn migrated components on/off without a deployment would have made testing much safer. We did everything in long-lived branches which created painful merge conflicts.