Angular v19 is all the rage these days! Of course, everyone talks about SSR improvements, like incremental hydration, linkedSignal and resource/rxResource APIs, and you can already find some articles about them in here:

But today, we are going to relax a bit from all the hype and talk about smaller, somewhat overlooked additions and improvements in Angular v19 that might have slid under the radar for the past week.

So, without further ado, let's dive in into the no-signals edition!

Improved Application initializer developer experience

Sometimes in Angular apps, we need to run a certain functions before the application, and everything inside, start running. This might be a function to fetch mission critical configurations, or maybe some logic to hydrate previous application state from localStorage.

Regardless of the purpose, the ability to run such functions is important, and before v19, we used the APP_INITIALIZER token to do so:

export function initializeApp(configService: ConfigService) {
  return () => configService.loadConfig();   

}

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    ConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: initializeApp,
      deps: [ConfigService],
      multi: true
    }
  ]
};

This worked well, but looks a bit boilerplate-y. To improve this, Angular has introduced a new provideAppInitializer function that can be used to register such functions:

import { provideAppInitializer } from '@angular/core';

export function initializeApp() {
  const configService = inject(ConfigService);
  configService.loadConfig();
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    ConfigService,
    provideAppInitializer(initializeApp)
  ]
};

Now, this is a lot cleaner. The provided initializer function runs in an injection context, so we can use inject to get the services we need. We can call the provideAppInitializer function multiple times to register multiple initializers.

This also works with PLATFORM_INITIALIZER and ENVIRONMENT_INITIALIZER tokens, which are replaced by providePlatformInitializer and provideEnvironmentInitializer respectively.

Note: the previous APP_INITIALIZER, PLATFORM_INITIALIZER and ENVIRONMENT_INITIALIZER tokens are still supported, but they are deprecated and will be removed in the future.

Router Outlet Data

Components that are rendered inside other components can receive data from the parent component via input properties. Routed components can read URL parameters, query parameters and resolved data via input-s too.

This we all know, but what about scenarios where the data is coming from a component higher in the component tree and is not part of routing data (params, etc)? For instance, we might have a secondary router outlet for a sidebar, but sidebar menu items are stored in an array in the component that actually renders the router outlet? So, how we pass that data down?

Thankfully, in v19, a new input property called routerOutletData is added on the <router-outlet> directive, which allows passing of some data down to the components rendered via that outlet.

We can simply provide that data:

@Component({
  template: `
    <router-outlet routerOutletData="sidebarItems"></router-outlet>
  `,
})
export class NavigationComponent {
    readonly #navigationService = inject(NavigationService);
    sidebarItems: NavigationData = {
        items: this.#navigationService.getSidebarItems(),
    };
}

Now, in any child route, in a component we can retrieve that data:

@Component({
  template: `
    @for (item of routerOutletData().sidebarItems) {
        <app-sidebar-item [item]="item" />
    }
  `,
})
export class SidebarComponent {
  routerOutletData = inject(ROUTER_OUTLET_DATA) as Signal<NavigationData>;
}

As you can see, the really amazing thing about this is that the data being injected comes as a Signal, which means that it will be updated when it changes in parent components, allowing us seamless communication between routes, without having to resort to state management solutions or services injected everywhere.

typeof operator in templates

Recently, there has been much talk about introducing lexical scope to Angular component templates, meaning we could access global variables, functions, and broader JavaScript functionality like Math and Date in templates. While this particular feature is still far in the future, there has been a slight improvement to the template operations thanks to Matthieu Riegler, one of our top experts, who submitted a PR which allows us to use the typeof operator in templates.

This can be useful when we have volatile data types; for instance, some third-party library might return an object as a result of an operation, and, instead of throwing an error, return an error message, leaving us with a Result | string type. As we do not have control over a third-party library, we are forced to write cumbersome logic to handle the error case.

However, with the typeof operator, we can easily check the type of the result and handle it accordingly:

@let result = getResult(data);
@if (typeof result === 'string') {
    <span>Error: {{ result }}</span>
} @else {
    <span>Success: {{ result.value }}</span>
}

This also ensures type safety directly in the template, taking our code to the next level!

Resolvers support redirecting

When writing guards, the logic is often "check something, and return true if it is ok to navigate, or return a redirect command to guide the user to a different page". This is very useful, however, it was never available to use in resolvers, where we were forced to write redirection logic by injecting the router and manually calling navigate or navigateByUrl, somewhat breaking the declarative approach to the resolver code.

This, however, is about to change, because resolvers, starting from Angular v19, are going to also support RedirectCommand objects as return types. For example, if we have a resolver that loads a product, we can easily redirect the user to a "Not found" page if the product is not found:

export const productResolver: ResolveFn<Product> = ({ paramMap }) => {
  const router = inject(Router);
  const productService = inject(ProductService);
  const id = paramMap.get('id') ?? 0;
  return productService
    .getProductById(+id)
    .pipe(
      catchError(() =>
        of(new RedirectCommand(router.createUrlTree(['/error']))),
      )
    );
};

This simple code is all that we need to perform this relatively complex action, improving the developer experience by a lot.

Empty styles result in ViewEncapsulation.None

In Angular, when we create a component that has its own styles, the component is wrapped in something known as ViewEncapsulation. This allows us to specify any styles and be assured that styles from other components that use the same selectors will not conflict with ours.

This is done via adding specific attributes to the component's template elements. A rendered Angular template might look like something like this:

<p _ngcontent-ng-c3298008605="">
    <a _ngcontent-ng-c3298008605="" href="example.com" target="_blank" class="location">
        <mat-icon _ngcontent-ng-c3298008605="" role="img" svgicon="marker"
                class="mat-icon notranslate mat-icon-no-color" aria-hidden="true" 
                data-mat-icon-type="svg" data-mat-icon-name="marker"></mat-icon>
    </a>
</p>

As we can see, cusomt attributes like _ngcontent-ng-c3298008605 and so on are added to the elements that Angular renders to make them unique enough so that styles applied to those don't collide with other styles created in the same application. However, if we do not have any custom styles in a component, it would make sense to set ViewEncapsulation to None so that we can avoid the overhead of adding those attributes to all the elements. Previously, if the styles or styleUrls properties were not set, Angular would default to ViewEncapsulation.None, but not when those properties were set without actually providing any styles, so this code:

@Component({
    template: `<p>Hello!</p>`,
    styles: ``,
})
export class AppComponent {}

would still render the auto-generated attributes unnecessarily, resulting in performance overhead. Considering that the Angular CLI by default generates component.scss files and lots of developers tend to leave them even when they are empty, this results in lots of unnecessary work by the compiler (and later by the browser when actually rendering the HTML). This behavior has been fixed in v19, and now skipping adding styles on a component will result in skipping adding the custom attributes.

Usage of the this keyword in the template

Usually, when we write logic in the template and refer to the component instance properties, we do not use the this keyword, because the Angular compiler knows to use the component instance, so code like <span>{{ title }}</span> will be automatically interpreted as <span>{{ this.title }}</span>. We all know this, however, it is a lesser known fact that we can also explicitly write the this keyword in the template, however useless that is.

Although, we might think that there are two cases where that could be useful. First is when we want to extract the value of a possibly nullable signal to work around a TypeScript issue present in Angular templates that use signals.

@if(someSignal()) {
  <!-- this won't work despite checking for null
       as signals are functions and checking the return value 
       for `null` once does not (from TypeScript's perspective) 
       guarantee that the signal will return a non-null value in the future,
       so we are forced to add non-null assertion everywhere we use the signal,
       even if we have checked it previously, so this code would be `{{ someSignal()!.someProperty }}` -->
  <span>{{ someSignal().someProperty }}</span>
}

To avoid doing this, we could extract the value of the signal to a local variable, and then use that local variable in the template, like this:

@let someSignalValue = someSignal()
@if(someSignalValue) {
  <span>{{ someSignalValue.someProperty }}</span>
}

However, it is a bit cumbersome to find names for all signals that behave this way, and suffixing everything with value is not the cleanest solution. Thankfully, we can create a local variable with the same name using the this keyword in the template, like this:

@let someSignal = this.someSignal()
@if(someSignal) {
  <span>{{ someSignal.someProperty }}</span>
  <!-- no non-null assertion required here -->
}

While this functionality existed for a while now and is not new to v19, as mentioned, there is another use case for this in templates: when a template variable has the same name as a component property, for example, in an ng-template with a local variable. So, we are justified to assume that this code is going to work:

<ng-template #myTemplate let-title>
  <!-- refers to the local variable -->
  <span>{{ title }}</span>
  <!-- refers to the component property -->
  <span>{{ this.title }}</span>
</ng-template>

But, as funny as it is, until v19, the Angular compiler in both cases assumed that the variable is referring to the local variable, and not the component property, yes, even with explicit this. This was a bug that was fixed in v19, so now we can easily differentiate between local variables and component class properties whenever necessary.

APIs marked as stable

As we all know all too well, Angular is in the process of reinventing itself, so many tools already available for us are yet marked as experimental or in developer preview. However, v19 is also a big release in terms of stabilizing functionality (ok, we gotta talk about signals a bit here too). Here is a table illustrating all the APIs that are now marked as stable:

API Introduced in Version Status
@let syntax v18.1 Stable
takeUntilDestroyed operator v15 Stable
outputFromObservable/outputToObservable functions v18.2 Stable
input/output/model properties v18+ Stable
Signal-based queries (viewChild and so on) v18+ Stable
withRequestsMadeViaParent option for HttpClient v18 Stable

This is great news for Angular enthusiasts, as it means we can now freely use all these new features in production-ready application without worrying about further breaking changes!

Note: you can use this interactive guide to keep track of all new, stable, or experimental features in Angular.

Conclusion

As we have seen, Angular releases, especially major ones like v19, are always full of improvements, that sometimes get overlooked because of really hyped-up (rightfully so) features. So, with this article, I tried to cover the most important and useful ones, to help Angular developers stay up-to-date with the latest developments.

You can check the official release on GitHub following this link to learn more.

Small promotion

Modern Angular.jpeg

Angular is evolving rapidly, and it is easy to find yourself in a bit overwhelmed kind of situation. Thankfully, I have a solution to this! This past 18 months, I have been working on a comprehensive Angular book!

It is called "Modern Angular" and it is a comprehensive guide to all the new amazing features we got in recent versions (v14-v18), including standalone, improved inputs, signals (of course!), better RxJS interoperability, SSR, and much more. If this interested you, you can find it here.

The book is now in the production phase with a print release scheduled in upcoming weeks, so it is currently in Early Access, with all the 10 chapters already available online. If you want to keep yourself updated on the print release, you can follow me on Twitter or LinkedIn, where I will be posting whenever there are news or promotions available.



Tagged in:

Articles

Last Update: November 29, 2024