What responsibilities belong inside an Angular component vs a service?

HighIntermediateAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

Angular components should focus on UI/presentation and user interactions, while services should hold reusable logic, business rules, and side effects (HTTP, caching, state). Clean separation keeps components thin, improves testability, and avoids duplicated logic across the app.

Answer

Overview

Use components for rendering + wiring UI events to actions. Use services for reusable logic and side effects (HTTP, caching, state, orchestration). A good component reads like a “view model composer”, not a mini back-end.

Area

Component (belongs here)

Service (belongs here)

UI rendering

Template bindings, UI composition, structural directives, view-only formatting

No DOM/template concerns

User interaction

Click handlers, form submit handlers, mapping UI events → intent/actions

Input validation rules that are reusable across screens

State

Ephemeral UI state (open/closed, selected tab, local filters, loading flags for UI)

Shared/stateful app logic (cache, session data, cross-component state), facades

Business rules

Minimal (only what’s needed to connect UI to rules)

Core rules (pricing logic, permission checks, normalization, mapping DTO→domain)

Data access

Never call low-level APIs directly (avoid HttpClient usage in components)

HTTP calls, retries, error mapping, caching, request dedupe

Reusability

Rare (components can be reusable, but should avoid app-specific business logic)

High (same service can be used by multiple components)

Testing

Shallow: assert template bindings + event wiring + observable usage

Deep: unit test rules/side effects without rendering

Component vs Service responsibilities

Rule of thumb

If the code depends on how something is shown (DOM, template state, view events) → component. If it depends on what something means (rules, data fetching, caching, orchestration) → service.

TYPESCRIPT
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, shareReplay } from 'rxjs';

export interface UserDto { id: string; name: string; }
export interface User { id: string; displayName: string; }

@Injectable({ providedIn: 'root' })
export class UserService {
  private users$?: Observable<User[]>;

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    // Cache + dedupe requests for this session
    this.users$ ??= this.http.get<UserDto[]>('/api/users').pipe(
      // Business mapping belongs here (or a dedicated mapper)
      // so multiple components get consistent data.
      // Keep mapping pure.
      // DTO -> domain model:
      // (If mapping grows, extract to a pure function.)
      shareReplay({ bufferSize: 1, refCount: true })
    ) as unknown as Observable<User[]>;

    return this.users$;
  }
}
                  
TYPESCRIPT
// user-list.component.ts
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Observable, map, startWith } from 'rxjs';
import { UserService, User } from './user.service';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserListComponent {
  // UI state belongs here
  readonly search = '';

  // Data stream comes from a service
  readonly users$: Observable<User[]> = this.userService.getUsers();

  // UI-only derived state can be computed in the component
  // (keep it cheap; heavy transforms should be in a service/facade)
  readonly viewModel$ = this.users$.pipe(
    map(users => ({
      users,
      total: users.length
    })),
    startWith({ users: [], total: 0 })
  );

  constructor(private userService: UserService) {}

  onRefreshClick(): void {
    // UI intent: trigger refresh.
    // If refresh needs side-effects (invalidate cache), expose that from the service.
    // e.g. this.userService.invalidateUsersCache();
  }
}
                  

Common mistake

Why it hurts

Fix

HttpClient calls inside components

Duplicates logic, makes components hard to test and reuse

Move data access to a service/facade

Business rules in templates (complex expressions)

Hard to read, hard to test, runs every CD cycle

Compute in TS (component for UI-only, service for reusable rules)

Services manipulating the DOM

Breaks separation, complicates SSR/testing

Keep DOM work in components/directives; services stay UI-agnostic

“God component” that owns everything

Becomes unmaintainable; PRs become risky

Extract services + presentational components; keep the container thin

Typical boundary violations

Extra nuance: service scope

Most services are singletons (providedIn: 'root'). If you need per-component instance state (e.g., a wizard session), provide the service at the component level via providers: [WizardStateService] so each component instance gets its own copy.

Summary

Keep components focused on UI + event wiring + lightweight view-model shaping. Put reusable logic, side effects, data access, and business rules into services (or facades). This separation improves testability, reuse, and long-term maintainability.

Similar questions
Guides
5 / 37