For the last few months, I’ve been involved in several projects where I barely interact with or use Angular, if at all. There’s a reason why I worked hard to become a GDE, though: it’s still my framework of choice, and I want to keep spreading the word about how it continues to get better and effective ways to use it, so I always stay in the loop.
We’re already so close to the v21 release, so aside from hyping up the shiny new stuff that’s coming, let’s also take a few steps back and talk about my favorite features and changes from a few older versions.
Low key changes
Things like signals, zoneless, AI, and SSR are always in the limelight, but there are often tiny features and improvements that get shipped and go under the radar. Let’s cover some of them.
- esbuild define (PR)
- Most people have given up on static environment files in favor of runtime configuration. Angular doesn’t even generate them by default anymore! Both of these fall short in some ways, though, especially when it comes to compile-time features like tree-shaking. The
defineoption from esbuild fills that gap. Read Netanel Basal’s article for a detailed explanation. Since v19, you can also pass multiple--defineflags through the CLI, allowing you to leverage shell commands, scripts, environment variables, and more.
- Most people have given up on static environment files in favor of runtime configuration. Angular doesn’t even generate them by default anymore! Both of these fall short in some ways, though, especially when it comes to compile-time features like tree-shaking. The
- PostCSS plugins (PR)
- If you’ve been needing PostCSS plugins, you’re in luck!
postcss.config.jsonand.postcssrc.jsonare now processed automatically if present. This one might not be so “under the radar” since Tailwind uses this and is everywhere nowadays, but few use it for anything else. JS based configs and plugins are unsupported and would make this even better.
- If you’ve been needing PostCSS plugins, you’re in luck!
- Binding route parameters as inputs
- As of v16, you can enable the
withComponentInputBinding()feature in your app’s router config which automatically binds query parameters, path parameters, static, and resolver data to equally named inputs in a routed component. I’ve personally found its usefulness to be limited since I prefer to keep this data in services or stores, or to reach forngxtension’slinkedQueryParamwhich syncs both ways. Be careful of name collisions, too.
- As of v16, you can enable the
- Form events observable
- Reactive forms suffered from several longstanding issues and design limitations over the years that made them not so reactive. Fortunately, Angular team rockstar Matthieu Riegler added a unified
eventsobservable that fixes all of this and exposes even more changes you couldn’t track before, all without a single breaking change.
- Reactive forms suffered from several longstanding issues and design limitations over the years that made them not so reactive. Fortunately, Angular team rockstar Matthieu Riegler added a unified
- RedirectCommand and other router goodies
- v18 introduced
RedirectCommand, which can be returned from guards, resolvers, and some other places where you’d useUrlTrees, adding extra navigation options, like the ability to set a separate browser URL from the router’s, state, and others.
- v18 introduced
- Standalone by default
- This one isn’t very low key. Anyone who participates in communities, reads release notes, changelogs, or blogs, or just keeps their projects up to date would be aware of this by now. For the sake of everyone else, and for outdated AI training too, here it is again: components are standalone by default since v19! No more
standalone: true!
- This one isn’t very low key. Anyone who participates in communities, reads release notes, changelogs, or blogs, or just keeps their projects up to date would be aware of this by now. For the sake of everyone else, and for outdated AI training too, here it is again: components are standalone by default since v19! No more
- Host type-checking
- Binding properties to the host element of a component has been possible for a long time, and I’ve been recommending it myself to reduce DOM clutter, among other reasons. The Angular team also recommends the host property over
@HostBindingand@HostListenerdecorators, but the lack of type safety was a hurdle in adoption. This is no longer the case, as they’re now fully checked, EventManagerPlugins and all.
- Binding properties to the host element of a component has been possible for a long time, and I’ve been recommending it myself to reduce DOM clutter, among other reasons. The Angular team also recommends the host property over
- New template operators
- Although some folks are averse to having too much logic in templates, you have to admit it’s kind of annoying to have to create functions, computeds, or other utilities to do tiny operations. Now you can perform assignment operations (
+=,??=, etc.), exponentiation (**), use template literals, andvoidandinoperators.voidis particularly useful to prevent functions with return values from overriding event listener behavior.
- Although some folks are averse to having too much logic in templates, you have to admit it’s kind of annoying to have to create functions, computeds, or other utilities to do tiny operations. Now you can perform assignment operations (
- Remove styles on component destroy
- From v17 on, component styles are removed from the DOM when its last instance is destroyed. This keeps redraws performant and the DOM lean! They’ll be added again if needed. If you need to opt out of this behavior, provide
REMOVE_STYLES_ON_COMPONENT_DESTROY.
- From v17 on, component styles are removed from the DOM when its last instance is destroyed. This keeps redraws performant and the DOM lean! They’ll be added again if needed. If you need to opt out of this behavior, provide
And now, the big ticket items.
Functional APIs
The shift to favoring functions over classes has been happening for years now, but we keep getting new goodies with every release. I discuss this at length in an article at the HeroDevs blog, in the context of some HTTP client changes, but in summary, functions are simpler to reason about, easier to make type-safe, and easier to compose. The framework benefits from said composition, making itself leaner when features are unused. inject is one of the best examples of this.
bootstrapApplication(App, {
providers: [
// Instead of raw DI tokens which are easy to mess up...
{
provide: APP_INITIALIZER,
useValue: "this is not strongly typed",
multi: true, // this flag is easily forgotten
},
// ...use strongly typed functions like this one
provideAppInitializer(() => true),
provideHttpClient(
/* don't need JSONP? the framework doesn't
contain unnecessary code up front */
// withJsonpSupport(),
withInterceptors([
(req, next) => next(req), // inline functions
configurableInterceptor(4), // this is harder to do with classes
])
),
],
});
Third-party libraries are doing this too, with the most prominent example being NgRx’s new Signals package. The benefits are clear, and we can only hope more and more APIs take advantage. Speaking of which…
Signals
Signals are the best thing to have happened to Angular in a long time. How can I express it all concisely? There’s enough to say about them to write 2 or more full articles!
In essence, they’re fine-grained reactive primitives tightly knit into the framework for reading and deriving state with precise change detection. (Which is why we can now go Zoneless!) They replace most component-local state and many RxJS patterns, lowering cognitive load while staying interoperable. Functional APIs (input, model, query signals) provide type-safe data flow, and render hooks like afterNextRender are client-only utilities for DOM work.
We could go on, including computed, linkedSignal, effect, resource, and more, but I’ll leave that to the excellent Signals guide in the official docs, or Cédric Exbrayat’s intro over at the Ninja Squad blog.
Angular Aria
Another big gap that has started getting filled was the need for a first-party headless UI library. Angular Material is great at what it does but comes with an opinionated design system that’s hard to modify (and sometimes just not meant to be). The CDK is a set of primitives, some of which are basically UI components, but most aren’t, and that’s not its primary goal anyway.
@angular/aria provides unstyled, thoughtfully built components with accessibility and performance in mind. It lives alongside CDK and Material and complements them well; you just bring your markup and styles, and Angular Aria gives you proper keyboard behavior, semantics, roles, and relationships so you don’t have to reinvent the wheel.
Angular Aria will launch in v21 including accordion, combobox, listbox, menu, radio-group, tabs, toolbar, tree, and more.
Signal forms
Saving the best for last: the new form APIs in Angular are here. Signal Forms are a ground-up redesign aimed at taking advantage of all the latest features, address long-standing complaints in the older patterns that were beyond fixing, and baking extensibility and composability in to prevent it from happening again.
Signal Forms are model driven, so by just providing a signal, you have a fully functional, HTML-compliant and compatible form.
readonly profile = signal({
fullName: '',
preferredName: '',
});
readonly profileForm = form(this.profile);
<form (submit)="save($event)">
<label>Full Name <input [field]="profileForm.fullName" /></label>
<label>Preferred Name <input [field]="profileForm.preferredName" /></label>
</form>
There’s so much to cover without its own content, so take a look at the official guide, but here are a few of my favorites:
- Reading values and states is much simpler now using signals and includes important separate states like
hiddenandreadonly. - Existing and custom validators are more powerful and cleaner. It’s easier than ever to make them apply dynamically, rely on sibling fields, separate error messages from behavior, and more.
- First class support for Standard Schema validation like Zod or Valibot.
- Functional APIs and Signals are great for composability.
- The nightmare that was
ControlValueAccessoris replaced byFormValueControlwhich just requires signal properties matching its interface and are bound automatically.
Signal Forms are the future, and even though now it seems confusing to have a third way of creating forms, I expect this approach to supersede the others in the end. It’s just that good.
There’s never been a better time to bet on Angular, and it’s going to keep getting better. All barriers to adoption are dropping off one by one: whether it’s the cognitive barriers caused by RxJS and change detection, or the ecosystem integration challenges brought by Zone.js, Webpack, or TypeScript decorators, it’s all coming together, so what are you waiting for?