Cómo estructurar un proyecto Angular grande sin morir en el intento
Tiempo estimado de lectura: 3 min
Ideas clave:
- Organiza por dominios (slicing vertical) en lugar de por tipo de archivo.
- Divide librerías en feature / ui / data-access (domain) / util y aplica reglas de dependencia.
- Usa el patrón Facade para aislar la UI del estado y poder cambiar implementaciones.
- Automatiza límites con reglas (ESLint / depConstraints de Nx) y aprovecha caching y librerías en Nx.
Tabla de contenidos
- Título
- Resumen rápido (lectores con prisa)
- Cómo estructurar un proyecto Angular grande sin morir en el intento (resumen práctico)
- Slicing vertical por dominio
- Taxonomía de librerías que sí funcionan
- Patrón Facade: amortiguador entre UI y estado
- Reglas de dependencia: automatiza la disciplina
- Estrategias prácticas para migración y crecimiento
- Criterio técnico final
- FAQ
Cómo estructurar un proyecto Angular grande sin morir en el intento empieza por admitir que el problema no es Angular: es tu arquitectura. Agrupar por tipo de archivo es cómodo al principio y mortal a los 6 meses. Aquí explico cómo organizar por dominios, qué librerías crear, qué patrones aplicar y qué reglas automatizar para que tu app crezca sin destrozar la productividad del equipo.
En las primeras líneas: si tu equipo supera los dos desarrolladores, deja de pensar en carpetas por tecnología y empieza a pensar en dominios de negocio. Usa Nx para orquestar librerías, aplica el patrón Facade y fuerza límites con ESLint. Enlaces útiles: Nx, Module Boundaries, Standalone Components.
Resumen rápido (lectores con prisa)
Slicing vertical = agrupar por dominio. Librerías: feature / ui / data-access (domain) / util. Facade = única API estable para UI. Reglas (ESLint / depConstraints) hacen cumplir límites. Nx facilita orquestación y caching.
Cómo estructurar un proyecto Angular grande sin morir en el intento (resumen práctico)
La estructura que realmente escala combina tres decisiones simples: 1) Slicing vertical por dominio, 2) Librerías tipadas (feature/ui/data-access/util), 3) Patrones que desacoplan (Facade, Barrels) y reglas automáticas (ESLint). No es exotismo; es disciplina.
Slicing vertical por dominio
Evita el antipatrón: /components, /services, /models. En su lugar, crea dominios verticales:
/libs/booking→ todo lo relacionado con reservas./libs/payments→ lógica de pagos./libs/shared/ui-kit→ componentes presentacionales.
Cada dominio contiene sub-librerías con responsabilidades claras. El resultado: para cambiar una feature solo tocas la librería del dominio pertinente.
Taxonomía de librerías que sí funcionan
Divide las librerías en cuatro tipos. Cada tipo tiene reglas claras de dependencia.
1. Feature (feature-*)
- Contiene smart components, rutas y orquestación UI.
- Puede depender de
data-access,uiyutil. - Normalmente cargada lazy.
2. UI (ui-*)
- Componentes dumb, sin lógica de negocio.
- Solo depende de
uiyutil. - Reutilizable y testeable.
3. Data-access / Domain (data-access-* o domain-*)
- Estado, API services, interfaces y Facades.
- No debe depender de
featureniui.
4. Util (util-*)
- Funciones puras, helpers, form validators.
- Sin dependencias del resto del repo.
Ejemplo de estructura en Nx:
/apps
/customer-portal
/libs
/booking
/feature-search
/data-access
/ui-seat-map
/payments
/feature-checkout
/data-access
/shared
/ui-kit
/util-formatters
Patrón Facade: amortiguador entre UI y estado
El patrón Facade es la guardia de contención. Cualquier componente de feature consume una Facade, no la store o servicios HTTP directamente. Si cambias la implementación de estado, solo cambias la Facade.
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class BookingFacade {
readonly bookings = this.store.selectSignal(selectBookings);
readonly loading = this.store.selectSignal(selectLoading);
loadBookings() { this.store.dispatch(BookingActions.load()); }
createBooking(data: BookingInput) { this.store.dispatch(BookingActions.create({ data })); }
}
El componente usa solo la Facade. Cambias NgRx por Signals o por otra librería y el componente sigue igual.
Reglas de dependencia: automatiza la disciplina
No confíes en la buena voluntad. Usa reglas de ESLint para imponer límites. Con Nx configuras depConstraints para que una ui nunca importe data-access, o que feature solo dependa de ui, data-access y util.
{
"depConstraints": [
{ "sourceTag": "type:ui", "onlyDependOnLibsWithTags": ["type:ui","type:util"] },
{ "sourceTag": "type:feature", "onlyDependOnLibsWithTags": ["type:ui","type:data-access","type:util"] }
]
}
Documentación: Module Boundaries
Estrategias prácticas para migración y crecimiento
- Start small: convierte primero librerías compartidas críticas a
ui-*ydata-access-*. - Facades on day one: crea facades incluso si hoy solo usan simple HTTP; evitarás reescrituras.
- Tests per lib: cada librería con su suite de unit y e2e en CI. Nx hará caching de builds/tests.
- Standalone components: adopta
standalone: truepara mejorar tree-shaking y simplificar imports (Standalone Components). - Evita SharedModule gigante: usa barrels (
index.ts) y exportaciones controladas.
Criterio técnico final
Un proyecto Angular grande que no se descomponga se basa en tres cosas: límites explícitos, contratos estables (Facades/interfaces) y herramientas que refuerzan la arquitectura. Nx + Slicing vertical + Facades + ESLint = velocidad sostenible. No prometo milagros, pero sí reproducibilidad: equipos que crecen y siguen entregando sin que el repo se convierta en una fosa de hormigón.
La próxima decisión es cultural: implantar y hacer cumplir estas reglas. Hazlo y tu código dejará de pelearse consigo mismo; tu equipo ganará espacio para resolver problemas reales.
FAQ
- ¿Por qué agrupar por dominios en lugar de por tipo de archivo?
- ¿Qué tipos de librerías debo crear?
- ¿Qué es una Facade y cuándo usarla?
- ¿Cómo aplico reglas de dependencia con Nx?
- ¿Debería adoptar standalone components ahora?
- ¿Cómo migrar un repo monolítico a este enfoque por dominios?
¿Por qué agrupar por dominios en lugar de por tipo de archivo?
Agrupar por dominios (slicing vertical) reduce el acoplamiento entre features y evita que cambios en una área afecten todo el repo. Cuando el equipo supera los dos desarrolladores, la organización por tecnología se vuelve difícil de mantener y ralentiza la entrega.
¿Qué tipos de librerías debo crear?
Crea cuatro tipos: feature-* (orquestación UI y rutas), ui-* (componentes dumb), data-access-* o domain-* (estado, servicios API, facades) y util-* (helpers puros). Cada tipo tiene reglas claras de dependencia para mantener límites.
¿Qué es una Facade y cuándo usarla?
Una Facade es una capa que expone una API estable para la UI y oculta la implementación del estado (NgRx, Signals, etc.). Úsala desde el inicio: permite cambiar la implementación interna sin tocar componentes presentacionales.
¿Cómo aplico reglas de dependencia con Nx?
Configura depConstraints en la configuración de Nx para imponer qué tags pueden depender entre sí. Complementa con reglas de ESLint para bloquear importaciones directas no deseadas y automatizar la disciplina arquitectónica.
¿Debería adoptar standalone components ahora?
Adoptar standalone: true mejora tree-shaking y simplifica imports, por lo que es una buena práctica a considerar. Evalúa el impacto en tu flujo y migra de forma incremental donde aporte más valor.
¿Cómo migrar un repo monolítico a este enfoque por dominios?
Empieza por mover librerías compartidas críticas a ui-* y data-access-*. Crea Facades desde el primer día para encapsular dependencias. Añade pruebas por librería y configura depConstraints en Nx para evitar regresiones.
