DEV Community

Cover image for My favorite Angular Setup in 2025
Rainer Hahnekamp
Rainer Hahnekamp

Posted on

My favorite Angular Setup in 2025

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

The web development framework for building modern apps.

favicon angular.dev

Nx: Smart Monorepos · Fast CI

Nx is a build system, optimized for monorepos, with plugins for popular frameworks and tools and advanced CI capabilities including caching and distribution.

favicon nx.dev

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.

Angular Material UI component library

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

favicon material.angular.io

Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.

Tailwind CSS is a utility-first CSS framework for rapidly building modern websites without ever leaving your HTML.

favicon tailwindcss.com

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 a render function.
  • It handles asynchronous tasks and triggers the change detection automatically

Angular Testing Library | Testing Library

Angular Testing Library

favicon testing-library.com

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 😅

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

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.


Prettier · Opinionated Code Formatter

Opinionated Code Formatter

favicon prettier.io

GitHub logo lint-staged / lint-staged

🚫💩 — Run linters on git staged files

🚫💩 lint-staged

npm version


Run 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

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…

Sheriff | Sheriff

Modularity for TypeScript Projects

favicon sheriff.softarc.io

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

NgRx Toolkit | NgRx Toolkit

Extensions for the NgRx SignalStore

favicon ngrx-toolkit.angulararchitects.io

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
  },
}
Enter fullscreen mode Exit fullscreen mode

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.

Fast, disk space efficient package manager | pnpm

Fast, disk space efficient package manager

favicon pnpm.io

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

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

Analog | Analog

The fullstack Angular meta-framework

favicon analogjs.org

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.

GitHub logo 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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Open tailwind.config.js and add the following code to module.exports:

module.exports = {
  // ...
  content: ["./src/**/*.{html,ts}"],
};
Enter fullscreen mode Exit fullscreen mode

At the beginning of styles.scss, add the following code:

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Testing

Install the Angular Testing Library along the user-event library:

pnpm i -D @testing-library/angular @testing-library/dom @testing-library/user-event
Enter fullscreen mode Exit fullscreen mode

Next, install Playwright and install the accompanying browsers:

pnpm i -D @playwright/test
pnpm playwright install
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

To integrate…


And as always, feedback/comments are very welcomed.

Top comments (3)

Collapse
 
ronaldohoch profile image
Ronaldo Hoch

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.

Collapse
 
sergio_che_a3c666fb730e54 profile image
Sergio Che

I would only disagree with the light theme in IDE :D

Collapse
 
sunny_kumar_6e6bbec0b77f0 profile image
Sunny Kumar

I am not able to style tye material components using tailwindcss. Does it require any additional setup?