Angular continues to evolve rapidly. What should we use in 2025? Especially if we start with a new project.
Inspired by a Reddit discussion, this guide shares my personal take (=opinionated) on the most effective tools, libraries, and practices for Angular development in 2025.
The video version of this article is available on YouTube.
Reactivity: Embrace Signals
The introduction of Signals marks a major milestone in how Angular handles state and change detection. Although certain pieces—such as Signal Forms and httpResource—are still maturing, and the effect function hasn't reached a stable release yet, the direction is unmistakable: commit fully to Signals.
Why hold off on adopting Signals until the remaining pieces are ready? The true cost lies in the migration effort. If you postpone Signals, you'll likely turn to RxJS as the default mechanism for reactivity. Moving from RxJS to Signals later is a substantial undertaking, since the two paradigms differ significantly in how they model data flow and side effects.
By moving to Signals now, though, you position your application in line with Angular's long-term vision. For features that aren't yet "signalized" — think Reactive Forms or HTTP interactions — you can temporarily step outside the Signal world and use the classic approach. A typical workaround is leveraging an effect to push values into a FormGroup.
The takeaway: if you start with Signals, future adjustments are limited to those unsignalized parts. Procrastinating means facing a complete rewrite of the codebase further down the road.
That doesn't mean RxJS is obsolete. My guidance: begin with Signals; resort to RxJS when it provides tangible advantages. Otherwise, stay the course with Signals.
For brand-new projects, I'd also advise incorporating resource or rxResource right away. These APIs are experimental and may shift, but by the time your product hits production—often after many months of development—they're likely to be solid.
For a deeper dive into Signals, my video may be a useful resource.
Angular CLI Over Nx
The Angular CLI is my pick because it gives me all the essentials out of the box without overcomplicating things. Nx does come with preconfigured tooling, but I find that manual setup through the Angular CLI is pretty simple—and it works smoothly with Schematics or the preconfigured scaffolding tools that come with the tool itself.
Choosing Nx from the start adds an extra layer of complexity, which can be overwhelming in a project's early days. Plus, it brings its own learning curve and migration overhead, which often demands extra attention. From what I've seen, kicking off with the Angular CLI and adopting Nx later—if the need emerges—is the more pragmatic route.
That being said, Nx shines as your application scales. Its capabilities, including sophisticated build optimizations and effective monorepo management, become crucial for complex codebases. And if you do decide to bring in Nx, the shift is uncomplicated—just execute nx import.
For library development, though, I'd pick Nx from the get-go.
Angular Material + Tailwind
For UI work, I combine Angular Material with Tailwind CSS.
Angular Material offers a solid set of components that fit naturally with Angular. Being a core part of the Angular project—alongside the CLI and the framework—guarantees it's officially supported and consistently updated.
I view Tailwind not as a substitute for a component library, but as an ideal partner to Angular Material. While Material covers the bulk of UI needs, Tailwind handles the finer touches—element positioning, margin tweaks, and utility styling.
I seldom face distinctive UI demands or need to integrate a bespoke design system. Most of my work involves what I'd call "UI-ordinary" enterprise applications, where the Material plus Tailwind combo more than does the job.
Testing: Jasmine/Karma and Playwright
Admittedly, selecting the appropriate testing tools is tricky these days. Since I align with the Angular CLI, I'll go with its only officially supported testing pair: Jasmine and Karma.
You may have heard rumors that this combo is done. Not quite—only Karma, the test runner that launches tests in the browser, is at its end of life. It's slated to be superseded by the Modern Web Test Runner, but that shouldn't worry us. The commands our tests use—Jasmine—aren't going anywhere. It stays put.
What about Jest or Vitest? Official support for Jest is in the pipeline. I'd personally root for Vitest, given its modern approach, but I suspect the Angular team leans toward Jest.
Additionally, I rely on the Angular Testing Library, which builds upon Jasmine. It improves the testing workflow by
- exposing helpers to locate elements using user-centric selectors like roles or labels, rather than mocking around with implementation particulars such as class names or
data-testid. - Simplifying the
TestingModulesetup via arenderfunction. - Handling async operations and running change detection automatically.
Given that I don't create a test per component (that is, traditional unit tests) but instead test features as cohesive units, I turn off the CLI's automatic test file generation.
I've also put together a video on why testing each file individually isn't necessary.
For end-to-end testing, Playwright is my choice. Without existing legacy code to accommodate, there's no reason to look elsewhere. Playwright brings speed, modernity, and reliability to the table.
True, there's no official plan for Angular component testing with Playwright at this stage, but I'm aware there's activity behind the scenes—possibly culminating in a community-driven initiative.
And, naturally, I've created a video on Playwright as well 😅
Code Guidelines: ESLint, Prettier, Husky, Lint-Staged & Sheriff
Enforcing clear coding conventions is key to maintaining a clean, uniform codebase.
For linting, ESLint via angular-eslint is the go-to. It offers robust Angular integration, including the Angular Styles Guidelines. I also bring in the eslint-plugin-unused-imports library, which, as the name implies, strips out unused imports automatically.
I avoid the ESLint integration for Prettier, even though a plugin exists. Formatting discrepancies shouldn't surface as lint errors, making everything glow red in the editor. Instead, I rely on lint-staged to invoke Prettier ahead of each commit. To see that the git hooks fire properly, Husky steps in.
For architectural enforcement—specifically, defining module boundaries and dependency rules—Sheriff does the heavy lifting. It also plugs neatly into ESLint.
angular-eslint
/
angular-eslint
✨ Monorepo for all the tooling related to using ESLint with Angular
Monorepo for all the tooling which enables ESLint to lint Angular projects
This project is made possible thanks to the continued hard work going into https://github.com/typescript-eslint/typescript-eslint, and brilliant work on the original TSLint rule implementations in https://github.com/mgechev/codelyzer.
Contents
- Quick Start
- Supported Angular CLI Versions
- Supported ESLint Versions
- Packages included in this project
- Package Versions
- Adding ESLint configuration to an existing Angular CLI project which has no existing linter
- Using ESLint by default when generating new Projects within your Workspace
- Configuring ESLint
- Writing Custom ESLint Rules
- Philosophy on lint rules which enforce code formatting concerns
- Linting with the VSCode extension for ESLint
- Usage without Angular CLI Builder and eslintrc style configs
- Notes on performance
- Using
eslint-disablecomments in Angular templates - Migrating an Angular CLI project from Codelyzer and TSLint
Quick Start
-
Follow the local environment and workspace setup guide in order to install the Angular CLI
-
Create…
lint-staged
/
lint-staged
🚫💩 — Run tasks like formatters and linters against staged git files
Run tasks like formatters and linters against staged git files and don't let 💩 slip into your code base!
npm install --save-dev lint-staged # requires further setup
$ git commit
✔ Backed up original state in git stash (5bda95f)
❯ Running tasks for staged files...
❯ packages/frontend/.lintstagedrc.json — 1 file
↓ *.js — no files [SKIPPED]
❯ *.{json,md} — 1 file
⠹ prettier --write
↓ packages/backend/.lintstagedrc.json — 2 files
❯ *.js — 2 files
⠼ eslint --fix
↓ *.{json,md} — no files [SKIPPED]
◼ Applying modifications from tasks...
◼ Cleaning up temporary files...
Table of Contents
- Why
- Installation and setup
- Changelog
- Command line flags
- Configuration
- Filtering files
- What commands are supported?
- Running multiple commands in a sequence
- Using JS configuration files
- Reformatting the code
- Examples
- Frequently Asked Questions
Why
Code quality tasks like formatters and linters make more sense when run before committing your code…
State Management: SignalStore
The arrival of Signals in Angular has made state management considerably simpler, which is why I gravitate toward a library that builds directly on top of that foundation. For me, that library is the NgRx SignalStore.
I reach for SignalStore both for local and global state. When exactly do I pull it in? As soon as I notice myself writing a service that holds a fairly large Signal and wraps logic around it.
SignalStore cuts down on boilerplate while still giving you a clear, consistent pattern to follow.
As for the classic NgRx Global Store built on Redux, I view it as something you'd bring in only when an eventing-based architecture is truly warranted. Honestly, those situations come up very rarely for me. And even if one did, I'd hold out for the eventing extension that's planned for SignalStore.
If you haven't experimented with SignalStore yet, here's a quick way to get started.
inject over constructor
One topic that always stirs debate is whether to use inject or constructor-based dependency injection. In my Java and Spring projects, I stick with constructor-based DI, but in Angular, I pick inject.
inject has the added benefit of working inside functions, which is a major win. My HTTP interceptors and router guards already depend on it, and there's also the possibility that a future authoring format won't need classes at all.
There are other reasons to choose inject as well — type-safe injection tokens come to mind, and it removes the need for property decorators, which aren't part of any standard. But for me, the decisive factor is simply that it can be used within functions.
Schematics
I aim to keep my components small. When a component starts to grow, I break it into a container component plus a set of presentational components. The business logic goes into services instead.
Since my components hover around 100 lines of code, I don't feel the need for separate CSS and HTML files. Instead, I go with inline styles and templates. To support this workflow, I override the schematics setting in the angular.json file.
"schematics": {
"@schematics/angular:component": {
"inlineTemplate": true,
"inlineStyle": true,
"style": "scss",
"skipTests": true,
"flat": true,
"changeDetection": "OnPush"
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:resolver": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
},
}
To be completely honest, I no longer use the Angular CLI to generate components or services. Given that I prefer a single-file structure with inline templates and styles, I find it quicker to create the file by hand using a live template or snippet I've set up in my IDE.
For me, that approach is far faster than running CLI commands and then modifying the generated output.
Zoneless & OnPush
As the name suggests, zoneless mode removes zone.js from the equation entirely. Instead, the markForCheck function — which has long been essential for components using the OnPush strategy — now triggers change detection directly.
I go with zoneless mode. While it's still marked experimental, some production applications, such as https://angular.dev, are already running on it. The reasoning here echoes what I said about rxResource: if you're launching a new app today, there's a good chance you'll end up in production with either a developer preview or even a stable release of zoneless.
I also set all my components to OnPush. OnPush pairs well with zoneless mode and shouldn't cause any conflicts. In fact, it's a prerequisite for an extra performance gain known as local change detection. Local change detection only kicks in when a component is OnPush and a Signal in its template updates. When that happens, only the component itself gets checked — not its parent components — making the whole process considerably faster.
If you'd like to dig deeper into change detection, I recommend checking out my video.
SSR, Hydration, and Incremental Hydration
Server-Side Rendering (SSR), Hydration, and Incremental Hydration play a vital role for any internet-facing application that cares about fast initial load times and strong SEO.
Before I joined Angular Architects, I worked on an application where these features would have made a huge difference. Had Incremental Hydration been available at the time, it would have felt like a gift.
That said, these capabilities come with trade-offs. They introduce extra complexity into both the development and the deployment pipeline.
For that reason, I'd recommend adopting SSR only when your application genuinely requires it — but if it does, go all in.
You might want to recheck this part. It could be that a video shows up here very soon...
Final Notes: Tools and Preferences
When it comes to package managers, I reach for pnpm over npm and yarn. For me, it's faster and handles dependencies more cleanly. Yarn is a fine option too, but I'd steer clear of version 1.
AI? Absolutely. It's become essential. I rely on CoPilot and ChatGPT as AI assistants to improve my productivity. As for my IDE, I stay with IntelliJ — a habit that comes from my Java background... and I still use the light theme 😅.
Outlook for 2025
Personally, I'm keen to experiment with Bun as a Node.js alternative.
I'd also like to work with Angular (TanStack) Query, which is picking up momentum as a solid tool for managing server state and caching. It also pairs nicely with SignalStore.
And Analog — I need that for the ng-news website, something I've been meaning to build for a while now.
I keep hearing good things about Cursor, the AI-powered IDE. I want to try that one out as well.
And for Angular itself, I'm excited about upcoming features like httpResource and Signal Forms, which look set to push reactivity even closer to Angular's core patterns.
That wraps it up. If you have a different collection of tools or know about ones I haven't mentioned, feel free to let me know.
I've also put together a GitHub repository that has all of these tools installed and ready to go.
rainerhahnekamp
/
angular-starter
A starter project for a modern Angular application
Setup Steps
Run the following command to scaffold a new project:pnpm create @angular@latest -s -t -S --experimental-zoneless --ssr false --style scss [yourProjectName]
After the project is created, open angular.json and locate the path projects.[yourProjectName].schematics.@schematics/angular:component. Add these two properties there manually:
"changeDetection": "OnPush""flat": true
Styling and Components
Install Angular Material and Tailwind CSS along with its dependencies:pnpm ng add @angular/material pnpm install -D tailwindcss postcss autoprefixer pnpm tailwindcss initNow, edit
tailwind.config.js. Inside module.exports, add the content array so Tailwind knows where to look for class names:
module.exports = { // ... content: ["./src/**/*.{html,ts}"], };Then, prepend the Tailwind directives to the top of
styles.scss:
@tailwind base; @tailwind components; @tailwind utilities;
Test Tooling
Start by adding the Angular Testing Library packages and the user-event library as development dependencies:pnpm i -D @testing-library/angular @testing-library/dom @testing-library/user-eventNext, set up Playwright for end-to-end tests and download the browsers it needs:
pnpm i -D @playwright/test pnpm playwright install---
Linting and Formatting
For code quality tooling, we bring inangular-eslint, eslint-plugin-unused-imports, husky, prettier, lint-staged, and the Sheriff libraries for enforcing modular boundaries.
pnpm ng add @angular-eslint/schematics
pnpm i -D eslint-plugin-unused-imports husky prettier lint-staged @softarc/{sheriff-core,eslint-plugin-sheriff}
The integration continues…
testing-library.com