git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/angular-service-di" ~/.claude/skills/intense-visions-harness-engineering-angular-service-di && rm -rf "$T"
agents/skills/claude-code/angular-service-di/SKILL.mdAngular Service & Dependency Injection
Design Angular services with the right provider scope, injection tokens, and hierarchical injector strategy
When to Use
- Creating a shared service (data fetching, state, utilities) and deciding on its scope
- Replacing a module-level provider with
or a standalone providerprovidedIn: 'root' - Using
to inject non-class values (config objects, feature flags, URLs)InjectionToken - Injecting services into other services without circular dependencies
- Providing mock implementations in tests
Instructions
- Decorate every service with
. Use@Injectable
as the default — it makes the service a singleton and tree-shakeable without needing to list it in a module.providedIn: 'root' - Use
for app-wide singletons. Use component-levelprovidedIn: 'root'
only when you need a fresh instance per component subtree.providers: [MyService] - Create
for any non-class dependency: API base URLs, feature flags, environment config, or factory functions.InjectionToken<T> - Use the
function (Angular 14+) instead of constructor injection when writing standalone functions, guards, or effects. It reads more cleanly and is required outside class constructors.inject() - Avoid circular dependencies by extracting shared state into a lower-level service that neither service imports.
- Prefer
injection over implementingDestroyRef
in services — it keeps cleanup co-located with subscription setup.OnDestroy
// environment config token export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL', { providedIn: 'root', factory: () => 'https://api.example.com', }); @Injectable({ providedIn: 'root' }) export class ProductService { private readonly http = inject(HttpClient); private readonly baseUrl = inject(API_BASE_URL); private readonly destroyRef = inject(DestroyRef); getProducts(): Observable<Product[]> { return this.http.get<Product[]>(`${this.baseUrl}/products`); } } // Override in tests or feature modules providers: [{ provide: API_BASE_URL, useValue: 'https://staging.example.com' }];
// Component-scoped service — new instance per component tree @Component({ selector: 'app-wizard', providers: [WizardStateService], // fresh instance, destroyed with component template: `...`, }) export class WizardComponent {}
Details
Injector hierarchy: Angular maintains a tree of injectors that mirrors the component tree. When a token is requested, Angular walks up the tree until it finds a provider. Root injector covers the entire app. Module injectors cover lazy-loaded modules. Component injectors cover a component and its descendants. This hierarchy is the mechanism behind scoped singletons.
vs module providers: Module providers were the Angular 2–12 idiom. They work but require importing the module to get the service. providedIn: 'root'
providedIn: 'root' eliminates that coupling and enables tree-shaking — unused services are removed from the bundle automatically.
function: Available in any injection context (constructor, field initializer, factory function, guard, resolver). It cannot be called in a method body called outside the injection context. Use it freely in class field initializers and inject()
@Component function callbacks.
Multi-providers: Use
multi: true with a token to collect multiple implementations under one token. Angular's HTTP_INTERCEPTORS and APP_INITIALIZER tokens use this pattern.
export const VALIDATORS = new InjectionToken<Validator[]>('VALIDATORS'); providers: [ { provide: VALIDATORS, useClass: EmailValidator, multi: true }, { provide: VALIDATORS, useClass: PhoneValidator, multi: true }, ]; // inject(VALIDATORS) → [EmailValidator, PhoneValidator]
Service isolation for testing: Provide a mock in
TestBed.configureTestingModule:
TestBed.configureTestingModule({ providers: [{ provide: ProductService, useValue: mockProductService }], });
Tree-shakeable tokens: Pair
InjectionToken with a factory to make the default value tree-shakeable:
export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>('FEATURE_FLAGS', { providedIn: 'root', factory: () => ({ darkMode: false, betaSearch: false }), });
Source
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.