What's new in Angular 17.3?

Angular 17.3.0 is here!

Angular logo

This is a minor release with some nice features: let’s dive in!

TypeScript 5.4 support

Angular v17.3 now supports TypeScript 5.4. This means that you can use the latest version of TypeScript in your Angular applications. You can check out the TypeScript 5.4 release notes to learn more about the new features.

New template compiler

Angular now uses a new template compiler! The work on this compiler started more than a year ago and has been done in parallel with the other features we saw in the previous releases to eventually pass all of the existing tests. It’s now the case, and this new compiler is now the default in Angular 17.3.

This compiler is based on an intermediate representation of template operations, a common concept in compilers, for example in LLVM. This IR semantically encodes what needs to happen at runtime to render and change-detect the template. Using an IR allows for different concerns of template compilation to be processed independently, which was not the case with the previous implementation. This new compiler is easier to maintain and extend, so it is a great foundation for future improvements in the framework.

Note that the compiler emits the same code as the previous one, so you should not see any difference in the generated code.

output functions

A new (developer preview) feature was added to allow the declaration of outputs similarly to the input() function.

As for inputs, you can use the output() function to define an output:

ponySelected = output<PonyModel>();
// ^? OutputEmitterRef<PonyModel>

The output() function returns an OutputEmitterRef<T> that can be used to emit values. OutputEmitterRef is an Angular class, really similar to a simplified EventEmitter but that does not rely on RxJS (to limit the coupling of Angular with RxJS).

The function accepts a parameter to specify options, the only one available for now is alias to alias the output.

As with EventEmitter, you can use the emit() method to emit a value:

ponySelected = output<PonyModel>();
// ^? OutputEmitterRef<PonyModel>
select() {
  this.ponySelected.emit(this.ponyModel());
}

You can also declare an output without a generic type, and the OutputEmitterRef will be of type OutputEmitterRef<void>. You can then call emit() without a parameter on such an output.

OutputEmitterRef also exposes a subscribe method to manually subscribe to the output. This is not something you’ll do often, but it can be handy in some cases. If you manually subscribe to an output, you’ll have to manually unsubscribe as well. To do so, the subscribe method returns an OutputRefSubscription object with an unsubscribe method.

Two new functions have been added to the rxjs-interop package to convert an output to an observable, and an observable to an output.

Angular always had the capability of using observables other than EventEmitter for outputs. This is not something that is largely used, but it’s possible. The new outputFromObservable function allows you to convert an observable to an output:

ponyRunning$ = new BehaviorSubject(false);
ponyRunning = outputFromObservable(this.ponyRunning$);
// ^? OutputRef<boolean>

The outputFromObservable function returns an OutputRef<T>, and not an OutputEmitterRef<T>, as you can’t emit values on an output created from an observable. The output emits every event that is emitted by the observable.

startRunning() {
  this.ponyRunning$.next(true);
}

It is also possible to convert an output to an observable using the outputToObservable function if needed. You can then use .pipe() and all the RxJS operators on the converted output.

These interoperability functions will probably be rarely used. output(), on the other hand, will become the recommended way to declare outputs in Angular components.

HostAttributeToken

Angular has always allowed to inject the value of an attribute of the host element. For example, to get the type of an input, you can use:

@Directive({ 
  selector: 'input',
  standalone: true
})
class InputAttrDirective {
  constructor(@Attribute('type') private type: string) {
    // type would be 'text' if `<input type="text" />
  }
}

Since Angular v14, injection can be done via the inject() function as well, but there was no option to get an attribute value with it.

This is now possible by using a special class HostAttributeToken:

type = inject(new HostAttributeToken('type'));

Note that inject throws if the attribute is not found (unless you pass a second argument { optional: true }).

RouterTestingModule deprecation

The RouterTestingModule is now deprecated. It is now recommended to provideRouter() in the TestBed configuration instead.

New router types

The router now has new types to model the result of guards and resolvers.

For example, the CanActivate guard was declared like this:

export type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;

This is because the guard can return a boolean to allow or forbid the navigation, or an UrlTree to trigger a redirection to another route. This result can be synchronous or asynchronous.

The signature has been updated to:

export type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => MaybeAsync<GuardResult>;

GuardResult is a new type equal to boolean | UrlTree, and MaybeAsync<T> is a new generic type equal to T | Observable<T> | Promise<T>.

A resolver function now also returns a MaybeAsync<T>. You can keep using the older signatures but the new ones are more concise.

Angular CLI

Angular CLI v17.3 doesn’t bring a lot of new features, but we can note that deployUrl is now supported in the application builder. It was initially marked as deprecated but was re-introduced after community feedback.

Summary

That’s all for this release. The next stop is v18, where we should see some developer preview features becoming stable. Stay tuned!

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!



blog comments powered by Disqus