· development · 11 min read
Angular surprised me after 10 years
I left Angular back in the AngularJS days and never looked back — until a recent project forced me to. What I found was a completely different framework. Here are the 10 improvements that genuinely shocked me and why React and Vue developers should pay attention.

I wrote off Angular. I was wrong.
Let me be honest — I abandoned Angular roughly a decade ago. Back then, the framework felt heavy, over-engineered, and burdened with ceremony. NgModules, decorators everywhere, RxJS for everything, zone.js magic that nobody fully understood. I moved to React, dabbled in Vue, and like many developers in my circle, I quietly assumed Angular was the framework you used because your enterprise employer told you to — not because you wanted to.
Then a recent project landed on my desk that required Angular. The newest Angular. And what I found there genuinely surprised me. The framework I remembered and the framework I worked with were barely the same thing. Angular has undergone one of the most ambitious and well-executed modernization efforts I’ve seen in the JavaScript ecosystem.
If you’re a React or Vue developer who dismissed Angular years ago like I did, this article is for you. Here are the 10 features and improvements that changed my mind.
1. Signals — reactive state without the ceremony
This was the single biggest shock. Angular now has a first-class reactive primitive system that feels remarkably familiar if you’ve used Vue’s ref() or Solid’s createSignal().
import { signal, computed, effect } from '@angular/core';
const count = signal(0);
const doubled = computed(() => count() * 2);
effect(() => {
console.log(`Count: ${count()}, Doubled: ${doubled()}`);
});
count.set(5);
count.update(v => v + 1);If you’re coming from React, imagine useState but without the re-render-the-entire-component overhead. If you’re coming from Vue, it’s essentially ref() and computed() with a slightly different API surface. The mental model is nearly identical.
What makes Angular’s implementation particularly impressive is how deeply it integrates with the framework. Signal-based input(), output(), view queries, and model bindings mean that signals aren’t bolted on — they’re woven into Angular’s DNA. YouTube reported a 35% improvement in input latency after adopting Angular Signals with Wiz, which speaks volumes about real-world impact.
Angular v20 stabilized effect, linkedSignal, and toSignal. By v21 (the current version), signals are the recommended way to manage state. RxJS is still there when you need it, but it’s no longer the default answer to everything.
2. Standalone components — NgModules are gone
Remember @NgModule? The configuration files that seemed to multiply endlessly, declaring components, importing other modules, exporting things, providing services? That’s effectively dead.
import { Component, input } from '@angular/core';
import { RouterLink } from '@angular/router';
interface User {
id: number;
name: string;
}
@Component({
selector: 'app-user-card',
standalone: true,
imports: [RouterLink],
template: `
<div class="user-card">
<h2>{{ user().name }}</h2>
<a [routerLink]="['/users', user().id]">View profile</a>
</div>
`
})
export class UserCardComponent {
user = input.required<User>();
}That’s a complete, self-contained component. No module declaration needed. It imports exactly what it depends on and nothing more. If you’re a React developer, this should feel natural — it’s how components should work. If you’re a Vue developer, think of it as Angular’s equivalent of a Single File Component’s simplicity.
Standalone has been the default since Angular 17, and by now the ecosystem has fully embraced it. The migration path was handled thoughtfully — Angular CLI provides automated schematics that convert module-based projects to standalone with a single command.
3. Built-in control flow — templates that finally make sense
The old structural directives (*ngIf, *ngFor, *ngSwitch) always felt like Angular’s weakest point compared to Vue’s v-if / v-for or React’s inline JSX expressions. The new built-in control flow syntax fixes this entirely.
The old way:
<div *ngIf="user; else loading">
<h2>{{ user.name }}</h2>
</div>
<ng-template #loading>
<p>Loading...</p>
</ng-template>
<ul>
<li *ngFor="let item of items; trackBy: trackById">
{{ item.name }}
</li>
</ul>The new way:
@if (user()) {
<h2>{{ user().name }}</h2>
} @else {
<p>Loading...</p>
}
@for (item of items(); track item.id) {
<li>{{ item.name }}</li>
} @empty {
<p>No items found.</p>
}
@switch (status()) {
@case ('active') { <span class="badge-active">Active</span> }
@case ('inactive') { <span class="badge-inactive">Inactive</span> }
@default { <span>Unknown</span> }
}The @if, @for, and @switch blocks are cleaner, more readable, and — critically — enable better performance optimization under the hood. The @for block requires a track expression (no more forgetting trackBy), and the @empty fallback is a nice touch that neither React nor Vue handles this elegantly out of the box.
As a React developer, I appreciate that this is closer to real control flow. As someone who’s used Vue templates, the readability is on par if not better.
4. Zoneless change detection — the magic is gone (in a good way)
Zone.js was always Angular’s most controversial design decision. It monkey-patched browser APIs to automatically detect changes, which was “magical” in the worst sense — hard to debug, unpredictable in edge cases, and a performance tax on every async operation.
Starting with Angular 21, new projects are zoneless by default. Change detection is now driven by signals and explicit notifications rather than global interception of every setTimeout, Promise, and event listener.
// Angular 21+ new projects are zoneless by default
bootstrapApplication(AppComponent);For React and Vue developers, this might not sound revolutionary — your frameworks never had this problem. But for Angular, it’s transformative. Applications are more predictable, easier to debug, and measurably faster. The removal of zone.js reduces bundle size and eliminates an entire category of subtle bugs that used to plague Angular developers. If you’re enabling zoneless manually in Angular 20, you would use provideZonelessChangeDetection() during bootstrap.
5. @defer blocks and incremental hydration — lazy loading reimagined
This is where Angular genuinely leapfrogs the competition. The @defer block provides declarative lazy loading at the template level with a level of granularity that React’s Suspense + lazy() and Vue’s defineAsyncComponent can only envy.
@defer (on viewport) {
<app-heavy-chart [data]="chartData()" />
} @placeholder {
<div class="chart-skeleton"></div>
} @loading (minimum 200ms) {
<app-spinner />
} @error {
<p>Failed to load chart component.</p>
}The trigger conditions are where it gets powerful: on viewport, on interaction, on hover, on idle, on timer(5s), or on immediate. You can combine multiple triggers and even use when conditions with signals.
But the real killer feature is incremental hydration for SSR applications. When paired with Angular’s server-side rendering, @defer blocks can remain dehydrated on the client and only hydrate when specific conditions are met:
@defer (on idle; hydrate on interaction) {
<app-comments [postId]="postId()" />
}This means the server renders the full HTML, the client receives it instantly, but the JavaScript for that component only loads and activates when the user actually interacts with it. Angular even captures and replays user events that occur before hydration completes, so no clicks are lost.
Neither React nor Vue has anything this seamless at the framework level.
6. The inject() function — dependency injection without the boilerplate
Angular’s dependency injection has always been its secret weapon — a truly powerful IoC container built into the framework. The problem was the syntax: constructor-based injection was verbose and felt alien to developers coming from React or Vue.
The old way:
@Component({ /* ... */ })
export class DashboardComponent {
constructor(
private userService: UserService,
private authService: AuthService,
private router: Router,
@Inject(API_BASE_URL) private apiUrl: string
) {}
}The new way:
@Component({ /* ... */ })
export class DashboardComponent {
private userService = inject(UserService);
private authService = inject(AuthService);
private router = inject(Router);
private apiUrl = inject(API_BASE_URL);
}It’s cleaner, more readable, and has better type inference. But beyond syntax, inject() unlocks patterns that weren’t possible before — you can use it in factories, router guards, and helper functions that run inside an injection context. Angular CLI provides an automated migration schematic (ng generate @angular/core:inject-migration) that converts your entire codebase in one shot.
For React developers used to useContext and custom hooks, the inject() function feels much more natural than the old constructor approach.
7. esbuild + Vite dev server — builds that actually feel fast
Remember waiting 30+ seconds for an Angular project to compile? The Angular CLI has completely replaced Webpack with esbuild for builds and Vite as the dev server. The difference is night and day.
esbuild, written in Go, handles the actual bundling using multi-core parallelism. Vite serves modules using native ES imports during development, enabling near-instant Hot Module Replacement. The combination delivers:
- Sub-second cold starts for development
- Instant HMR — save a file, see the change immediately
- Dramatically faster production builds — what used to take minutes now takes seconds
If you’ve been using Vite with React or Vue, Angular’s dev experience now feels identical. There’s no more “Angular tax” on build times. This was the default since Angular 17, and by now the tooling is rock-solid.
8. Signal-based forms — finally, forms that don’t fight you
Angular’s forms were always powerful but notoriously complex. FormGroup, FormControl, FormArray, validators, valueChanges observables — the learning curve was steep even for experienced developers. Angular 21 introduces Signal Forms (currently experimental) that fundamentally rethink the approach.
import { signal } from '@angular/core';
import { form, required, email } from '@angular/forms/signals';
interface UserModel {
name: string;
email: string;
}
const userModel = signal<UserModel>({ name: '', email: '' });
const userForm = form(userModel, (schemaPath) => {
required(schemaPath.name, { message: 'Name is required' });
required(schemaPath.email, { message: 'Email is required' });
email(schemaPath.email, { message: 'Enter a valid email' });
});
// Type-safe access — no more .get('name')!
const name = userForm.name().value();
const isValid = userForm.name().valid() && userForm.email().valid();Signal Forms provide full type safety for field access, schema-based validation, and automatic syncing between the form model and template. They combine the simplicity of template-driven forms with the power of reactive forms. Nested forms and repeating groups become straightforward to create.
For React developers who’ve used libraries like React Hook Form or Formik, Signal Forms offer similar ergonomics but with deeper framework integration. For Vue developers familiar with VeeValidate, the signal-based reactivity will feel natural.
9. Vitest as the default test runner — testing that doesn’t slow you down
Angular’s testing story used to be one of its biggest pain points. Karma was slow, configuration-heavy, and felt like a relic from a different era. Angular 21 ships with Vitest as the default test runner, and the improvement is substantial.
Vitest runs tests in parallel, provides near-instant feedback with watch mode, and shares the same Vite-based pipeline as the dev server. The API is compatible with Jest patterns that most developers already know:
import { describe, it, expect } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { UserCardComponent } from './user-card.component';
describe('UserCardComponent', () => {
it('should display the user name', async () => {
const fixture = TestBed.createComponent(UserCardComponent);
fixture.componentRef.setInput('user', {
id: 1,
name: 'Tomas',
});
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Tomas');
});
});If you’ve used Vitest with React or Vue, the experience is now consistent across frameworks. No more context-switching between different test runners and configurations.
10. Angular Aria and the headless component philosophy
Angular 21 introduces Angular Aria in developer preview — a set of headless, accessible components that handle all the complex ARIA patterns, keyboard navigation, and focus management while leaving the visual design entirely up to you.
This is a significant philosophical shift. Instead of shipping opinionated Material Design components that you then fight to customize, Angular now provides the accessibility and behavior layer as a foundation. You bring your own styles, your own design system, your own CSS framework.
If you’re familiar with Radix UI or Headless UI in the React ecosystem, or Headless UI for Vue, this is Angular’s answer to that pattern. Given that the Angular team built Angular Material (which remains one of the most complete component libraries in any framework), they bring deep expertise in accessibility to this new approach.
Honorable mentions
A few more things that impressed me but didn’t make the top 10:
- Resource API — a signal-based primitive for async data fetching (
httpResource) that handles loading states, errors, and caching - Angular MCP Server — AI-powered development tools that let LLMs scaffold and modify Angular code using the Model Context Protocol
- Automated migrations — Angular CLI schematics that handle major version upgrades, standalone conversion, inject migration, and more with a single command
- Angular DevTools with deep Chrome DevTools integration for debugging signals, change detection, and component trees
The bigger picture
What impressed me most wasn’t any single feature — it was the coherence of the vision. Angular didn’t just bolt on trendy features. The team systematically identified the framework’s weaknesses (modules, zone.js, forms complexity, build times, testing) and addressed them with a clear migration path from old to new.
Signals feed into zoneless change detection. Standalone components eliminate modules. @defer blocks enable incremental hydration. esbuild + Vite fix the build story. Signal Forms modernize the data entry experience. Each improvement reinforces the others.
For React developers, Angular now offers something your framework doesn’t: a batteries-included, deeply integrated platform where routing, forms, HTTP, testing, SSR, and state management all work together out of the box — without the “choose your own adventure” ecosystem fatigue.
For Vue developers, Angular’s new developer experience is closer to Vue’s simplicity than you might expect, while offering stronger typing, a more powerful DI system, and enterprise-grade tooling.
Conclusion
I went into this project expecting to tolerate Angular. I came out genuinely impressed. The Angular team has pulled off something remarkable — they modernized a massive, opinionated framework without breaking the ecosystem, while making it feel fresh and competitive with anything React or Vue offers today.
If you haven’t looked at Angular in a few years, you owe it to yourself to spend an afternoon with a fresh ng new project. The framework that greets you is not the one you remember.
Angular surprised me. It might surprise you too.