Introduction

On November 8, 2023, Angular 17 hit the shelves, and this release feels like a fresh start for a framework first introduced back in September 2016.

To be clear, this isn’t a from-scratch rework of the framework. Rather, it’s about layering in substantial new capabilities that tap into modern browser APIs, pushing the framework further than before.

The key questions are: Why does this version mark such a pivotal moment for Angular? And what sets it apart from earlier releases?


A Fresh Look

After a brief period where the Angular logo vanished, replaced by a question mark across social channels like X, the Angular team unveiled a new logo on Monday, November 6, along with a revamped documentation site, accessible here.

This new site—still expanding its API coverage—offers several upgrades, such as:

  • a built-in sandbox for experimenting with features directly
  • interactive tutorials that can be launched inside a sandbox
  • a reorganized structure grouped by topic (components, forms, accessibility, best practices, etc.).

Feel free to explore it and flag any issues or questions you come across.


The @Component Annotation

The @Component annotation has seen some refinements. Historically, attaching styles to a component required passing an array of style objects or a list of relative stylesheet paths.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {}

Typically, a view pairs with a single stylesheet.

  • a brand-new option, styleUrl, has been added
  • the styles property now accepts a single string or an array of strings
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {}

The New Application Builder

Angular has traditionally relied on separate builders for different app types. There are two primary categories of Angular applications:

  • client-side rendering: the entire app runs and renders on the client. In essence, the app is expressed in DOM form, with navigation, data retrieval, and templating all managed in the browser rather than on a server.
  • server-side rendering: the app is served as HTML, with each route rendered to HTML in response to navigation, then rehydrated on the client to become interactive.

The builder you used depended on which type of app you were building.
Angular previously relied on:

  • @angular-devkit/build-angular:browser for client-side rendering setups
  • @nguniversal/builders:ssr-dev-server for server-side rendering setups.

Angular 16 introduced an experimental builder:
@angular-devkit/build-angular:browser-esbuild. This one brought vite for the development server and esbuild for builds, with impressive speed gains:

  • 87% faster application builds
  • 82% faster dev-server startup

However, the server-rendering builder remained unchanged.
Now, with Angular 17, a new, unified "generic" builder is here. It handles both client-side and server-side rendering from a single entry point.
Let’s hear it for:
@angular-devkit/build-angular:application

For projects already using @angular-devkit/build-angular:browser-esbuild, moving to this new builder is straightforward.

For server-side rendering projects, the switch involves more effort due to the larger number of configuration properties that need updating.


The New Angular Control Flow

Angular 17 also brings a new, more robust control flow syntax.
Previously, to shape the DOM, you relied on structural directives like ngIf, ngSwitch, and ngFor.
These directives are potent, thanks to the microsyntax they’re built on. However, that microsyntax has its constraints, which led to the creation of this new approach.

So, what does this new syntax look like?

@Component({
  standalone: true,
  selector: 'app-root',
  template: `
    @if(name === 'SFEIR'){
      <ul>
         @for(entity of entities; track entity.id) {
           <li>{{ entity.name }}</li>
         }@empty {
           No data
         }
      </ul>

    }@else {
      Name not correct
    }
  `
})
export class AppComponent {
  name = 'SFEIR';
  entities = [{ name: 'Luxembourg', id: 'LU' }];
}

As you can see, there’s no need to import ngIf or ngFor anymore. This control flow is baked directly into Angular, simplifying future integration with Angular signals.

Structural directives aren’t being deprecated, and there are no plans to do so.
Looking ahead, only ngIf, ngFor, and ngSwitch are candidates for deprecation.

To migrate your components automatically to this new syntax, this schematic will become your go-to tool:

ng g @angular/core:control-flow 

Heads-up: since this is brand new, you might see some formatting quirks. (Prettier 3.1 now has support for this syntax.)

IDE support:

  • VsCode, via Angular Language Service, already offers syntax highlighting
  • Webstorm EAP also highlights it. Template checking isn’t available yet, but it’s on the way.

The Most Anticipated Feature: defer

This is likely one of the biggest additions in this release—the straightforward ability to lazy-load components.

Of course, lazy loading was possible before. Using ES modules along with the *ngComponentOutlet structural directive, you could load components on demand. But that method was error-prone, requiring the developer to manage every phase of the load (like loading states and error handling) and offering limited flexibility (such as complex prefetching). Simply put, the developer experience fell short.

With Angular 17, defer steps in, simplifying component lazy-loading while covering all the necessary stages.

@Component({
  selector: 'app-root',
  template: `
    <button type="button" #loadButton>Click Me</button>
    @defer(on interaction(loadButton)) {
      <app-lazy-component />
    }@placeholder {
      showed until the chunk file not begin to load
    }@loading {
      showed during the loading of the chunk
    }@error {
      showed if an error happen during loading
    }
  `
})
export class AppComponent {}

defer comes with a handful of other options, including placeholder and loading. For a deep dive, I recommend Tomas Trajan’s excellent write-up, available here.

As with anything powerful, moderation is key. How you apply defer will depend on your use case. Generally speaking, your decision should weigh your network conditions and the size of the component you’re lazy-loading. On top of that, this feature is a boon for improving Core Web Vitals, specifically the LCP and CLS metrics.


A Note on Signals

Signals were introduced as a developer preview in Angular 16. With Angular 17, signals are now stable and production-ready.
When paired with the ChangeDetection.OnPush strategy, you get precise reactivity, updating only the component tied to a changed signal.

The mutate method on signals has been removed.

Effects, on the other hand, are still in developer preview.


Angular Devtools

Angular Devtools has picked up a handy new trick 😄 you can now inspect the injection tree. This is a lifesaver for debugging issues like circular dependencies or tokens that aren’t resolving as expected.


Conclusion

Angular 17 is packed with new features, which is exactly why this feels like a rebirth for the framework. And, like all prior releases, Angular 17 introduces no breaking changes, making an upgrade from Angular 16 a smooth process.

These features are there for you to explore—but you’re under no obligation to adopt them all right away.