
Angular Di
Structure Angular apps with inject(), facade services, hierarchical providers, and testable DI patterns instead of ad-hoc singletons.
Overview
angular-di is an agent skill for the Build phase that teaches Angular dependency injection patterns including facades, state services, tokens, and hierarchical providers.
Install
npx skills add https://github.com/analogjs/angular-skills --skill angular-diWhat is this skill?
- Facade service pattern combining Product, Cart, and Order behind one ShopFacade API
- State service pattern using signals for UserState with selective exposed state
- Abstract classes as injection tokens for swappable implementations
- Hierarchical injection and dynamic providers for feature-scoped trees
- Testing with DI plus DestroyRef and cleanup lifecycle guidance
- Table of contents lists 6 DI topic sections from service patterns through DestroyRef
Adoption & trust: 4.7k installs on skills.sh; 592 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Angular feature code sprawl makes services hard to test and providers hard to scope across routes and lazy modules.
Who is it for?
Indie developers building Angular SaaS or mobile-web apps who want copy-paste DI architecture patterns.
Skip if: Non-Angular frameworks or backend-only API design with no Angular client.
When should I use this skill?
You are implementing or refactoring Angular services, providers, facades, or DI-based tests.
What do I get? / Deliverables
You implement facade and state-service patterns with inject(), explicit tokens, and DestroyRef-aware cleanup that agents can replicate in new modules.
- Facade and state-service TypeScript scaffolds
- Provider and token configuration for feature modules
Recommended Skills
Journey fit
Dependency injection patterns are applied while implementing the Angular frontend and shared client services. Frontend subphase is the canonical shelf for Angular service composition, tokens, and component-level providers.
How it compares
Frontend DI pattern reference for Angular, not a generic TypeScript refactoring or npm packaging skill.
Common Questions / FAQ
Who is angular-di for?
Solo and indie builders shipping Angular apps who need consistent service layering, injection tokens, and test doubles.
When should I use angular-di?
Use it in Build while designing shop or dashboard facades, signal-based state services, route-level providers, or DI-focused unit tests.
Is angular-di safe to install?
It is documentation and code-pattern guidance; review the Security Audits panel on this page before allowing broader agent permissions in your repo.
SKILL.md
READMESKILL.md - Angular Di
# Angular Dependency Injection Patterns ## Table of Contents - [Service Patterns](#service-patterns) - [Abstract Classes as Tokens](#abstract-classes-as-tokens) - [Hierarchical Injection](#hierarchical-injection) - [Dynamic Providers](#dynamic-providers) - [Testing with DI](#testing-with-di) - [DestroyRef and Cleanup](#destroyref-and-cleanup) ## Service Patterns ### Facade Service Combine multiple services into a single API: ```typescript @Injectable({ providedIn: 'root' }) export class ShopFacade { private productService = inject(Product); private cartService = inject(Cart); private orderService = inject(Order); // Expose combined state readonly products = this.productService.products; readonly cart = this.cartService.items; readonly cartTotal = this.cartService.total; // Unified actions addToCart(productId: string, quantity: number) { const product = this.productService.getById(productId); if (product) { this.cartService.add(product, quantity); } } async checkout() { const items = this.cartService.items(); const order = await this.orderService.create(items); this.cartService.clear(); return order; } } ``` ### State Service Pattern ```typescript interface UserState { user: User | null; loading: boolean; error: string | null; } @Injectable({ providedIn: 'root' }) export class UserState { private state = signal<UserState>({ user: null, loading: false, error: null, }); // Selectors readonly user = computed(() => this.state().user); readonly loading = computed(() => this.state().loading); readonly error = computed(() => this.state().error); readonly isAuthenticated = computed(() => this.state().user !== null); // Actions setUser(user: User) { this.state.update(s => ({ ...s, user, loading: false, error: null })); } setLoading() { this.state.update(s => ({ ...s, loading: true, error: null })); } setError(error: string) { this.state.update(s => ({ ...s, loading: false, error })); } clear() { this.state.set({ user: null, loading: false, error: null }); } } ``` ### Repository Pattern ```typescript // Generic repository interface export abstract class Repository<T extends { id: string }> { abstract getAll(): Promise<T[]>; abstract getById(id: string): Promise<T | null>; abstract create(item: Omit<T, 'id'>): Promise<T>; abstract update(id: string, item: Partial<T>): Promise<T>; abstract delete(id: string): Promise<void>; } // HTTP implementation @Injectable() export class HttpUserRepo extends Repository<User> { private http = inject(HttpClient); private apiUrl = inject(API_URL); async getAll(): Promise<User[]> { return firstValueFrom(this.http.get<User[]>(`${this.apiUrl}/users`)); } async getById(id: string): Promise<User | null> { return firstValueFrom( this.http.get<User>(`${this.apiUrl}/users/${id}`).pipe( catchError(() => of(null)) ) ); } async create(user: Omit<User, 'id'>): Promise<User> { return firstValueFrom(this.http.post<User>(`${this.apiUrl}/users`, user)); } async update(id: string, user: Partial<User>): Promise<User> { return firstValueFrom(this.http.patch<User>(`${this.apiUrl}/users/${id}`, user)); } async delete(id: string): Promise<void> { await firstValueFrom(this.http.delete(`${this.apiUrl}/users/${id}`)); } } // Provide implementation { provide: Repository, useClass: HttpUserRepo } ``` ## Abstract Classes as Tokens Use abstract classes for better type safety: ```typescript // Abstract service definition export abstract class Logger { abstract log(message: string): void; abstract error(message: string, error?: Error): void; abstract warn(message: string): void; } // Console implementation @Injectable() export class ConsoleLog extends Logger { log(message: string) { console.log(`[LOG] ${message}`); } error(message: string, error?: Error) { cons