Angular 20.2.0: What's new
Angular 20.2.0 introduces cleaner templates, smarter tooling, and improved debugging as some of its new features.
1. 🔥 @angular/core
a) Support TypeScript 5.9
b) Angular Components Now Use Real Tag Names in Tests - PR
Until now, there was a subtle but important difference between how your components behaved in tests versus production:
In Production: angular creates components using tag names inferred from their selectors:
@Component({selector: 'user-profile', template: '...'})
// Creates actual <user-profile> element
In Tests (Previously): TestBed always wrapped components in generic <div>
elements:
const fixture = TestBed.createComponent(UserProfileComponent);
// Always created <div> regardless of selector
This mismatch could hide CSS styling issues, accessibility problems, or any logic that depended on the actual element tag name.
Angular now offers the inferTagName
option to align test behavior with production:
// Option 1: Per-component basis
const fixture = TestBed.createComponent(UserProfileComponent, {
inferTagName: true
});
// Now creates <user-profile> element in tests
// Option 2: Configure globally for all tests
TestBed.configureTestingModule({
inferTagName: true,
// ... other config
});
@Component({selector: 'my-button'})
// → Creates <my-button>
@Component({selector: 'custom-input[type="text"]'})
// → Creates <custom-input>
@Component({selector: '[data-widget]'})
// → Falls back to <div> (no tag name in selector)
@Component({template: '...'})
// → Creates <ng-component> (no selector)
c) Property-to-Attribute Mapping - PR
Previously, developers faced a choice between verbose syntax and potential SSR problems when working with ARIA attributes:
// Verbose but SSR-safe
<button [attr.aria-label]="buttonText">
// Clean but potential SSR issues
<button [ariaLabel]="buttonText">
The problem: ARIA DOM properties don't always correctly reflect as HTML attributes during server-side rendering, potentially breaking accessibility for users with assistive technologies.
Angular now supports clean property binding syntax that automatically renders as proper HTML attributes:
// All of these now work identically and render correctly on SSR
<button [aria-label]="buttonText"> // New simplified syntax
<button [ariaLabel]="buttonText"> // Existing camelCase
<button [attr.aria-label]="buttonText"> // Explicit attribute binding
The enhancement includes intelligent mapping for all standard ARIA properties:
@Component({
template: `
<div [ariaLabel]="label"
[ariaExpanded]="isExpanded"
[ariaDisabled]="isDisabled"
[aria-hidden]="isHidden">
<!-- All render as proper aria-* attributes -->
</div>
`
})
Automatic conversions include:
ariaLabel
→aria-label
ariaExpanded
→aria-expanded
ariaHasPopup
→aria-haspopup
Plus 30+ other ARIA properties
The system intelligently prioritizes component inputs over attribute binding:
@Component({
selector: 'custom-button'
})
class CustomButton {
@Input() ariaLabel!: string; // This takes precedence
}
// Binds to component input, not HTML attribute
<custom-button [ariaLabel]="text">
This feature particularly shines in SSR scenarios where proper attribute rendering is crucial for accessibility:
// Server renders: <button aria-label="Save Document">
// Client hydrates seamlessly with identical markup
<button [aria-label]="saveLabel">
d) Promote zoneless to stable
As of Angular v20.2, Zoneless (provideZonelessChangeDetection
) Angular is now stable and includes improvements in error handling and server-side rendering.
e) Control Flow Enhancement: as
Aliases in @else if
Blocks - PR
<!-- ✅ Enhanced: as works in @else if blocks -->
@if (user$ | async; as user) {
<h1>Welcome, {{user.name}}</h1>
<p>Role: {{user.role}}</p>
} @else if (userRole$ | async; as role) {
<!-- 🎉 Now we can use 'as' here too! -->
<p>Loading user data for {{role}}...</p>
<p>Please wait while we fetch your {{role}} profile...</p>
} @else {
<p>Please log in</p>
}
2. 🔥 @angular/common/http
a) Add referrer & integrity support for fetch requests in httpResource - PR
Currently, Angular's httpResource
does not expose the referrer
and integrity
options from the underlying Fetch API.
Exposing these options would provide developers with finer control over the request's referrer and subresource integrity validation, which are important for ensuring security, privacy, and trust in critical resource fetching scenarios.
httpResource(() => ({
url: '${CDN_DOMAIN}/assets/data.json',
method: 'GET',
referrer: 'no-referrer',
integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GhEXAMPLEKEY='
}));
b) New redirected
Property - PR
Angular's HttpClient now provides complete visibility into HTTP redirects with a new redirected
property that aligns with the native Fetch API.
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private http: HttpClient) {}
getUserProfile(userId: string) {
return this.http.get(`/api/users/${userId}`, {
observe: 'response'
}).pipe(
tap(response => {
if (response.redirected) {
console.log('User profile request was redirected');
console.log('Original URL:', `/api/users/${userId}`);
console.log('Final URL:', response.url);
// Track redirect for analytics
this.trackRedirect('user-profile', response.url);
}
}),
map(response => response.body)
);
}
private trackRedirect(endpoint: string, finalUrl: string | null) {
// Send redirect data to analytics service
analytics.track('http_redirect', {
endpoint,
finalUrl,
timestamp: new Date().toISOString()
});
}
}
3. 🔥 @angular/language-service
a) Angular Language Service Now Detects Deprecated APIs in Templates - PR
export class LegacyComponent {
/**
* @deprecated Use newProp instead
*/
@Input() oldProp: string;
}
<!-- Your IDE will now show a suggestion diagnostic here -->
<legacy-component [oldProp]="someValue"></legacy-component>
~~~~~~~ 'oldProp' is deprecated. Use newProp instead
b) Auto-Import for Angular Attributes: No More Manual Directive Imports - PR
When you use directive attributes in your templates, the IDE will now automatically suggest importing the directive for you.
<!-- Type this: -->
<div appHighlight="yellow">
↑
💡 Quick Fix: Import HighlightDirective from '@app/highlight'
// Automatically added to your component:
import { HighlightDirective } from '@app/highlight';
@Component({
imports: [HighlightDirective], // ← Added automatically!
template: `<div appHighlight="yellow">Content</div>`
})
export class MyComponent { }
4. 🔥 @angular/service-worker
a) Take Control of Service Worker Updates: Angular's New updateViaCache Configuration Option - PR
The new updateViaCache
option allows you to specify exactly when the browser should check its HTTP cache when updating service workers or any scripts imported via importScripts()
. This translates to:
Improved Performance Control: choose between 'imports'
, 'all'
, or 'none'
to optimize your update strategy based on your application's specific needs.
Better Development Experience: gain more predictable behavior during development cycles, especially when testing service worker updates.
Production Optimization: fine-tune caching strategies for production deployments where update timing is critical.
export const appConfig: ApplicationConfig = {
providers: [
provideServiceWorker('ngsw-worker.js', {
enabled: !isDevMode(),
updateViaCache: 'imports', // New cache control option
registrationStrategy: 'registerWhenStable:30000',
}),
],
};
b) New proactive storage monitoring system that prevents cache failures before they happen - PR
The system monitors storage usage and alerts when capacity reaches 95% of the available quota:
private async detectStorageFull() {
try {
const estimate = await navigator.storage.estimate();
const { quota, usage } = estimate;
// Handle cases where quota or usage might be undefined
if (typeof quota !== 'number' || typeof usage !== 'number') {
return;
}
// Consider storage "full" if usage is >= 95% of quota
// This provides a safety buffer before actual storage exhaustion
const usagePercentage = (usage / quota) * 100;
const isStorageFull = usagePercentage >= 95;
if (isStorageFull) {
this.debugHandler.log(
'Storage is full or nearly full',
`DataGroup(${this.config.name}@${this.config.version}).detectStorageFull()`,
);
}
} catch {
// Error estimating storage, possibly by unsupported browser.
}
}
For PWA Applications
// Example of how this helps PWAs maintain offline functionality
if (storageNearFull) {
// Implement cleanup strategies
// Prioritize critical resources
// Notify user about storage constraints
}
For Content-Heavy Apps
// Applications with large caching needs benefit from early warnings
if (storageApproachingLimit) {
// Implement cache eviction policies
// Compress cached data
// Switch to selective caching strategies
}
Developers now get clear indicators when storage issues occur:
Storage is full or nearly full -DataGroup(api@v1.2.3).detectStorageFull()
c) Real-Time Version Failure Notifications - PR
Previously, when a Service Worker version encountered critical failures, applications would experience degraded functionality without clear visibility into the root cause. The system introduces a new VersionFailedEvent
that provides comprehensive failure information:
/**
* An event emitted when a specific version of the app has encountered a critical failure
* that prevents it from functioning correctly.
*/
export interface VersionFailedEvent {
type: 'VERSION_FAILED';
version: {hash: string; appData?: object};
error: string;
}
Automatic Client Notification
When a version fails, the Service Worker automatically notifies all affected clients. Applications can listen for version failures using the existing SwUpdate
service:
@Component({
selector: 'app-root',
template: `
<div class="app">
@if (versionError()) {
<div class="error-banner">
<h3>Application Update Issue</h3>
<p>{{versionError()}}</p>
<button (click)="handleVersionFailure()">
Refresh Application
</button>
</div>
}
<router-outlet />
</div>
`,
styles: [`
.error-banner {
background: #fee;
border: 1px solid #fcc;
border-radius: 4px;
padding: 16px;
margin: 8px;
color: #c66;
}
.error-banner button {
background: #c66;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin-top: 8px;
}
`]
})
export class AppComponent implements OnInit {
versionError = signal<string | null>(null);
constructor(private swUpdate: SwUpdate) {}
ngOnInit() {
// Listen for all version events
this.swUpdate.versionUpdates.subscribe(event => {
switch (event.type) {
case 'VERSION_FAILED':
this.handleVersionFailure(event);
break;
case 'VERSION_READY':
this.handleVersionReady(event);
break;
case 'VERSION_DETECTED':
this.handleVersionDetected(event);
break;
}
});
}
private handleVersionFailure(event: VersionFailedEvent) {
console.error('Service Worker version failed:', event);
// Set user-friendly error message
this.versionError.set(
`Application version ${event.version.hash.substring(0, 8)} has encountered an error. ` +
`Please refresh to restore full functionality.`
);
// Optional: Report to error monitoring service
this.reportVersionFailure(event);
}
private handleVersionReady(event: VersionReadyEvent) {
// Clear any previous errors when new version is ready
this.versionError.set(null);
console.log('New version ready:', event.latestVersion.hash);
}
private handleVersionDetected(event: VersionDetectedEvent) {
console.log('New version detected:', event.latestVersion.hash);
}
handleVersionFailure() {
// Force page reload to get latest version
window.location.reload();
}
private reportVersionFailure(event: VersionFailedEvent) {
// Example: Send to monitoring service
// errorService.report({
// type: 'service-worker-version-failure',
// version: event.version.hash,
// error: event.error,
// userAgent: navigator.userAgent,
// timestamp: new Date().toISOString()
// });
}
}
Testing the Feature
The new functionality includes comprehensive test coverage:
it('processes version failed events with cache corruption error', (done) => {
update.versionUpdates.subscribe((event) => {
expect(event.type).toEqual('VERSION_FAILED');
expect((event as VersionFailedEvent).version).toEqual({
hash: 'B',
appData: {name: 'test-app'},
});
expect((event as VersionFailedEvent).error).toContain('Cache corruption detected');
done();
});
mock.sendMessage({
type: 'VERSION_FAILED',
version: {
hash: 'B',
appData: {name: 'test-app'},
},
error: 'Cache corruption detected during resource fetch',
});
});
d) Better Service Worker Debugging: Angular Now Catches Message Errors - PR
When Service Workers receive corrupted or badly formatted messages, they would previously fail silently. Instead of silent failures, you now get clear logs when messages fail:
[SW] Message error occurred - data could not be deserialized
[SW] Driver.onMessageError(origin: https://myapp.com)
e) Modern Service Workers: Angular Adds ES Module Support - PR
Angular Service Workers now support ES modules with a new type
configuration option, bringing modern JavaScript features like import
and export
to your Service Worker scripts.
// Enable ES modules in Service Workers
export const appConfig: ApplicationConfig = {
providers: [
provideServiceWorker('ngsw-worker.js', {
enabled: !isDevMode(),
type: 'module', // Enable ES module features
scope: '/app',
updateViaCache: 'imports'
}),
],
};
f) Service Worker Error Handling: unhandled promise rejections - PR
Unhandled promise rejections happen when a Promise fails but there's no .catch()
block or error handling to deal with it:
// Before: Silent failure scenario
class DataSyncService {
syncUserPreferences() {
// This could fail silently if the API is down
fetch('/api/sync-preferences', {
method: 'POST',
body: JSON.stringify(this.preferences)
})
.then(response => response.json())
.then(result => {
// Update local cache
this.updateLocalCache(result);
});
// No .catch() - failures would be silent!
}
}
// After: With the new logging, you'd see in DevTools:
// "Unhandled promise rejection occurred: NetworkError: Failed to fetch"
5. 🔥 @angular/compiler-cli
a) Smart Template Diagnostics: Catch Function Reference Mistakes Before Runtime - PR
How many times have you written something like this and wondered why it displays [Function]
instead of the actual value?
@Component({
template: `<p>Welcome {{ getUserName }}</p>` // Missing parentheses!
})
class WelcomeComponent {
getUserName(): string {
return 'Sarah';
}
}
The Solution: NG8117 Diagnostic
Angular now automatically detects this pattern and shows a clear warning:
❌ Before: Silent runtime behavior displaying [Function]
✅ Now: Compile-time warning with diagnostic NG8117
6. 🔥 @angular/animations
Say goodbye to @angular/animations (60KB bundle impact) and hello to native CSS animations with animate.enter
and animate.leave
in Angular 20.2!
7. 🔥 @angular/forms
a) FormArray Gets Efficient Multi-Control Push Support - PR
Previously, adding multiple controls to a FormArray required individual push()
calls, each triggering change detection and validation events:
// Old approach - inefficient for large datasets
const formArray = new FormArray([]);
const newControls = [
new FormControl('value1'),
new FormControl('value2'),
new FormControl('value3'),
// ... potentially hundreds more
];
// Each push triggers valueChanges, statusChanges, and validation
newControls.forEach(control => {
formArray.push(control); // Triggers events every time!
});
The push()
method now supports both individual and batch operations:
// Before: Only single controls
push(control: TControl, options?: { emitEvent?: boolean }): void
// After: Single controls OR arrays of controls
push(control: TControl | Array<TControl>, options?: { emitEvent?: boolean }): void
8. 🔥 @angular/router
a) Router Goes Reactive: New currentNavigation
Signal Replaces Deprecated Method - PR
// Old approach - complex and inefficient
export class App {
private router = inject(Router);
// Required complex Observable setup
isNavigating = toSignal(this.router.events.pipe(
map(() => !!this.router.getCurrentNavigation()) // Deprecated method
));
// Manual state checking
checkNavigationState() {
const nav = this.router.getCurrentNavigation(); // Deprecated
return nav ? 'Navigating...' : 'Idle';
}
}
// New approach - simple and reactive
export class App {
private router = inject(Router);
// Clean, reactive navigation state
isNavigating = computed(() => !!this.router.currentNavigation());
// Derive any navigation state reactively
navigationState = computed(() => {
const nav = this.router.currentNavigation();
return nav ? 'Navigating...' : 'Idle';
});
}
9. 🔥 @angular/platform-browser
a) Warns About Hydration and Blocking Navigation Conflicts - PR
When building Angular applications with server-side rendering (SSR), developers sometimes unknowingly combine features that don't work well together:
// This configuration causes subtle runtime issues
bootstrapApplication(AppComponent, {
providers: [
provideClientHydration(), // Enable hydration
provideRouter(routes,
withEnabledBlockingInitialNavigation() // Enable blocking navigation
)
]
});
// Console output:
// ⚠️ Configuration error: found both hydration and enabledBlocking initial navigation
// in the same application, which is a contradiction.
b) Angular Introduces IsolatedShadowDom
Encapsulation - PR
Ever tried building a reusable component only to have it break when used in different projects?:
// Your beautiful blue button component
@Component({
template: '<button class="btn">Click me</button>',
styles: ['.btn { background: blue; color: white; }'],
encapsulation: ViewEncapsulation.ShadowDom
})
/* Their global styles */
.btn { background: red !important; }
Your blue button becomes red! 😱 Shadow DOM was supposed to prevent this, but Angular's implementation had a leak. The solution:
// Now with TRUE isolation
@Component({
template: '<button class="btn">Click me</button>',
styles: ['.btn { background: blue; color: white; }'],
encapsulation: ViewEncapsulation.IsolatedShadowDom // 🎉
})
// Result: Your button stays blue EVERYWHERE! 💙
When to Choose Each Mode
Use ViewEncapsulation.ShadowDom
when:
You need backward compatibility with existing applications
You want some global styles to be available in your component
You're gradually migrating to Shadow DOM
Use ViewEncapsulation.IsolatedShadowDom
when:
Building reusable component libraries
Creating embeddable widgets for third-party sites
Need guaranteed style isolation
Following web standards precisely
Building micro-frontends that must be completely isolated
10. 🔥 @angular/cli
a) New Angular MCP features
Angular CLI just added powerful AI integration capabilities through MCP (Model Context Protocol) server functionality.
Thanks for reading so far 🙏
I’d like to have your feedback, so please leave a comment, clap or follow. 👏
Spread the Angular love! 💜
If you liked it, share it among your community, tech bros and whoever you want! 🚀👥
Don't forget to follow me and stay updated: 📱
Thanks for being part of this Angular journey! 👋😁