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 CLI • Overview • Angular

The web development framework for building modern apps.

favicon angular.dev

Nx: Smart Monorepos · Fast Builds

Get to green PRs in half the time. Nx optimizes your builds, scales your CI, and fixes failed PRs. Built for developers and AI agents.

favicon nx.dev

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.

Angular Material UI Component Library

UI component infrastructure and Material Design components for Angular web applications.

favicon material.angular.dev

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 TestingModule setup via a render function.
  • Handling async operations and running change detection automatically.

Angular Testing Library | Testing Library

Angular Testing Library

favicon testing-library.com

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 😅

Fast and reliable end-to-end testing for modern web apps | Playwright

Cross-browser end-to-end testing for modern web apps

favicon playwright.dev

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.


Prettier · Opinionated Code Formatter · Prettier

Opinionated Code Formatter

favicon prettier.io

GitHub logo lint-staged / lint-staged

🚫💩 — Run tasks like formatters and linters against staged git files

npm version


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
Enter fullscreen mode Exit fullscreen mode
$ 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...
See asciinema video

asciicast

Table of Contents

Why

Code quality tasks like formatters and linters make more sense when run before committing your code…

Sheriff | Sheriff

Modularity for TypeScript Projects

favicon sheriff.softarc.io

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.

NgRx Toolkit | NgRx Toolkit

Extensions for the NgRx SignalStore

favicon ngrx-toolkit.angulararchitects.io

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.

Fast, disk space efficient package manager | pnpm

Fast, disk space efficient package manager

favicon pnpm.io

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 😅.

Microsoft Copilot: Your AI companion

Microsoft Copilot is your companion to inform, entertain and inspire. Get advice, feedback and straightforward answers. Try Copilot now.

favicon copilot.microsoft.com

https://openai.com/

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.

Analog | Analog

The fullstack Angular meta-framework

favicon analogjs.org

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.

GitHub logo 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 init
Now, 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-event
Next, 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 in angular-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…