Angular 22 Architecture & Composition
Prefer small, explicit feature slices over large shared abstractions. Keep components focused on orchestration and move domain rules into services or pure functions.
Core Rules
- Use standalone components, directives, and pipes by default.
- Prefer
inject() over constructor injection when the dependency is used directly in the class body.
- Keep state ownership local to the nearest feature boundary; lift state only when a second consumer actually needs it.
- Split large screens into container and presentational components before extracting shared utilities.
- Avoid
NgModule-based organization for new code unless a third-party integration requires it.
- Treat route boundaries, feature folders, and service scopes as deliberate design tools.
Good Structure
import { Component, inject } from '@angular/core';
interface UserSummary {
id: string;
name: string;
tier: 'free' | 'pro';
}
class UserSummaryService {
getSummary(userId: string): UserSummary {
return { id: userId, name: 'Ada Lovelace', tier: 'pro' };
}
}
@Component({
standalone: true,
selector: 'app-user-summary-card',
template: `
<article>
<h2>{{ user.name }}</h2>
<p>Plan: {{ user.tier }}</p>
</article>
`,
providers: [UserSummaryService],
})
export class UserSummaryCardComponent {
private readonly service = inject(UserSummaryService);
user = this.service.getSummary('user-1');
}
Feature Boundary Pattern
import { Injectable, signal } from '@angular/core';
@Injectable()
export class CartFacade {
private readonly _items = signal<string[]>([]);
readonly items = this._items.asReadonly();
add(item: string): void {
this._items.update((items) => [...items, item]);
}
}
Architectural Guardrails
- Use services for I/O, persistence, and cross-component state coordination.
- Use pure helper functions for deterministic data shaping.
- Keep templates declarative and keep business rules out of them.
- Co-locate feature-specific routes, tests, and assets with the feature when possible.
- Prefer explicit provider scopes over global singleton behavior when a feature owns the dependency.
Anti-Patterns
- Do not create shared abstractions before you have two real consumers.
- Do not put view logic, API calls, and state mutation into one component.
- Do not use a global service when a feature-scoped facade is enough.
Review Checklist
- The feature can be understood from its folder and route boundary.
- Components are thin and declarative.
- Services own rules, data access, or coordination.
- Shared code is actually shared.
1---2name: ng22-architecture-composition3description: Guides Angular 22 architecture toward standalone composition, narrow services, and feature boundaries.4---5
6# Angular 22 Architecture & Composition
7
8Prefer small, explicit feature slices over large shared abstractions. Keep components focused on orchestration and move domain rules into services or pure functions.
9
10## Core Rules
11
121. Use standalone components, directives, and pipes by default.
132. Prefer `inject()` over constructor injection when the dependency is used directly in the class body.
143. Keep state ownership local to the nearest feature boundary; lift state only when a second consumer actually needs it.
154. Split large screens into container and presentational components before extracting shared utilities.
165. Avoid `NgModule`-based organization for new code unless a third-party integration requires it.
176. Treat route boundaries, feature folders, and service scopes as deliberate design tools.
18
19## Good Structure
20
21```typescript
22import { Component, inject } from '@angular/core';
23
24interface UserSummary {
25 id: string;
26 name: string;
27 tier: 'free' | 'pro';
28}
29
30class UserSummaryService {
31 getSummary(userId: string): UserSummary {
32 return { id: userId, name: 'Ada Lovelace', tier: 'pro' };
33 }
34}
35
36@Component({
37 standalone: true,
38 selector: 'app-user-summary-card',
39 template: `
40 <article>
41 <h2>{{ user.name }}</h2>
42 <p>Plan: {{ user.tier }}</p>
43 </article>
44 `,
45 providers: [UserSummaryService],
46})
47export class UserSummaryCardComponent {
48 private readonly service = inject(UserSummaryService);
49 user = this.service.getSummary('user-1');
50}
51```
52
53## Feature Boundary Pattern
54
55```typescript
56import { Injectable, signal } from '@angular/core';
57
58@Injectable()
59export class CartFacade {
60 private readonly _items = signal<string[]>([]);
61 readonly items = this._items.asReadonly();
62
63 add(item: string): void {
64 this._items.update((items) => [...items, item]);
65 }
66}
67```
68
69## Architectural Guardrails
70
71- Use services for I/O, persistence, and cross-component state coordination.
72- Use pure helper functions for deterministic data shaping.
73- Keep templates declarative and keep business rules out of them.
74- Co-locate feature-specific routes, tests, and assets with the feature when possible.
75- Prefer explicit provider scopes over global singleton behavior when a feature owns the dependency.
76
77## Anti-Patterns
78
79- Do not create shared abstractions before you have two real consumers.
80- Do not put view logic, API calls, and state mutation into one component.
81- Do not use a global service when a feature-scoped facade is enough.
82
83## Review Checklist
84
85- The feature can be understood from its folder and route boundary.
86- Components are thin and declarative.
87- Services own rules, data access, or coordination.
88- Shared code is actually shared.