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: Go for Signals
Signals represent one of the most significant shifts in Angular's approach to reactivity. While some pieces like Signal Forms and httpResource
are still in development, and effect
remains in developer preview, my recommendation is clear: go all in with Signals.
Why shouldn’t you wait for Signal Forms and httpResource
? The answer lies in the cost of rewrites. If you skip Signals now, you’ll likely default to RxJS for reactivity. But transitioning from RxJS to Signals later will require a much more extensive rewrite. Your have to realize that with RxJS, the paradigms are different, a lot of things are different.
On the other hand, by adopting Signals, you can ensure that your application is already aligned with Angular's future. For areas not yet "signalized" (e.g., Reactive Forms or HTTP communication), you can step out of the Signal reactivity and handle them the traditional way for now. For instance, you could use an effect
to set the value of a FormGroup
.
The key point is that starting with Signals means you only need to adjust the non-signalized parts of your code in the future. If you skip Signals now, you’ll face the daunting task of refactoring the entire application later.
That said, this doesn’t mean you should completely replace RxJS with Signals. My advice: Start with Signals, and if RxJS doesn’t offer any significant benefits, stick with Signals. Otherwise, go with RxJS.
For new applications, I’d also recommend adopting resource
or rxResource
from the start. While these APIs are currently experimental and might evolve, by the time your application goes live (which is often well beyond 2-3 months of initial development), they are likely to be stable.
Go learn more about Signals, you can check out my video
Angular CLI Over Nx
I prefer the Angular CLI because it provides everything I need to get started without adding unnecessary layers of complexity. While Nx offers pre-configured tooling, I find that manually setting up what I need with the Angular CLI is straightforward and works seamlessly with Schematics or pre-configured setup tools from that tool.
Starting with Nx introduces an additional layer of abstraction, which can feel overwhelming, especially in the early stages of a project. It also comes with its own learning curve and migration processes, which usually requires extra effort. In my experience, beginning with the Angular CLI and transitioning to Nx later, if needed, is the more practical approach.
That said, Nx offers value when your application grows. Its features, like advanced build tooling and mono repository management, become indispensable for large-scale projects. And if the time comes to adopt Nx, the process is straightforward — just run nx import
.
For library development, however, I would choose Nx from the start.
Angular Material + Tailwind
When it comes to UI development, I use Angular Material together with Tailwind.
Angular Material provides a robust library of components that integrate seamlessly with Angular. Since it is part of the Angular project (which consists of Material, the CLI, and the framework), it’s officially maintained and always kept in sync.
For me, Tailwind CSS is not a replacement for a UI library but the perfect complement to Angular Material. While Material handles most of my UI needs, Tailwind is used for fine-grained customizations, like positioning elements, tweaking margins, and adding utility classes.
I rarely encounter unique UI needs or work with an existing design system. Most of the time, I develop what I like to call "UI-boring" enterprise applications, where the combination of Angular Material and Tailwind is more than sufficient.
Testing: Jasmine/Karma and Playwright
To be fair, choosing the right testing tools is not easy at the moment. Since I go with the Angular CLI, I’ll stick to the only officially supported testing framework: the combination of Jasmine and Karma.
You might have heard that this pair has reached its end-of-life. That's not true. End-of-life applies only to Karma, which handles running the tests in the browser. Karma will be replaced by the Modern Web Test Runner in the future, but we don’t have to care about that. The commands we use in our tests are Jasmine, and that’s not going to be replaced. It will just stay.
What about Jest or Vitest? There are plans to support Jest officially. I hope it’s going to be Vitest, which is the modern tool, but I’m afraid the Angular team leans toward Jest.
Additionally, I use the Angular Testing Library, which runs on top of Jasmine. It enhances the testing experience by
- providing utility functions to select elements via user-facing selectors, such as roles or labels, instead of relying on implementation details like class names or
data-testid
. - It simplifies the configuration of the
TestingModule
via arender
function. - It handles asynchronous tasks and triggers the change detection automatically
Since I don't write a test per file (aka. Unit Test), but for features a as a whole, I disable the automatic generation of a test in the CLI.
There is also a video about this "don't write tests for each file"
For end-to-end (E2E) testing, it’s going to be Playwright. If I don’t have existing code, I don’t see any reason to use something else. Playwright is fast, modern, and reliable.
Unfortunately, there’s no plan to support component testing in Angular with Playwright, but I can tell you that some work is going on in the background. It might come, maybe in the form of a community-driven effort.
And guess what, there I have also a video about Playwright 😅
Code Guidelines: ESLint, Prettier, Husky, Lint-Staged & Sheriff
Sticking to clear code guidelines is essential for a clean and consistent codebase.
For linting, ESLint via angular-eslint remains the standard. It provides strong integration with Angular, meaning it also comes with the Angular Styles Guidelines. I also include the eslint-plugin-unused-imports library, which automatically removes unused imports — just as the name suggests.
While there’s an ESLint plugin for Prettier, I avoid using it. I don’t want formatting errors to show up as linting errors, turning everything red in the editor. Instead, I use lint-staged to run Prettier before every commit. To ensure the git hooks execute properly, I rely on Husky.
When it comes to enforcing architectural rules — particularly module boundaries and dependency rules between them — I use Sheriff. It also integrates with ESLint.
angular-eslint / angular-eslint
✨ Monorepo for all the tooling related to using ESLint with Angular
Angular ESLint
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
- 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-disable
comments 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 a new Angular CLI…
lint-staged / lint-staged
🚫💩 — Run linters on git staged files
🚫💩 lint-staged
Run 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...
Why
Linting makes more sense when run before committing your code. By doing so you can ensure no errors go into the repository and enforce code style. But running a lint process on a whole project is slow, and linting results can be irrelevant. Ultimately you only want to lint files that will be committed.
This…
State Management: SignalStore
Angular’s introduction of Signals has greatly simplified state management, which is why I also choose a state management library that is built on top of Signals. For me, that’s the NgRx SignalStore.
I use SignalStore for both local and global state management. When do I decide to bring it in? The moment I find myself writing a service that holds a larger Signal and includes logic around it,
The SignalStore is not boilerplaty and it provides a clear standard.
As for the NgRx Global Store with Redux, I see it as a tool where an eventing-based system is necessary. To be honest, those scenarios are extremely rare for me. Even if such a need arises, I would wait for the upcoming eventing extension for the SignalStore.
If you haven't had the chance to try out the SignalStore, here's a jumpstart
inject
over constructor
A heavily debated topic is inject vs constructor-based dependency injection. In my Java/Spring applications, I go with constructor-based DI, but in Angular, I choose inject.
inject
also works within functions, which is a big advantage. My HTTP interceptors and router guards already depend on it, and who knows — there may be an alternative authoring format in the future that doesn’t require classes.
There are also other reasons for using inject
, like type-safe injection tokens or the fact that it eliminates the need for property decorators, which aren’t standardized. But for me personally, the key argument is its support for use in functions.
Schematics
I try to keep my components small. As soon as a component grows larger, I split it into a container component and multiple presentational components. Logic goes into services.
Since my components therefore have around 100 lines of code, I don’t see the need for separate CSS and HTML files. Instead, I prefer inline styles and templates. To streamline this approach, I overwrite the schematics property 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 fully honest, I don’t use the Angular CLI anymore to create components or services. Since I prefer a single-file setup with inline templates and styles, I find it faster to simply create the file manually and use a live template/snippet that I’ve defined in my IDE.
For me, this approach is much quicker than running CLI commands and adjusting the generated files.
Zoneless & OnPush
As the name suggests, zone.js is no longer part of the game with zoneless mode. Instead, the function markForCheck
— which has always been important for components using the OnPush
strategy — now triggers change detection directly.
I go with zoneless mode. While it’s still experimental, some applications, like https://angular.dev, already use it in production. The argument here is the same as for rxResource
: If you start a new application today, chances are high that you’ll go into production with either a developer preview or even a stable version of zoneless.
I also set my components to OnPush
. OnPush
shouldn’t cause any issues with zoneless mode. It’s necessary for an additional boost, called local change detection. You only get local change detection if the component is OnPush
and a Signal in the template changes. With local change detection, only the component is checked, not its parents, which makes the process much faster.
For more information on change detection, I would recommend my video
SSR, Hydration, and Incremental Hydration
Server-Side Rendering (SSR), Hydration, and Incremental Hydration are essential for any internet-facing application with high demands on initial loading performance and good SEO.
Before I joined Angular Architects, I worked on an application where these features would have been a game-changer. If we had access to Incremental Hydration at the time, it would have been paradise for us.
That said, these capabilities come at a cost. They add additional complexity to the development and deployment processes.
Because of this, I recommend using SSR only if your application truly needs it, but then absolutely.
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 use pnpm over npm and yarn. For me it’s faster, and handles dependencies better. Yarn is also good, but I don't want to use version 1.
AI? Absolutely. It has become a necessity. I use CoPilot and ChatGPT as AI tools to boost productivity. As for my IDE, I stick with IntelliJ, a choice rooted in my Java background... and I still use the light theme 😅.
Outlook for 2025
Personally, I’m eager to try out Bun as an alternative to Node.js.
I also want to use Angular (TanStack) Query, which is gaining traction as a powerful tool for handling server state and caching. Also in combination with the SignalStore.
And Analog, I need that for the ng-news website, which I wanted to do for some time now.
I hear a lot of good things about Cursor, the AI IDE. Want to give that one a try as well.
And for Angular, I'm looking forward to the new features, such as httpResource
and Signal Forms, which promise to bring reactivity even closer to core Angular patterns.
That's it. If you have an other selection or know about some tools which I don't, then please let me know.
I am also providing a GitHub repo which has all these tools installed and configured.
rainerhahnekamp / angular-starter
A starter project for a modern Angular application
Angular Project Setup
Installation
pnpm create @angular@latest -s -t -S --experimental-zoneless --ssr false --style scss [yourProjectName]
Manually add the following props to the angular.json
The path is projects.[yourProjectName].schematics.@schematics/angular:component
- "changeDetection": "OnPush"
- "flat": true
UI
pnpm ng add @angular/material
pnpm install -D tailwindcss postcss autoprefixer
pnpm tailwindcss init
Open tailwind.config.js
and add the following code to module.exports
:
module.exports = {
// ...
content: ["./src/**/*.{html,ts}"],
};
At the beginning of styles.scss
, add the following code:
Testing
Install the Angular Testing Library along the user-event library:
pnpm i -D @testing-library/angular @testing-library/dom @testing-library/user-event
Next, install Playwright and install the accompanying browsers:
pnpm i -D @playwright/test
pnpm playwright install
Code Quality
For code quality, we install angular-eslint
, eslint-plugin-unused-imports
, husky
, prettier
,lint-staged
and Sheriff.
pnpm ng add @angular-eslint/schematics
pnpm i -D eslint-plugin-unused-imports husky prettier lint-staged @softarc/{sheriff-core,eslint-plugin-sheriff}
To integrate…
And as always, feedback/comments are very welcomed.
Top comments (3)
Do you have any material about SSR with Angular? It could even be SSG, but I find it difficult to find anything that shows the server rendering the Angular application.
I would only disagree with the light theme in IDE :D
I am not able to style tye material components using tailwindcss. Does it require any additional setup?