DEV Community

Cover image for Angular's Declarative Shift
Antonio Cardenas for Turing's Oracle

Posted on

Angular's Declarative Shift

How the New Folder Structure Changes Everything

If you haven't updated to version 20, the first and second parts of this guide can help you understand what's changing and how to update.

Part 1: The Upgrade Itself πŸ› οΈ

First, let's get the basics out of the way. Before running any commands, make sure your environment is ready.

Prerequisites

  • Node.js: v20.11.1 or later.
  • TypeScript: v5.8 or later.
  • Project Backup: Make sure you commit all your current changes in Git. Seriously.

The Update Command

Once you've confirmed your Node.js version, run the command that fits your project.

For a standard Angular project:

ng update @angular/cli @angular/core
Enter fullscreen mode Exit fullscreen mode

If you use Angular Material:

ng update @angular/cli @angular/core @angular/material
Enter fullscreen mode Exit fullscreen mode

The update process will run, but you're likely to hit your first roadblock immediately.

Part 2: What Breaks Immediately πŸ›‘

Unlike previous updates, Angular 20 introduces a significant breaking change that will stop your build.

1 The Full Story Behind Karma’s Removal

The first thing you'll notice is that ng test will fail. This isn't just a bug; it's a fundamental change in Angular’s build tools.

With Angular 20, the default build package changes from @angular-devkit/build-angular to the new @angular/build. This new package does not include Karma. The web ecosystem has moved on to faster, more modern test runners like Vitest and Jest, and Karma had become a bottleneck.

The Temporary Fix:

To get your tests running without migrating everything today, you need to manually reinstall the old build tool. This forces the CLI to use the old compiler that still supports Karma.

npm install @angular-devkit/build-angular --save-dev
Enter fullscreen mode Exit fullscreen mode

This is a compatibility bridge. The message from the Angular team is clear: start planning your migration to Jest or Vitest soon.

2. browserslist and Browser Support

Here's a smaller detail that might catch you by surprise. Angular 20 officially no longer supports Opera. If you have "Opera" listed in your .browserslistrc file, your build may fail or throw warnings. Remove it to resolve the issue.

Part 3: The New Architecture πŸ›οΈ

Beyond the breaking changes, Angular 20 pushes forward a more modern, explicit, and scalable architecture.

1. Standalone is the Default

New projects generated with ng new are now standalone by default. This marks a fundamental architectural shift away from NgModules. By listing dependencies directly in a component's imports array, each component becomes self-contained.

This change:

  • Clarifies your architecture: You know exactly what each component needs.
  • Improves tree-shaking: Leads to smaller, more optimized bundles.

To migrate your existing projects, you can run the standalone migration schematic:

ng generate @angular/core:standalone
Enter fullscreen mode Exit fullscreen mode

2. A Folder Structure That Tells a Story

A good folder structure doesn't just hold filesβ€”it tells you what the application does. It's worth mentioning the official Angular style guide, as its recommendations are built on years of community experience. This philosophy, detailed on their file structure reference page, is often summarized by the acronym LIFT:

  • Locate your code easily.
  • Identify what a file does at a glance.
  • Flat structure for as long as you can.
  • Try to be DRY (Don't Repeat Yourself).

The main takeaway is to organize by feature, not by type. Instead of a components folder and a services folder, you create folders for features like user-profile or product-list.

Let's use a practical example for an e-commerce app:

src/app/
β”œβ”€β”€ core/
β”‚   β”œβ”€β”€ auth/          # Authentication logic used everywhere
β”‚   β”‚   β”œβ”€β”€ auth-store.ts
β”‚   β”‚   └── auth-interceptor.ts
β”‚   └── layout/        # The app's shell: navbar, footer
β”‚       β”œβ”€β”€ navbar.ts
β”‚       └── footer.ts
β”‚  
β”œβ”€β”€ features/
β”‚   β”œβ”€β”€ products/      # Everything for browsing products
β”‚   β”‚   β”œβ”€β”€ product-list.ts
β”‚   β”‚   β”œβ”€β”€ product-details.ts
β”‚   β”‚   └── product-search.ts
β”‚   β”‚  
β”‚   └── cart/          # The shopping cart feature
β”‚       β”œβ”€β”€ cart-store.ts
β”‚       β”œβ”€β”€ cart-view.ts
β”‚       └── add-to-cart.ts
β”‚  
└── shared/            # Reusable "dumb" components & utilities
    └── ui/
        β”œβ”€β”€ button.ts
        β”œβ”€β”€ spinner.ts
        └── price.pipe.ts

Enter fullscreen mode Exit fullscreen mode

Why This Works:

  1. core πŸ›οΈ (Provide once): Services and components the app needs to run, loaded only once (AuthStore, Navbar).
  2. features ✨ (What your app does): The heart of your application. Each folder is a self-contained feature.
  3. shared ♻️ (Reusable building blocks): "Dumb" components, pipes, and directives that don't know anything about the features they're used in (Button, Spinner). They are imported by feature modules.

3. The New Naming Convention

Angular 20 introduces a new official naming convention that drops traditional suffixes like .component.ts or .service.ts.

Old Naming New Naming Intent
user-profile.component.ts user-profile.ts UI Component
auth.service.ts auth-store.ts State Management
highlight.directive.ts highlight.ts Directive
user-api.service.ts user-api.ts HTTP Client

The goal is to focus on the file's intent rather than its technical type. A class that handles state is a "store," and one that makes HTTP requests is an "api." This makes your codebase's purpose much clearer, especially in a feature-based folder structure.

Part 4: The New Tools & Syntax πŸš€

Finally, let's look at the new tools that will make your day-to-day development better.

1. Control Flow is More Than Syntactic Sugar

The new @for block replaces *ngFor and is a major improvement.

Old Syntax:

<div *ngFor="let item of items; trackBy: trackItemById">
  {{ item.name }}
</div>
Enter fullscreen mode Exit fullscreen mode

New Syntax:

@for (item of items; track item.id) {
  <div>{{ item.name }}</div>
} @empty {
  <div>No items to display.</div>
}
Enter fullscreen mode Exit fullscreen mode

Key improvements:

  • track is mandatory, enforcing a performance best practice that was often forgotten.
  • The built-in @empty block cleans up templates by removing the need for a separate *ngIf.

You can use the CLI to automatically refactor your templates:

ng generate @angular/core:control-flow
Enter fullscreen mode Exit fullscreen mode

2. Zoneless: Escaping the "Magic" of Change Detection

While still experimental, the path to a zoneless Angular is becoming clearer. In a zoneless world, the UI only updates when you explicitly tell it to.

Signals are the primary tool for this.

mySignal.set(newValue);
Enter fullscreen mode Exit fullscreen mode

This line directly tells Angular to update only the specific parts of the DOM that depend on that signal. It’s a surgical, predictable, and high-performance approach that eliminates the overhead and unpredictability of Zone.js.

Conclusion

Angular 20 is a significant release. The upgrade requires manual intervention for testing, but it pushes the framework toward a more modern, explicit, and performant future. By embracing standalone components, a feature-based architecture, and the new control flow, you're not just updatingβ€”you're preparing your application for the next era of web development.

Happy coding!

Top comments (0)