Angular 20.1 has landed, bringing along a handful of interesting additions. Here's a rundown of the most significant updates:
🧠 Signals in DevTools
The Angular DevTools operates at the component level. That means you first pick a component, enable the SignalGraph, and only then can you inspect the signals alongside their contextual metadata.
This covers signal(), computed(), linkedSignal(), and even effect().
Keep in mind, this is still in an experimental phase. As such, the resource isn't shown as a single entity but rather as a cluster of individual signals.
There's also the option to label your signals using the debugName property.
Example:
// Define a signal with a debug name for easier DevTools debugging
const number = signal(1, {
debugName: 'number',
});
// Create a computed signal that doubles the `number` signal
const double = computed(() => number() * 2, {
debugName: 'doubleNumber',
});
// Set up an effect to log the value of `double` whenever it changes
effect(() => console.log(double()), {
debugName: 'logger',
});
🌐 Enhanced httpResource & HttpClient
Both the HttpClient and, by extension, the httpResource (which relies on HttpClient internally) got a welcome boost.
They now support passing through options exposed by the native fetch function. That includes handling for timeouts, caching, redirects, and more.
Example:
// Define an httpResource with native fetch options passed through
httpResource(() => ({
url: `/holiday/1/quiz`,
priority: 'high',
redirect: 'follow',
cache: 'no-cache',
credentials: 'same-origin',
mode: 'no-cors',
}));
// Equivalent call using HttpClient directly with fetch-like options
this.httpClient.get('/holiday/1/quiz', {
priority: 'high',
redirect: 'follow',
cache: 'no-cache',
credentials: 'same-origin',
mode: 'no-cors',
});
🧪 Testing with Bindings
Testing a component that relies on property bindings previously meant either crafting a wrapper component to mimic a parent or going through ComponentRef.setInput().
The new TestBed.createComponent() method now accepts a second argument to handle bindings directly.
This works for more than just property bindings — event bindings and two-way bindings are covered as well.
Take a component that has two property bindings, timeLeft and status:
export class QuizStatusComponent {
timeLeft = input.required<number>();
status = input.required<{ correct: number; incorrect: number }>();
}
The previous approach involved using the componentRef:
it('should show the time left', async () => {
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
}).createComponent(QuizStatusComponent);
// Setting the inputs
fixture.componentRef.setInput('timeLeft', 10);
fixture.componentRef.setInput('status', { correct: 0, incorrect: 0 });
const timeLeft = await screen.findByLabelText('Time remaining');
expect(timeLeft.textContent).toContain('Time Left: 10 seconds');
});
With Angular 20.1, we can achieve this far more cleanly through createComponent:
it('should show the time left', async () => {
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
}).createComponent(QuizStatusComponent, {
bindings: [
inputBinding('timeLeft', () => 10),
inputBinding('status', () => ({ correct: 0, incorrect: 0 })),
],
});
const timeLeft = await screen.findByLabelText('Time remaining');
expect(timeLeft.textContent).toContain('Time Left: 10 seconds');
});
🤖 Angular CLI and MCP
For AI-driven development workflows — which by now we're all presumably using — the Angular CLI ships with an MCP server.
After registering it in your IDE, you can start generating Angular code or ask Angular-specific questions straight from the editor.
Depending on your setup, registering the MCP server in Cursor involves something like this:
mcp.json
{
"mcpServers": {
"angular-cli": {
"command": "npx",
"args": ["@angular/cli", "mcp"]
}
}
}
🧩 Odds and Ends
There are moments when you need to update or assign property values upon a specific event. If that's a quick one-liner, you can now do it right in the template — thanks to a syntax extension that accommodates various assignment operators.
Given that signals represent the recommended path and logic has no place in templates, this is likely an uncommon scenario.
Finally, TypeScript now allows importing image files, which can then be base64-encoded and used in templates.
Before:
@Component({
template: `
<img alt="Eternal" src="assets/logo.png" />
`
})
export class Header {}
After:
import logo from '../../../assets/logo.png' with { loader: 'base64' };
@Component({
template: `
<img alt="Eternal" [src]="logo" />
`,
})
export class Header {
protected readonly logo = `data:image/png;base64,${logo}`;
}
Admittedly, the older form is more concise — however, the new approach bakes the image straight into the component, eliminating the need for a separate load.
