„O mut în shared?” e probabil cea mai frecventă întrebare de arhitectură într-un proiect Angular. Răspunsul scurt: mai rar decât crezi. shared e pentru cod generic (fără domeniu) și stabil. Orice pui acolo devine o dependență a mai multor feature-uri — și orice schimbare a lui le poate strica pe toate.
Mută în shared doar ce e folosit deja în mai multe feature-uri, fără termeni de domeniu și fără dependențe de date. În rest, componenta stă lângă codul care o folosește.
De ce: O singură utilizare nu justifică o abstracție. Nu știi încă ce variații vor apărea.
Pasul următor: Las-o acolo. Reevaluează când apare a doua (sau a treia) utilizare reală.
Checklist înainte de mutare
Înainte să muți în shared, răspunde DA la toate:
[ ] E folosită ACUM în cel puțin 2 feature-uri diferite (nu „poate o să fie”)
[ ] Numele și API-ul nu conțin termeni de domeniu
[ ] Nu injectează store-uri, servicii de date sau Router
[ ] Comunică doar prin input / output / model / ng-content
[ ] Toate utilizările au același comportament (fără flag-uri per utilizare)
[ ] Ești dispus să-i tratezi API-ul ca pe unul public (orice schimbare afectează pe toți)Cum extragi corect: generic în shared, domeniu în feature
Rar muți o componentă „ca atare”. De obicei o spargi: partea generică (aspect, comportament) merge în shared/ui, iar partea de domeniu (etichete, culori pe status, reguli) rămâne în feature, ca un wrapper subțire.
// shared/ui/badge.ts — generic, fără domeniu
@Component({
selector: 'app-badge',
host: { '[class]': '"badge " + tone()' },
template: `<ng-content />`,
})
export class Badge {
readonly tone = input<'neutral' | 'success' | 'danger' | 'warning'>('neutral');
}
// features/orders/ui/order-status-badge.ts — wrapper de domeniu, rămâne în feature
@Component({
selector: 'app-order-status-badge',
imports: [Badge],
template: `<app-badge [tone]="tone()">{{ label() }}</app-badge>`,
})
export class OrderStatusBadge {
readonly status = input.required<OrderStatus>();
protected readonly label = computed(() => ORDER_LABELS[this.status()]);
protected readonly tone = computed(() => (this.status() === 'cancelled' ? 'danger' : 'success'));
}// features/orders/ui/order-status-badge.ts — folosită în orders ȘI în dashboard
@Component({
selector: 'app-order-status-badge',
template: `<span [class]="'badge ' + color()">{{ label() }}</span>`,
})
export class OrderStatusBadge {
readonly status = input.required<OrderStatus>();
protected readonly label = computed(() => ORDER_LABELS[this.status()]);
protected readonly color = computed(() => (this.status() === 'cancelled' ? 'red' : 'green'));
}Componente de domeniu folosite în mai multe feature-uri
UserAvatar e folosit în orders, comments și admin. Nu e generic (e despre useri), dar e partajat. Soluția: rămâne în feature-ul proprietar (users) și e expus printr-un API public. Așa e clar cine îl întreține, iar restul feature-ului users rămâne privat.
// features/users/index.ts — „vitrina” feature-ului users pentru restul aplicației
export { UserAvatar } from './ui/user-avatar';
export type { User } from './models/user';
// tot restul (store-ul intern, paginile, mapperii) rămâne privat
// features/orders/ui/order-row.ts
import { UserAvatar } from '@features/users'; // ✓ prin API-ul public
// import { UserAvatar } from '@features/users/ui/user-avatar'; ✗ în interiorul altui featureSemnale că o componentă shared trebuie readusă în feature
// Semnalul că o componentă shared trebuie spartă (sau readusă în feature)
@Component({ selector: 'app-card' })
export class Card {
readonly compact = input(false);
readonly showPrice = input(true);
readonly isCheckout = input(false); // ← un feature se „scurge” în shared
readonly hideAvatarOnMobile = input(false);
readonly legacyMode = input(false); // ← nimeni nu mai știe de ce există
}- Input-uri de tip „sunt folosită în pagina X”.
- Orice modificare cerută de un feature necesită verificarea tuturor celorlalte.
- Doar un feature mai folosește componenta.
Unde stă fiecare folder e la Structura de foldere; regulile despre cine are voie să importe ce, la Barrels, aliasuri & granițe.
Când DA, când NU
- Butoane, carduri, badge-uri, dialoguri, tabele generice, paginatoare.
- Directive și pipe-uri de UI: tooltip, autofocus, timeAgo.
- Layout-uri: page header, empty state, skeleton.
- Orice cu nume de domeniu:
ProductCard,InvoiceTable,UserFilters. - Componente folosite într-un singur feature (chiar dacă de 10 ori).
- Componente care injectează store-uri sau API-uri.
Două componente aproape identice în două feature-uri care evoluează independent sunt mai ușor de întreținut decât una comună plină de if-uri. Așteaptă până vezi clar ce e comun („rule of three”).