Skip to main content
Angular Architecture Smells That Hurt Teams as Apps Grow

Angular Architecture Smells That Hurt Teams as Apps Grow

An architecture smell is not the same thing as a bug.

The app can compile, tests can pass, releases can ship, and the architecture can still be quietly making the team slower every month. That is why these smells matter. They usually show up first as delivery problems: too many files touched for a small change, unclear ownership, fragile upgrades, slow reviews, or features that somehow affect unrelated parts of the app. Angular’s style guide is really about preventing exactly that kind of friction through consistency and clearer organization.

Here are the smells I would watch for before the codebase becomes painful.

1. Everything is “shared”

This is one of the oldest Angular repo problems. You get a massive shared area with components, pipes, utilities, services, and half the business logic in the app.

It happens because “shared” feels safe early on. It hurts later because nothing has a clear home, and reuse becomes an excuse for weak boundaries.

Refactoring direction: split by domain first, then keep shared code limited to real UI primitives, utilities, or platform concerns. Angular workspaces and libraries are designed for deliberate reuse, not accidental dumping grounds.

2. NgModules are still doing too much

Older Angular apps often keep piling responsibility into NgModules long after the app needs that structure. Angular now recommends standalone components, directives, and pipes as the simplified direction, and provides an incremental migration path for existing apps.

This smell shows up as:

  • hard-to-trace imports
  • oversized module files
  • unclear dependency boundaries
  • onboarding friction for new developers

Refactoring direction: migrate new work first, then move stable areas gradually toward standalone.

3. Components own too much business logic

If components are fetching data, transforming it, enforcing business rules, handling permissions, and coordinating half the page, the problem is not only size. It is responsibility.

This happens because components are the easiest place to put working code. It scales badly because UI and business decisions get tangled together.

Refactoring direction: move business logic into domain services, facades, or state layers. Keep components focused on view composition and user interaction.

4. State is synchronized manually

This smell got worse in the years before signals because teams often used subscriptions, setters, and side effects to keep values in sync.

Angular signals are designed so derived state can be expressed directly with computed, and Angular’s effect guide explicitly frames effects as side-effect tools, not as a replacement for proper state modeling.

If you see state copied from one variable to another “just to keep things updated,” that is a smell.

Refactoring direction: use computed state where possible. Reduce propagation code.

5. RxJS is being used for problems that are not really streams

RxJS is powerful, but older Angular apps often use it for everything, including simple local UI state. Angular now offers official RxJS interop with signals, including toSignal, precisely because not every piece of state should stay in Observable form forever.

This smell shows up as:

  • BehaviorSubject for simple toggles
  • operator chains for trivial derived values
  • unreadable local component state

Refactoring direction: keep RxJS for streams, async orchestration, and time-based workflows. Use simpler state models for local state.

6. Templates are doing too much work

Angular’s runtime performance guide is blunt: change detection is optimized, but apps still slow down when too much work happens during it.

You usually see this smell as:

  • heavy method calls in templates
  • repeated filtering or sorting inline
  • large list rendering without proper tracking
  • “smart templates” that hide logic

Refactoring direction: move expensive work into computed state, memoized view models, or preprocessing before render.

7. The repo is organized by file type instead of by feature or domain

A components/, services/, models/, utils/ structure at the root of the repo looks tidy for a while. Then no one knows who owns what.

Angular’s style guide recommends organizing by feature areas where possible because it helps developers find related files and reason about functionality as a unit.

Refactoring direction: align structure with business domains, feature areas, and team ownership.

8. New Angular capabilities are ignored because the repo is “not ready”

This is a subtle smell, but a serious one. If the app is stuck on older patterns by default, every framework improvement starts feeling optional or risky. Angular has been pushing clearer modern defaults around standalone, new control flow, SSR integration, and better diagnostics.

The smell is not “old code exists”. The smell is “the codebase resists modernization”.

Refactoring direction: stop treating upgrades as isolated chores. Modernize architecture in a way that reduces future migration cost.

9. Performance problems are treated as page-level bugs, not architecture issues

If the app is slow, teams often jump straight into component tweaks. But Angular’s performance guidance separates runtime performance from rendering and loading strategy for a reason.

Sometimes the real smell is architectural:

  • too much on the first route
  • weak lazy-loading boundaries
  • no rendering strategy by route
  • shared code pulling too much into critical paths

Refactoring direction: review route boundaries, defer what is not critical, and treat performance as an architectural concern, not only a component concern.

10. The codebase depends on tribal knowledge

This is usually the final smell before scaling hurts badly. The app may still work, but only a few people know why certain libraries exist, where state should live, or which dependencies are safe to touch.

That happens when architecture decisions were never turned into lightweight rules.

Refactoring direction: document ownership, dependency rules, and preferred patterns. Use Angular’s diagnostics, language service, and modern conventions to make the right path easier.

A quick self-audit

Ask these questions:

  • Is most new code going into real feature areas or into shared?
  • Can a new developer tell who owns a part of the repo?
  • Are components mostly UI, or mini-application layers?
  • Is derived state modeled directly, or synchronized manually?
  • Are simple UI problems still being solved with heavy RxJS?
  • Are templates cheap to render?
  • Is the repo easy to modernize, or structurally stuck?
  • Are performance issues mostly local?

If too many of those answers feel uncomfortable, the codebase is already telling you something.

Suggested articleAI Coding with Angular MCP: A Practical Setup for Cursor, Codex, and Windsurf AI Coding for Angular Developers AI Coding with Angular MCP: A Practical Setup for Cursor, Codex, and WindsurfA practical Angular MCP workflow for Cursor, Codex, and Windsurf, with safe setup rules, prompt patterns, and review guardrails.

About

A little more about me

I help teams build stronger Angular products through clearer architecture, better frontend execution, and more confident technical decisions. The ideas in these articles come from real work with client teams, real delivery challenges, and practical solutions that proved useful in the field.

Need help with Angular delivery?

If your team needs clearer direction, stronger frontend execution, or a second expert opinion, send me a short message and I will suggest the most useful next step.