Angular Services and Dependency Injection Explained

Angular Tutorial: Beginner to Advanced · 7. Services and dependency injection · 4 min read · Updated

Dependency injection

  1. A service is a class that holds logic or data you want to reuse, such as API calls.
  2. Mark it with @Injectable and provide it, often with providedIn: root.
  3. A component asks for it with inject(). It does not create it with new.
  4. Another component asks, and gets the same instance.
  5. This keeps components small and makes services easy to replace in tests.

Components should focus on the screen. Logic that is not about drawing, such as loading data, calculating prices or remembering the signed-in user, belongs somewhere else. In Angular that place is a service, and the way components get hold of services is called dependency injection.

What you will learn

  • What a service is and why it helps
  • What dependency injection means in simple terms
  • How to create a service and use it with inject()

What is a service?

A service is a plain class with a job. It has no template. Components ask for it when they need it, and Angular supplies the same shared instance every time.

Good uses for services:

  • Talking to a server (you will do this in the HTTP module)
  • Holding data that several components share
  • Business rules that you want to test on their own

What is dependency injection?

Suppose a component needs a quote service. The component could create one itself with new QuoteService(), but then it is stuck with that exact class, and it is hard to replace it in a test. With dependency injection the component only says what it needs, and Angular provides it. This keeps components simple and easy to test.

Create a service

Generate one with ng generate service quote. The CLI creates a class marked with the @Service() decorator, which tells Angular to make it available everywhere in your app. Here it is with some content:

// src/app/quote.ts
import { Service } from '@angular/core';

@Service()
export class Quote {
  private readonly quotes = [
    'Make it work, then make it better.',
    'Small steps every day add up.',
    'Read the error message. It is usually right.',
  ];

  random(): string {
    const index = Math.floor(Math.random() * this.quotes.length);
    return this.quotes[index];
  }
}

By default there is one instance of the service for the whole application, shared by everyone who asks for it.

Use it with inject()

Inside a component, call inject() with the class you want:

// src/app/quote-box/quote-box.ts
import { Component, inject, signal } from '@angular/core';
import { Quote } from '../quote';

@Component({
  selector: 'app-quote-box',
  template: `
    <blockquote>{{ text() }}</blockquote>
    <button type="button" (click)="next()">Another quote</button>
  `,
})
export class QuoteBox {
  private readonly quote = inject(Quote);
  protected readonly text = signal(this.quote.random());

  protected next(): void {
    this.text.set(this.quote.random());
  }
}
// src/app/app.ts
import { Component } from '@angular/core';
import { QuoteBox } from './quote-box/quote-box';

@Component({
  imports: [QuoteBox],
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
  <h1>Services</h1>
  <app-quote-box />
</main>

The component never writes new Quote(). It asks for it with inject(Quote), and Angular hands over the shared instance. Call inject in a field initializer or the constructor, which are the places where Angular can supply it.

The older way you will still see

Before @Service(), services were written with @Injectable and an option that makes them available everywhere. Both work, and you will see the older form in most existing projects and tutorials:

// src/app/legacy-greeting.ts
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class LegacyGreeting {
  hello(name: string): string {
    return `Hello, ${name}!`;
  }
}

Older code also injects through the constructor, such as constructor(private quote: Quote) {}. The inject() function does the same job with less code and is easier to use in functions.

Where do instances live?

  • App-wide (default): one instance for the whole app, which suits data and logic you share.
  • Per component: if you add the service to a component's providers list, each instance of that component gets its own copy. This is useful when every card needs separate state.

Try it yourself

  1. Add a count() method to the Quote service that returns how many quotes it knows, and show it.
  2. Create a second component that also uses Quote. Confirm that both work.
  3. Create a service Greeter with a method that returns a greeting for a name, and use it in a component.

Quick check

  1. What is a service?
  2. What does inject(Quote) do?
  3. How many instances of a default service does the application have?

Next, put a service to work by sharing state in Share state with a service. The dependency injection guide has the full details.

Frequently asked questions

What is the difference between @Service() and @Injectable()?

Both mark a class that Angular can inject. @Service() is the newer, shorter form for services that are available app-wide. @Injectable({ providedIn: 'root' }) does the same job and is common in existing code.

Can a service use another service?

Yes. Inside the service call inject() for whatever it needs. Angular builds the chain for you.