“The Ultimate Guide To Angular Evolution”

Angular 20.2

8 Features To Maximize Your Team Efficiency in Angular 19

Scaling and maintaining Angular applications efficiently requires more than just writing clean code – it also involves faster workflows, reduced overhead, and streamlined state management. When even small improvements add up, your team can release features more quickly, minimize bottlenecks, and concentrate on innovation rather than repetitive tasks.

Angular 19 introduces enhancements that reduce boilerplate code, automate tedious migrations, and accelerate development cycles. These updates ensure that your team spends less time on routine fixes and more time delivering value to the business.

Here are new features of Angular 19 that equip your developers to work smarter and faster.

Resource API (experimental) – easier asynchronous data management

Challenge:

Managing asynchronous operations in Angular applications often involves complex patterns such as subscriptions, manual state tracking, and additional boilerplate. Current practices, such as combining RxJS for data fetching with signals for state management,  can result in fragmented and difficult-to-maintain code.   Developers need a simpler and more integrated approach to handle async data reactively.

 

Solution:

Angular 19 introduces the experimental resource API, which integrates asynchronous operations into the signal graph. A resource is a declarative way to define, load, and manage async dependencies, providing both the value and status of an operation as signals. 

This API simplifies the management of asynchronous workflows by coupling data fetching, state, and reactivity into a single coherent abstraction.

fruitId = signal<string>('apple-id-1');
fruitDetails = resource({
  request: this.fruitId,
  loader: async (params) => {
    const fruitId = params.request;
    const response = await fetch(`https://api.example.com/fruit/${fruitId}`, {signal: params.abortSignal});
    return await response.json() as Fruit;
  }
});
protected isFruitLoading = this.fruitDetails.isLoading;
protected fruit = this.fruitDetails.value;
protected error = this.fruitDetails.error;
protected updateFruit(name: string): void {
  this.fruitDetails.update((fruit) => (fruit ? {
    ...fruit,
    name,
  } : undefined))
}
protected reloadFruit(): void {
  this.fruitDetails.reload();
}
protected onFruitIdChange(fruitId: string): void {
  this.fruitId.set(fruitId);
}Code language: JavaScript (javascript)

The optional request parameter accepts the input signal that the asynchronous resource is associated with (in our example, it is fruitId, but it could just easily be a computed signal consisting of multiple values). 

We also define the loader function, with which we asynchronously download the data (the function should return a promise). The created resource named fruitDetails allows us to do the following, among other things: 

  • access the current value signal (which also returns undefined if the resource is not available at the moment),
  • access the status signal (one of:  idle, error, loading, reloading, resolved, local),
  • access extra signals like ‘isLoading’ or ‘error’,
  • trigger the ‘loader’ function again (using the ‘reload’ method),
  • update the local state of the resource (using the ‘update’ method).

The resource is automatically reloaded when the ‘request’ signal (in our case fruitId) changes. The loader is also triggered when the resource is first created.

What about RxJS Interop? Angular also provides an RxJS counterpart to the resource method called rxResource. In this case, the loader method returns Observable, but all other properties remain signals.

fruitDetails = rxResource({
  request: this.fruitId,
  loader: (params) => this.httpClient.get<Fruit>(`https://api.example.com/fruit/${params.request}`)
})Code language: JavaScript (javascript)

Benefits:

The resource API streamlines the management of async operations in Angular by using an integrated, declarative approach. It reduces boilerplate, boosts maintainability, and keeps async data seamlessly reactive with the app state. By exposing signals for both value and status, resources make it easier to consistently handle loading, success, and error states.

New reactive primitive – linkedSignal (experimental) – simplified reactive state synchronization

Challenge:

Managing the dependencies and changes between reactive signals in Angular can be a challenging task in complex applications. 

Although the existing reactive primitives, such as signal and computed, offer robust solutions for handling reactivity, they lack mechanisms for directly coupling reactive state changes with derived calculations that automatically reset when their source changes. 

Developers are often required to add extra boilerplate to achieve this functionality, which increases the complexity of the code and reduces its maintainability.

Solution:

Angular 19 introduces linkedSignal, a new experimental reactive primitive designed to fill this gap. 

linkedSignal is a writable signal that links its value to a source signal and recalculates itself based on a given computation function whenever the source signal changes.

Key features of linkedSignal:

  • when the source signal changes, the value of linkedSignal automatically resets,
  • the value of linkedSignal can be set manually using the set method, but it will revert to the computed value when the source signal updates,
  • The computation logic is located inside linkedSignal, keeping dependencies clear and declarative while preserving all reactive programming features.

As shown in the example below, the set method of favoriteColorId allows the user to specify a chosen color ID (see onFavoriteColorChange). When the available colors are updated (see changeColorOptions), the value of favoriteColorId is recalculated. If the selected color exists in the new list, the signal value remains unchanged; otherwise, it defaults to null.

protected readonly colorOptions = signal<Color[]>([
  { id: 1, name: 'Red',}, 
  { id: 2, name: 'Green'}, 
  { id: 3, name: 'Blue'}
]);


protected favoriteColorId = linkedSignal<Color[], number | null>({
  source: this.colorOptions,
  computation: (source, previous) => {
    if(previous?.value) {
      return source.some(color => color.id === previous.value) ? previous.value : null;
    }
    return null;
  }
});


protected onFavoriteColorChange(colorId: number): void {
  this.favoriteColorId.set(colorId);
}


protected changeColorOptions(): void {
  this.colorOptions.set([
    { id: 1, name: 'Red' },
    { id: 4, name: 'Yellow' },
    { id: 5, name: 'Orange' }
  ])
}}Code language: JavaScript (javascript)

Benefits:

linkedSignal makes state management easier by linking updates directly to source signals, ensuring that dependencies remain up-to-date without explicit subscriptions and/or side effects. With the flexibility to override values as needed, it reduces boilerplate, improves maintainability, and optimizes responsiveness.

@let template variable syntax – cleaner template logic with better falsy value handling

Challenge:

In previous versions of Angular, declaring variables within templates often meant using the ngIf directive and @if with the as keyword. This method had its limitations, especially when dealing with falsy values (such as 0, empty strings, null, undefined, and false), which would prevent content from rendering. For example:

<div *ngIf="userName$ | async as userName">
 <h1>Welcome, {{ userName }}</h1>
</div>Code language: HTML, XML (xml)

If userName were an empty string, nothing would be displayed.

Solution:

Angular introduces the @let block to simplify template logic by enabling variable declarations directly in the template. This prevents issues with falsy values and improves readability. The @let block lets developers declare variables directly in the template, making it easier to handle complex conditions and asynchronous data.

With @let:

<div>
 @let userName = (userName$ | async) ?? 'Guest';
 <h1>Welcome, {{ userName }}</h1>
</div>Code language: JavaScript (javascript)

This code handles falsy values like empty strings by providing a default value (‘Guest’).

Benefits:

With the @let block, developers can declare variables within the template, simplifying the overall logic. This improvement makes the code cleaner and more readable, especially when dealing with complex conditions and dynamic data. It also prevents falsy value issues in ngIf, making sure content appears as expected.

New useful migrations (injections, standalone API) – automated upgrade to modern Angular features

Challenge:

Keeping Angular projects up to date with the latest features and best practices can be a daunting task. Especially for large or legacy codebases, manually refactoring code to take advantage of new APIs, improve performance, or simplify the architecture is a significant maintenance burden.

Solution:

A set of automated migrations in Angular helps developers transition smoothly to modern features. These migrations are designed to address common refactoring needs, reduce manual effort, and ensure a problem-free upgrade process.

Shift from constructor-based injection to inject() function:

ng g @angular/core:inject

This migration simplifies the code by replacing the constructor syntax with a more streamlined approach:

// before
constructor(private productService: ProductService) {}


// after
private productService = inject(ProductService);Code language: JavaScript (javascript)

After migration, you may encounter compilation issues, especially in tests that directly create instances of injectables. The migration utility provides several options to customize the process, such as how to handle abstract classes, how to maintain backward-compatible constructors, and how to manage nullable settings to ensure a smooth transition without breaking existing code.


Lazy loading of standalone components in routing configuration: 

ng g @angular/core:route-lazy-loading

This migration changes direct references to components into dynamic imports (lazy loading):

// before
{
  path: 'products',
  component: ProductsComponent
}
// after
{
  path: 'products',
  loadComponent: () => import('./products.component').then(m => m.ProductsComponent)
}Code language: JavaScript (javascript)

The migrations for signal inputs, outputs, and queries make Angular more responsive by converting traditional input properties, event outputs, and query fields to their modern, signal-based counterparts. This improves code efficiency and maintainability.

ng generate @angular/core:signal-input-migration
ng generate @angular/core:output-migration
ng generate @angular/core:signal-queries-migration

Example:

export class MyComponent {
  // before
  @Input() name: string|undefined = undefined;
  @Output() someChange = new EventEmitter<string>();
  @ContentChild('someRef') ref: ElementRef|undefined = undefined;
  // after
  name = input<string>();
  someChange = output<string>();
  ref = contentChild<ElementRef>('someRef');
}Code language: JavaScript (javascript)

Benefits:

These migrations dramatically increase developer productivity by simplifying the adoption of the latest Angular features, such as the inject() function and standalone APIs.  By automating refactoring tasks, they reduce errors, ensure code consistency, and allow developers to focus on building modern, maintainable applications. Developers can focus on delivering new features and improving the user experience instead of spending time on manual updates.

New angular diagnostics – smarter code checks for cleaner apps

Challenge:

Maintaining clean, efficient, and bug-free Angular applications is a challenge, especially when subtle issues like unused imports or incorrect event bindings can go unnoticed during development. Traditional diagnostics can miss these nuanced problems, leading to technical debt and inefficient applications over time.

Solution:

Angular’s Extended Diagnostics provide real-time code checks that go beyond standard errors and warnings to identify potential problems and enforce best practices. In Angular 19, two new diagnostics extend this capability:

Uninvoked Functions: Flags instances where a function is used in an event binding but isn’t called due to missing parentheses in the template. This helps ensure that functions in event bindings are executed correctly, preventing runtime errors. 

Example:

<!-- Incorrect -->
<button (click)="onClick">Click me</button>
<!-- Correct -->
<button (click)="onClick()">Click me</button>Code language: HTML, XML (xml)

Unused Standalone Imports: Detects standalone components, directives, or pipes that are imported but not used in the application. This maintains a clean codebase by prompting developers to remove unused imports. 

Example:

// Incorrect
@Component({
  imports: [UnusedComponent],
  template: '<div>hello</div>'
})
export class MyComponent {}
// Correct
@Component({
  imports: [],
  template: '<div>hello</div>'
})
export class MyComponent {}Code language: JavaScript (javascript)

We encourage you to explore and test other Angular diagnostics documented in the official Angular documentation to further refine your projects and uphold strong code quality.

Benefits:

These new diagnostics improve the quality of your code by catching subtle problems early in the development process, reducing technical debt and run-time errors. They ensure adherence to Angular’s best practices, resulting in cleaner, more maintainable applications.

Hot module replacement for ng serve – instant updates without losing state

Challenge:

Developers often face workflow disruptions when making changes to styles or templates,  because the Angular CLI traditionally requires a full page refresh for updates to take effect. This process not only slows down the development cycle, but also results in the loss of application state, breaking the flow of iterative design and debugging.

Solution:

Angular v19 introduces built-in support for Hot Module Replacement (HMR) for styles and experimental support for template HMR, greatly improving the development experience. With HMR, any changes made to styles or templates are compiled and patched into the application in real time, without requiring a page refresh. This ensures that updates are applied immediately while preserving the state of the application, resulting in a faster and more seamless development workflow.

The HMR for styles is enabled by default. Simply modify the styles of a component, save the changes, and see them immediately reflected in the browser without reloading the page.

To enable HMR for templates, use the following command:

NG_HMR_TEMPLATES=1 ng serve

To disable HMR for development servers, use:

ng serve --no-hmr

Benefits:

Hot module replacement updates styles and templates in real time, cutting development time without losing application state. This feature increases developer productivity by maintaining an uninterrupted workflow, speeding up iterations, and promoting a more efficient debugging process.

Components become standalone by default

Challenge:

The legacy approach to Angular components often relies on explicit module declarations, which can add complexity and create barriers for new developers. Standalone components were introduced in v14, but not by default, creating inconsistencies and additional setup for developers wishing to use them.

Solution:

With Angular v19, standalone: true becomes the default for components, directives, and pipes. Angular becomes more intuitive and accessible with this change, as explicit module declarations are no longer needed in most cases.


Example:

@Component({
  imports: [],
  selector: 'home',
  template: './home-component.html'
  // standalone in Angular 19!
})
export class HomeComponent { }Code language: JavaScript (javascript)

For existing projects, an automated migration during the ng update will adjust standalone flag settings as needed, ensuring compatibility and facilitating a smooth transition.

Benefits:

Making standalone components the default makes Angular’s mental model simpler,  especially for new developers. In keeping with Angular’s goal of streamlining development workflows, this enhancement better supports features such as lazy loading and component composition, and reduces boilerplate. Automated migration tools make sure that existing projects can seamlessly adopt the new defaults, minimizing disruption while providing a modern development experience.

Default query params handling strategy – global config, cleaner navigation

Challenge:

Configuring query parameter handling strategies individually for each navigation can become repetitive and susceptible to errors. Developers often need a consistent way to manage query parameters across the entire application without having to manually configure each route.

Solution:

Angular now allows developers to globally set a default query parameter handling strategy in the provideRouter() configuration. The result is the elimination of repetitive per-route configurations, which streamlines navigation logic and improves maintainability.

Example:

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withRouterConfig({ defaultQueryParamsHandling: 'preserve' }))
  ]
};Code language: JavaScript (javascript)

Angular’s default strategy is replace, but developers can choose between:

  • preserve: It retains the current query parameters during navigation.
  • merge: It combines the new query parameters with the existing ones.

This flexibility ensures that query parameter handling meets application-specific requirements, reducing boilerplate and potential inconsistencies.

Benefits:

With a global query parameter handling strategy, Angular reduces repetitive code, simplifies navigation, and ensures consistency across the app. This feature increases the productivity of the developer while maintaining the flexibility for advanced use cases.

Final thoughts

Angular 19 brings updates that enhance the efficiency of development teams, reduce overhead, and streamline workflows. By minimizing repetitive tasks and improving maintainability, these changes empower teams to focus on building high-quality applications more quickly.

However, there’s more to creating scalable and high-performance Angular applications. If you’re interested in exploring additional features of the latest versions that enhance performance, simplify maintenance, and improve user experience, download our free ebook: The Ultimate Guide to Angular Evolution.

Free ebook: The Ultimate Guide to Angular Evolution with Angular 20

Discover How Each Angular Version Impacts Efficiency, DX, UX, and App Performance

Written by
Mateusz
A developer who specializes in building web applications with TypeScript. A self-improvement enthusiast and a detail-oriented individual who tends to strive for the facts.

Social Media

 
The latest articles