Skip to the solution
MediumFrontend9 min read

What is a service and why use it?

365 views

The problem

Question presented to candidate: "Three components on our dashboard each fetch and filter notifications themselves, and they keep drifting out of sync. What is an Angular service, and how would one fix this?"

What a strong answer should cover:

  • A service is a plain class that holds logic or state that does not belong to one view, made available through dependency injection.
  • Why: one source of truth for shared state, reusable business rules, thin components, and an easy seam for test fakes.
  • How it is registered: @Injectable({ providedIn: 'root' }) for an app-wide singleton, or the newer @Service() decorator (Angular 22.1+), which is root-provided by default and only supports inject().
  • How it is consumed: inject(MyService) in a component, directive, pipe, guard or another service.
  • Good practice: expose read-only state (a readonly signal or observable) and mutate only through service methods.
  • Scope matters: root gives one shared instance; a component providers array gives each component its own.

Clarifying questions expected:

  • "Should all three components see the same notifications, or does each need its own list?"
  • "Is the data local state, or does it come from an API that should be cached?"

Code / implementation expected: Yes β€” a small service with state and methods, injected into two or more components.

  • services

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

AngularA ToastService shared by three components, with the rules kept in the service
import { Component, Service, computed, inject, signal } from "@angular/core";

export interface Toast {
  id: number;
  text: string;
}

// The service owns the state and the rules. Components only display and forward clicks.
// @Service() (Angular 22.1+) is provided in root, like @Injectable({ providedIn: "root" }).
@Service()
export class ToastService {
  private nextId = 1;
  private readonly _toasts = signal<Toast[]>([]);
  readonly toasts = this._toasts.asReadonly(); // components can read, not write
  readonly count = computed(() => this._toasts().length);

  show(text: string) {
    const clean = text.trim();
    if (!clean) return; // business rule lives here, not in every component
    this._toasts.update((list) => [...list, { id: this.nextId++, text: clean }]);
  }
  dismiss(id: number) {
    this._toasts.update((list) => list.filter((t) => t.id !== id));
  }
}

@Component({
  selector: "app-header",
  template: `<header>Inbox ({{ toasts.count() }})</header>`,
})
export class Header {
  toasts = inject(ToastService);
}

@Component({
  selector: "app-composer",
  template: `
    <input #box placeholder="message" />
    <button (click)="toasts.show(box.value); box.value = ''">send</button>
  `,
})
export class Composer {
  toasts = inject(ToastService);
}

@Component({
  selector: "app-root",
  imports: [Header, Composer],
  template: `
    <app-header />
    <app-composer />
    <ul>
      @for (t of toasts.toasts(); track t.id) {
        <li>{{ t.text }} <button (click)="toasts.dismiss(t.id)">x</button></li>
      }
    </ul>
  `,
})
export class AppComponent {
  toasts = inject(ToastService); // same instance the header and composer got
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 65 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextMedium

How do you test signal components, defer blocks, and new control flow in 2025?

Continue