Angular 20: What's New?
New Requirements: TypeScript v5.8 and Node v20
Angular v20 is not ambiguous about its new requirements. You must now be on TypeScript v5.8 (which, by the way, was already supported since v19.2), so it's time to say goodbye to older TypeScript versions.
And for Node, welcome Node v20 as the new minimum. Angular v19 was the farewell tour for Node 18. Time to update those environments, folks!
2025 Style Guide: A Breath of Fresh Air!
The Angular Style Guide has received a major overhaul! Many recommendations have been trimmed down to focus on what really matters.
- Trimmed Filenames: Forget
UserComponentinuser.component.ts. It's now justUserinuser.ts. Same for directives, pipes, etc. The CLI is already enforcing this for new things. How long will it take for your muscle memory to adapt? - Lighter Folder Structure: The top-level
appfolder might disappear soon (though the CLI hasn't gotten there yet). - Property Visibility:
protectedis now the way for properties used only in your template, andreadonlyfor all those Angular-initialized properties (input(),output(), etc.). It just makes sense! - Class and Style Bindings: It's official!
[class.something]and[style.something]are the recommended champions overngClassandngStyle.
This is a big shift. New projects will adopt it by default. Existing ones? Well, you either migrate or stick to the old ways (the CLI helps with a setting for that, phew!).
Signal APIs: Mostly Greenlit and Stable!
Signals are the future, and the APIs are solidifying! Most are now stable:
effect()toSignal()toObservable()afterRenderEffect()afterNextRender()linkedSignal()PendingTasks
Heads up! afterRender() has been renamed to afterEveryRender() and is stable. Crucially, the old name is gone with no automatic migration. Oof, that could sting!
Also, TestBed.flushEffects() (that slightly elusive developer preview API) is deprecated. Use TestBed.tick() now, which runs the full sync process, much closer to real app behavior. effect() lost its forceRoot option (was anyone using it much?), and toSignal() dropped rejectErrors (good call, best practices for the win!). pendingUntilEvent() is still warming up in developer preview.
Zoneless: Out of Experimental, Into Developer Preview!
If signals are the future of reactivity, Zoneless is the future of change detection! It's no longer "experimental," it's now officially in developer preview.
provideExperimentalZonelessChangeDetectionis now justprovideZonelessChangeDetection.- The CLI flag
--experimental-zonelessis now just--zoneless.
The CLI will even ask if you want to enable it for new projects. Ready for #NoZone?
What's Leaving, What's Staying, and What Might Break Your Code:
As with any major version, some things are getting tossed:
- The
ngIf,ngFor,ngSwitchdirectives are officially deprecated. The built-in control flow (@if,@for,@switch) is king now. Start migrating; they'll likely be gone in v22!ng updatewill lend a hand. fixture.autoDetectChanges(boolean): The boolean parameter is gone. Just usefixture.autoDetectChanges(). Usingfixture.autoDetectChanges(false)in a zoneless test? That will now throw an error.TestBed.get(): Finally, finally GONE! It was deprecated way back in Angular v9.TestBed.inject()is your friend (and has been for a while). An automatic migration will handle this.InjectFlagsEnum: Removed. Option objects for DI APIs (likeinject()) have been the norm since v14.1.DOCUMENTToken: Moved from@angular/commonto@angular/core. A migration will update your imports.@angular/platform-browser-dynamic: Deprecated in favor of@angular/platform-browser. You'll have to manually update imports for this one for now.@angular/platform-server/testing: Also deprecated, with no replacement. E2E tests are now the recommended way to verify SSR apps.- HammerJS Integration: Deprecated. HammerJS hasn't seen an update in 8 years. It's time to say goodbye to those framework entities.
ng-reflect-*Attributes: Gone by default in dev mode. They were for old devtools. If you relied on them (you probably shouldn't have!), you can re-enable them withprovideNgReflectAttributes(). But maybe... just don't?
Templates: New Tricks Up Their Sleeve!
Your templates just got a bit more expressive:
- Exponentiation:
{{ 2 ** 3 }}is now possible. Makes calculations easier! - Tagged Template Literals: Yep,
{{ translate\app.title` }}` is here (technically since v19.2, but it's settled now). voidOperator: Use it like<button (click)="void selectUser()">to explicitly ignore a function's return, especially useful for event listeners wherereturn falsemight prevent default behavior.inOperator: Check for properties like@if ('invoicing' in permissions). Super useful!
Extended Diagnostics: The Compiler Keeps You in the Loop!
More built-in checks to catch common errors:
missingStructuralDirective: Using something like*ngTemplateOutletbut forgot to importNgTemplateOutlet? The compiler will now let you know (withstrictTemplates).uninvokedTrackFunction: Wrote@for (user of users; track getUserId)instead oftrack getUserId(user)? You'll get a friendly nudge.unparenthesizedNullishCoalescing: Mixing??with&&or||(e.g.,a ?? b && c) now requires parentheses for clarity, like{{ a ?? (b && c) }}. TypeScript does this, so it's great to see in templates too!
You can suppress these in tsconfig.json if you really want to, but... why would you?
Host Binding Type Checking: No More Mysteries!
There's a new compiler option typeCheckHostBindings (already in new CLI projects). If you use host metadata in decorators (or @HostBinding/@HostListener), the compiler now checks:
- If the binding/listener target (e.g.,
valuein[value]="value()") is actually valid for the host element. - If the property in your component/directive class (e.g.,
value()) actually exists.
This is huge for catching typos and ensuring your host bindings are legit!
Error Handling: Fewer Errors Slipping Through!
provideBrowserGlobalErrorListeners: A new provider (added by default in new CLI projects) to register global error listeners in the browser. Catches errors Angular might otherwise miss.- Errors in event listeners are now reported to Angular's internal error handler. This means you might see errors in tests that were previously silent. Time to fix them (or use
rethrowApplicationErrors: falseinconfigureTestingModuleas a last resort).
Dynamically Created Components: Level Up!
createComponent() (and ViewContainerRef.createComponent) got way cooler in v20! You can now pass options to:
- Specify
directivesto apply to the dynamic component. - Provide input values using the new
inputBinding()function. - Declare two-way bindings with
twoWayBinding(). - Listen to outputs with
outputBinding().
This is a huge improvement over calling setInput after the first change detection. More power and control! Will this come to TestBed.createComponent() too?
Forms: Still Waiting on Signals, but...
No signal-based forms yet (we're all on the edge of our seats for that!). But v20 brings a couple of small but welcome tweaks:
userForm.resetForm(undefined, { emitEvent: false }): Reset forms without triggering events.markAllAsDirty(): Finally, a method onAbstractControlto mark a control and all its descendants asdirty.markAllAsTouched()was missing a sibling!
Router: Smoother Navigation Ahead!
Some cool improvements for the router:
- Scroll Options: Pass native scroll options to
ViewportScroller.scrollToAnchor()/scrollToPosition(). For example,behavior: 'smooth'for that slick scrolling effect. - Resolvers with More Context: Child route resolvers can now access resolved data from their parent route!
route.data.userfrom the parent will be available. Less gymnastics to get data! - Async Redirects: The
redirectTooption in route configs can now accept a function that returns a Promise or an Observable for async redirects. (Technically a breaking change due to the evolving return type). - Custom Element Support: Writing Web Components? You can now use a custom element as the host of a
RouterLink.
Http: Evolving resource APIs and keepalive!
resourceAPI Changes: Thequeryparameter ofresource()is nowparams. ForrxResource(),loaderis nowstream. Thereloadmethod moved toWritableResource(only mutable resources can be reloaded).httpResourceUpdates: Themapoption is nowparse. You can specify the HTTP context in the options. And, the request must now be reactive (e.g.,httpResource<User[]>(() => '/users')instead of just the URL string).keepaliveSupport:HttpClientnow supports thekeepaliveoption when using the Fetch API (enabled withwithFetch()). Requests won't be aborted if the page unloads. Useful for things like analytics.
Profiling: See Where Your App's Performance is Hit!
There's a new enableProfiling() function in @angular/core. Call this, and Angular will use the browser's Performance API to label framework operations (change detection, templates, outputs, defer, etc.). Then, open Chrome Devtools, record a performance profile, and see the custom "Angular" track. Finally, a clear view of internal performance!
Devtools: Better Insights!
Angular Devtools are getting smarter:
OnPushcomponents are now marked as such in the component tree.- Deferred blocks (
defer) are also displayed. - Signal support is improving – we should see the signal tree soon! Peeking under the hood is now easier.
SSR: Stable APIs and Streamlined Config!
Good news for Server-Side Rendering:
- The
withI18nSupport()andwithIncrementalHydration()APIs are now stable! provideServerRendering()(now in@angular/ssrinstead of@angular/platform-server) is combined withprovideServerRoutesConfig()into a singleprovideServerRendering(withRoutes(serverRoutes)). A migration will handle this.- New CLI apps with
--ssrget Express v5 and server routing support by default (the--server-routingoption is gone).
Angular CLI: This is HUGE!
The CLI saw a ton of changes. Brace yourselves:
- Updated Naming for 2025 Style Guide: As mentioned,
user.ts(with classUser) instead ofuser.component.ts(classUserComponent). Same for directives, services. Pipes, resolvers, etc. use hyphens in filenames (from-now-pipe.ts). A migration forangular.jsonconfigures your existing projects to use the old convention if you want a smooth transition. - TypeScript Config: The
moduleoption is nowpreserve(better reflecting modern bundlers).tsconfig.jsonuses a "solution" style, referencingtsconfig.app.jsonandtsconfig.spec.json. - Simplified
angular.json: New projects use@angular/builddirectly, dropping@angular-devkit/build-angularand its transitive Webpack dependencies. That's almost 200 Mb less innode_modules! Some options likeoutputPathare also removed as they have sensible defaults. Lighter and more powerful! - Browserslist Config: Now targets the "widely available" base (browsers released < 30 months from the main Baseline set). More consistent and realistic browser support.
- Sass Package Importers: You can now use
pkg:importers, like@use 'pkg:@angular/material' as mat;. - Ahead of Time (AoT) Testing and Template Code Coverage: Add
"aot": trueto your test options inangular.json. Run tests in the same mode as production! Plus, you get code coverage for templates. - Testing with Vitest (Experimental!): Big news! The CLI now supports running tests with Vitest. There's a new
@angular/build:unit-testbuilder. Karma and Jasmine might have a new challenger.- Set it up with
"runner": "vitest". You'll need a"buildTarget". - Defaults to Node with
jsdom(install it). - Update
tsconfig.spec.jsontypesto["vitest/globals"]. - Browser Mode: Yep!
"browsers": ["chromium"](or firefox, webkit). Uses Playwright or WebdriverIO (install one). - Watch mode is faster (only runs affected tests). Full runs might be a bit slower than Karma.
--no-watchis often implied in CI.- New
--debugflag (Vitest + jsdom/Playwright). - Limitations: No custom Vitest config file yet. But the
providersFileoption lets you set up things like zoneless testing. Reporter and coverage exclusions are inangular.json.
- Set it up with
- Automatic Chrome Workspace Folders: The Vite dev server now helps Chrome Devtools map your project files, allowing direct editing from Devtools that saves to your disk! Enable it in Chrome flags.
- Sourcemaps without Sources: Generate sourcemaps without embedding the original source code (
"sourcesContent": false). Great for production error reporting without exposing all your code.
Angular and its Future with AI!
Angular is positioning itself to facilitate the development of applications with generative AI capabilities:
File: llms.txt An llms.txt file is maintained in the Angular repository. This file helps large language models (LLMs) discover the latest and most correct Angular documentation and code examples, so they generate more modern and accurate code, avoiding issues with obsolete APIs or syntax.
Guides and Resources: Guides and resources are being provided, angular.dev/ai, including examples and live streams showing how to integrate Angular with tools like Genkit and Vertex AI from Google Cloud.
What We Might See in Angular v21
- Selectorless Components: Imagine this:
Components used by their class name directly in templates! Directives might use animport { User } from './user/user'; // TS import is still needed @Component({ template: '<User [name]="name()" (selected)="selectUser()" />', // NO Angular 'imports' array needed for User! }) export class App { /* ... */ }@prefix (e.g.,<User @CdkDrag />). Pipes also by class name ({{ date | FromNowPipe }}). The syntax is FAR from final, but the compiler work has begun. Mind-blowing, right? This could be revolutionary! An RFC should be coming. - Signal-based Forms (The Third Way?): Get ready for a potential new way of doing forms, separate from template-driven and reactive!
A new// VERY EARLY PROTOTYPE - DO NOT USE YET! @Component({ selector: "user-form", imports: [FieldDirective], // New directive template: ` <form> <label>Username: <input [field]="userForm.username" /></label> <label>Name: <input [field]="userForm.name" /></label> </form> `, }) class UserFormComponent { userModel = signal<UserModel>({ /* ... */ }); protected readonly userForm: Field<User> = form<User>( // new form() function userModel, // data to edit (userPath: FieldPath<User>) => { // schema for dynamic behavior and validation disabled(userPath.username, () => true, "Cannot change username"); required(userPath.name); error(userPath.age, ({ value }) => value() < 18, "Must be over 18"); } ); }form()function andFieldclass to handle form state with signals. Check out the design doc if you're curious, or wait for the RFC!