
Angular's New @Service Decorator: A Cleaner Take on DI
Since I started with Angular 6, dependency injection has worked the same way: slap @Injectable({ providedIn: 'root' }) on a class, and Angular knows it can be injected into components, pipes, guards, whatever needs it. That pattern hasn't changed in years — until Angular 22.
The old way: @Injectable({providedIn: 'root'})
Here's a typical service using the pattern most of us have written a hundred times:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Order {
id: string;
total: number;
status: string;
}
@Injectable({ providedIn: 'root' })
export class OrderService {
constructor(private http: HttpClient) {}
getOrders(): Observable<Order[]> {
return this.http.get<Order[]>('https://api.example.com/orders');
}
}
providedIn: 'root' registers the service as a singleton at the application root, and getOrders() returns an RxJS Observable — you subscribe to it to get the data.
To use it in a component, you inject it through the constructor:
import { Component, OnInit } from '@angular/core';
import { OrderService, Order } from './order.service';
@Component({
selector: 'app-order-list',
templateUrl: './order-list.component.html',
})
export class OrderListComponent implements OnInit {
orders: Order[] = [];
constructor(private orderService: OrderService) {}
ngOnInit(): void {
this.orderService.getOrders().subscribe((orders) => {
this.orders = orders;
});
}
}
This works fine. It's also a lot of ceremony for something so common: an injectable decorator with an options object, a constructor parameter just to grab a dependency, and a manual subscription to manage.
The new way: @Service
Angular 22 adds a @Service() decorator that's a more opinionated, minimal version of @Injectable({ providedIn: 'root' }). You drop the options object entirely — root-level singleton is the default — and it only works with the inject() function, not constructor injection. Paired with httpResource, the signal-based wrapper around HttpClient, you get a service with almost no boilerplate:
import { Service } from '@angular/core';
import { httpResource } from '@angular/common/http';
export interface Order {
id: string;
total: number;
status: string;
}
@Service()
export class OrderService {
ordersResource = httpResource<Order[]>(() => 'https://api.example.com/orders');
}
No constructor, no HttpClient injection to wire up manually, no Observable to subscribe and unsubscribe from. ordersResource exposes value(), isLoading(), error(), and hasValue() as signals, and the request fires eagerly the moment the resource is created.
Consuming it in a component is just as lean:
import { Component, inject } from '@angular/core';
import { OrderService } from './order.service';
@Component({
selector: 'app-order-list',
template: `
@if (orderService.ordersResource.isLoading()) {
<p>Loading orders...</p>
} @else if (orderService.ordersResource.hasValue()) {
<ul>
@for (order of orderService.ordersResource.value(); track order.id) {
<li>{{ order.id }} — {{ order.total }} ({{ order.status }})</li>
}
</ul>
} @else if (orderService.ordersResource.error()) {
<p>Failed to load orders.</p>
}
`,
})
export class OrderListComponent {
orderService = inject(OrderService);
}
inject() replaces the constructor parameter, and the template reads straight off the resource's signals — no ngOnInit, no subscription, no manual state variable to keep in sync.
Worth knowing before you reach for @Service everywhere: it's not a full replacement for @Injectable. If you need constructor injection (say, a codebase that hasn't migrated to inject() yet), advanced provider config like useClass or useFactory, or non-root scopes like platform, you still want @Injectable. @Service also has a factory option and an autoProvided: false flag if you need to scope it manually instead of at root.
Worth adopting
I like where Angular's taken this. It's less boilerplate, the name tells you exactly what the class is for, and pairing it with inject() and signals makes services feel like a natural extension of the rest of the signal-based API instead of a bolted-on RxJS layer. I've already started using @Service and inject() in projects I'm working on. If you're migrating an existing Angular app or starting a new one, it's worth adopting now.