Notes on... Domain-Driven Design for Angular and Frontend Developers
How DDD and the Angular Styleguide help teams organize frontend code around business concepts, keep components focused, and make domain logic explicit.
Domain-Driven Design (DDD) is an approach to software design that centers the business domain in how software is structured and named. It relies on close collaboration between technical and business experts, using a shared language to express business concepts, rules, and workflows clearly in code. The goal is to build software that reflects the real domain and stays easier to understand, evolve, and maintain.
In practice, that means using domain terms consistently, keeping business logic explicit, and drawing clear boundaries between different parts of the system.
That matters a lot in Angular applications, where business rules often show up in forms, validation, permissions, workflows, state, user actions, and conditional UI. If the frontend does not understand the domain, the application quickly becomes harder to maintain.

What Is Domain-Driven Design?
DDD is an approach where the structure of your application is shaped by the domain: the real-world problem you are solving.
Instead of organizing code around technical categories like:
- components
- services
- utils
- models
you organize it around business concepts like:
- orders
- payments
- users
- invoices
- cart
This shift sounds simple, but it fundamentally changes how scalable and understandable your application becomes.
It also fits well with the Angular style guide, which recommends organizing projects by feature areas, not by technical file types.
Why Frontend Developers Should Care
Frontend codebases often degrade into:
- giant component folders
- unclear state management
- duplicated business logic
- inconsistent naming
- complex templates
DDD helps fix this by:
- aligning UI with business concepts
- making state easier to reason about
- reducing cognitive load
- improving collaboration with backend and product teams
- making business rules easier to test
For Angular developers, DDD supports many existing Angular best practices: feature-based structure, focused components, simple templates, clear naming, and small files with one main concept.
Angular’s style guide already nudges teams in a very DDD-friendly direction: organize by feature areas, group closely related files together, keep one main concept per file, keep components focused on presentation, and move overly complex template logic back into TypeScript.
Core Concepts, Simplified
You do not need the full complexity of DDD to benefit from it. These are the ideas that matter most for Angular and frontend work.
In plain English, DDD terminology is mostly about naming a few useful ideas: the shared language your team uses, the boundaries between parts of the product, and the types that represent important business concepts.
1. Ubiquitous Language
Ubiquitous language simply means “the shared vocabulary of the product.” Martin Fowler has a good short explanation of ubiquitous language.
Everyone should use the same terms: developers, designers, product owners, backend engineers, and stakeholders.
If the business says Cart, do not call it BasketState in the frontend.
Bad:
getUserItems();
Good:
getCartItems();
Consistency beats cleverness.
This also matches Angular’s naming philosophy: file names and identifiers should describe what the code actually represents.
For example:
cart-summary.ts
cart-summary.html
cart-summary.css
is clearer than:
summary.component.ts
summary.component.html
summary.component.css
The first version tells us the domain concept. The second only tells us the technical role.
2. Bounded Contexts
DDD splits an application into logical domains called bounded contexts.
A bounded context is just a clear boundary around one part of the business, with its own terms, rules, and behavior.
In frontend terms, these are usually feature areas:
- auth
- orders
- payments
- profile
- cart
Each context should:
- own its state
- own its terminology
- own its UI flows
- own its domain rules
- avoid leaking internal logic everywhere else
In Angular, this maps nicely to feature-based folders.
Prefer:
src/
orders/
order-list/
order-list.ts
order-list.html
order-list.css
order-list.spec.ts
order-details/
order-details.ts
order-details.html
order-details.css
order.ts
order-api.ts
order-store.ts
cart/
cart-summary/
cart-summary.ts
cart-summary.html
cart-summary.css
cart.ts
cart-api.ts
cart-store.ts
payments/
payment-method/
payment-confirmation/
Avoid:
src/
components/
services/
models/
utils/
The second structure groups files by technical type. The first groups files by business meaning.
That is both more DDD-friendly and closer to the Angular style guide.
3. Entities and Value Objects
The simplest way to think about the difference is this:
- an entity is something you care about as a specific thing
- a value object is something you care about because of the value it holds
An entity has identity. Even if some of its properties change, it is still the same thing.
Examples:
- a user with a specific
id - an order with a specific order number
- an invoice that moves from
drafttopaid
A value object does not need identity. You only care about the data it represents. If two value objects have the same attributes, they should usually be treated as the same value, and they are usually a good fit for immutable modeling.
Examples:
- a price
- an address
- a date range
- a money value
In frontend terms, if an order’s status changes from pending to shipped, it is still the same order. That makes it an entity.
But if you create { amount: 20, currency: "EUR" }, you usually do not care which specific instance it is. You care that it represents twenty euros. Another object with the same amount and currency means the same thing. That makes it a value object.
For a deeper DDD breakdown of entities and value objects, Martin Fowler’s note on Evans’ classification is a useful reference.
export type Price = Readonly<{
amount: number;
currency: string;
}>;
Treating value objects as immutable fits well with modern frontend patterns, Angular signals, NgRx, and predictable state updates. If a price changes, you usually replace it with a new value instead of mutating the old one.
4. Business Logic Belongs in the Domain
A common frontend mistake is putting business logic directly into components.
Here, “the domain” just means the part of your code that expresses business rules, not UI wiring or framework details.
Bad:
if (order.total > 1000 && user.isPremium) {
finalPrice = order.total * 0.9;
}
Better:
finalPrice = calculateOrderPrice(order, user);
Or, when it makes sense:
finalPrice = order.calculatePriceFor(user);
Not every frontend app needs rich domain classes. But business rules should not be scattered across components, templates, and event handlers.
Components should mostly:
- display data
- collect user input
- trigger meaningful actions
- delegate business decisions
Domain logic can live in:
- domain functions
- state logic
- facades
- stores
- API adapters
- small domain classes
5. Keep Angular Components Focused
The Angular style guide says components and directives should stay focused on presentation.
That goes well with DDD.
A component should not know too much about how the business works. It should coordinate the UI and call domain-level code.
Example:
@Component({
selector: "app-order-details",
templateUrl: "./order-details.html",
})
export class OrderDetails {
private readonly orders = inject(Orders);
readonly order = input.required<Order>();
protected approveOrder() {
this.orders.approve(this.order().id);
}
}
Notice a few Angular-friendly details:
inject()instead of constructor injectionreadonlyfor Angular-initialized propertiesprotectedfor members used only by the template- an event handler named for what it does:
approveOrder()
The template should also stay simple:
<button (click)="approveOrder()">
Approve order
</button>
Avoid vague handlers like:
<button (click)="handleClick()">
Approve order
</button>
The template should communicate intent.
6. Avoid Complex Logic in Templates
DDD wants business rules to be explicit. Angular wants templates to stay readable.
So avoid this:
@if (order().total > 1000 && user().isPremium && !order().hasDiscount) {
<p>Premium discount available</p>
}
Prefer moving the decision into TypeScript:
protected readonly canApplyPremiumDiscount = computed(() =>
canApplyPremiumDiscount(this.order(), this.user())
);
Then the template becomes:
@if (canApplyPremiumDiscount()) {
<p>Premium discount available</p>
}
This is easier to read, test, and reuse.
7. State Is Part of the Domain
State management becomes clearer when it follows business concepts.
Instead of thinking:
- UI state
- API state
- shared state
think:
- cart state
- order state
- payment state
- user session state
With NgRx, signals, or another state approach, this means:
- state slices = domain areas
- actions or events = business events
- selectors or computed values = domain questions
For example:
orderApprovedpaymentFailedcartItemRemovedinvoiceGenerated
is clearer than:
setDataupdateStatusloadSuccesshandleSubmit
Business events tell a story.
8. Naming Files Without Overusing Suffixes
We do not need to force names like:
order.service.ts
order.model.ts
The Angular style guide favors names that describe the concept, use hyphens, and match the main TypeScript identifier.
So instead of this:
order.service.ts
order.model.ts
order-utils.ts
prefer something more domain-specific:
order.ts
order-api.ts
order-store.ts
order-price.ts
order-permissions.ts
These names describe what the file contains, not only what technical category it belongs to.
The goal is not “never use suffixes.” Sometimes order-api.ts, cart-store.ts, or payment-form.ts is perfectly clear. The goal is to avoid generic technical buckets and meaningless names.
How to Apply DDD in an Angular Project
You do not need a full rewrite. The easiest way to start is to make a few small changes in places that already feel messy.
- Rename things to match the business. If the product says
invoice,cart, orpayment method, use those same terms in files, functions, state, and UI labels. - Move business rules out of components. Take a pricing rule, permission check, or workflow condition and move it into a domain function, computed value, store, or facade.
- Clean up folder boundaries. Replace a generic technical bucket like
utils/orservices/with a clearer domain area such asorders/,cart/, orpayments/.
That is usually enough to start feeling the benefit of DDD: clearer naming, clearer ownership, and less logic scattered across the UI.
If the backend also uses DDD, mirror the same business concepts in the frontend where it makes sense. That reduces translation overhead and prevents bugs caused by mismatched terminology.
Final Thought
DDD is not about adding complexity. It is about removing accidental complexity.
For Angular developers, it is a practical way to:
- write clearer code
- scale features without chaos
- keep components clean
- make state easier to understand
- collaborate better with backend and product teams
If your Angular app is growing and starting to feel messy, DDD is worth exploring, not as just as a strict methodology, but as a mindset.
Small improvements compound quickly.