Publicado em
- 6 minutos de leitura
Migre @Injectable para @Service com o schematic do Angular 22
O Angular 22 ganhou uma forma mais curta de declarar serviços com o decorator @Service. Para projetos existentes, o framework agora oferece um schematic que faz boa parte da migração automaticamente:
ng generate @angular/core:service
O comando procura classes decoradas com @Injectable, analisa se elas são compatíveis com @Service e altera apenas os casos seguros. Ele também ajusta os imports de @angular/core.
Apesar do nome, esse comando não cria um serviço novo. ng generate service products continua sendo o gerador de arquivos do Angular CLI. Ao informar a collection @angular/core, o alias service aponta para o schematic de migração service-migration.
Essa cautela é necessária porque os dois decorators têm diferenças. O @Service() cria um provider no injetor raiz por padrão e usa inject() para receber dependências. Já o @Injectable continua atendendo cenários como injeção pelo construtor, escopo platform e configurações avançadas de provider.
Neste artigo, vamos ver o que o schematic altera, quais classes ele preserva e como encaixar a migração no fluxo de atualização de um projeto real.
O que muda com @Service
O caso mais comum em uma aplicação Angular é este:
import { inject, Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
@Injectable({ providedIn: 'root' })
export class ProductsApi {
private readonly http = inject(HttpClient)
findAll() {
return this.http.get<Product[]>('/api/products')
}
}
Depois da migração, o resultado fica assim:
import { inject, Service } from '@angular/core'
import { HttpClient } from '@angular/common/http'
@Service()
export class ProductsApi {
private readonly http = inject(HttpClient)
findAll() {
return this.http.get<Product[]>('/api/products')
}
}
O comportamento do serviço permanece equivalente. @Service() já significa que a classe será fornecida no injetor raiz, então o providedIn: 'root' deixa de ser necessário.
Segundo a documentação do Angular, esse provider continua sendo singleton, disponível em toda a aplicação e compatível com tree shaking quando não é usado.
Como executar o novo schematic
Rode o comando na raiz do workspace Angular:
ng generate @angular/core:service
Sem opções, o schematic percorre a aplicação inteira. Internamente, ele localiza os arquivos incluídos nos tsconfig de build e teste, usa o analisador do TypeScript para entender as classes e aplica as mudanças nos arquivos elegíveis.
Para migrar apenas uma feature, informe um caminho relativo à raiz do projeto:
ng generate @angular/core:service --path src/app/products
O schematic possui somente a opção path. Isso deixa o comportamento simples: você escolhe todo o projeto ou um diretório específico.
Em um monorepo, começar por uma feature reduz o tamanho do diff e facilita a revisão. Depois de validar esse primeiro grupo, execute o comando nos demais diretórios.
Como @Injectable() é convertido
Nem todo @Injectable registra o serviço no injetor raiz. Uma classe sem providedIn costuma ser adicionada manualmente ao array de providers de uma rota, componente ou configuração da aplicação:
import { Injectable } from '@angular/core'
@Injectable()
export class CheckoutState {}
O schematic preserva essa decisão de escopo:
import { Service } from '@angular/core'
@Service({ autoProvided: false })
export class CheckoutState {}
O autoProvided: false impede o registro automático no injetor raiz. Os providers existentes continuam responsáveis por definir onde a instância será criada:
import { Routes } from '@angular/router'
import { CheckoutState } from './checkout-state'
export const checkoutRoutes: Routes = [
{
path: '',
providers: [CheckoutState],
loadComponent: () => import('./checkout').then((component) => component.Checkout)
}
]
Essa conversão evita uma mudança silenciosa no ciclo de vida do serviço. Um estado que antes pertencia a uma rota não passa a ser global por causa da migração.
Quais classes não são migradas
O schematic foi implementado para deixar casos incompatíveis como estão. Isso permite executar a migração em bases grandes sem forçar uma conversão arriscada.
Serviços com injeção pelo construtor
O @Service aceita dependências por meio da função inject(). Se a classe ainda usa parâmetros no construtor, ela será ignorada:
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
@Injectable({ providedIn: 'root' })
export class OrdersApi {
constructor(private readonly http: HttpClient) {}
}
A análise também considera classes base. Se uma classe herdada usa DI pelo construtor, o schematic mantém @Injectable nas subclasses afetadas.
Para modernizar esses serviços, execute primeiro a migração oficial para inject():
ng generate @angular/core:inject --path src/app
Valide essa alteração e, em seguida, rode a migração para @Service:
ng generate @angular/core:service --path src/app
Separar os dois passos deixa os diffs menores e ajuda a identificar em qual transformação apareceu um possível problema.
providedIn diferente de root
O schematic converte providedIn: 'root'. Outros valores permanecem com @Injectable:
@Injectable({ providedIn: 'platform' })
export class PlatformCache {}
O @Service não oferece o escopo platform. Manter o decorator anterior é o comportamento correto nesse caso.
Configurações avançadas no decorator
Classes com opções como useClass, useValue, useExisting ou useFactory também são preservadas. O @Service possui uma opção factory, mas ela não é uma conversão direta de todas as configurações suportadas por @Injectable.
O schematic evita adivinhar a intenção do provider. Esses serviços pedem uma revisão manual antes de qualquer mudança.
O que acontece com os imports
Quando todas as classes elegíveis de um arquivo são migradas, o schematic remove Injectable do import e adiciona Service:
import { Service } from '@angular/core'
Se o mesmo arquivo possuir uma classe migrada e outra que precisa continuar com @Injectable, os dois imports são mantidos:
import { Injectable, Service } from '@angular/core'
@Service()
export class UserPreferences {}
@Injectable({ providedIn: 'platform' })
export class PlatformPreferences {}
Esse detalhe parece pequeno, mas evita imports quebrados em arquivos que concentram mais de um serviço.
Fluxo recomendado para projetos existentes
Antes de executar o schematic no projeto inteiro, crie uma branch e confirme que o build atual está saudável. A migração fica mais fácil de revisar quando o diff contém somente as alterações do Angular.
Um fluxo prático é:
# 1. Migre a DI pelo construtor, quando necessário
ng generate @angular/core:inject --path src/app
# 2. Valide a primeira transformação
ng build
ng test
# 3. Converta os serviços elegíveis
ng generate @angular/core:service --path src/app
# 4. Revise e valide o resultado
git diff
ng build
ng test
Em seguida, procure pelos serviços que continuaram com @Injectable. Eles não representam uma falha do schematic. Na maioria dos casos, há um motivo técnico para a preservação.
rg "@Injectable" src/app
Revise cada resultado e decida se o serviço precisa mesmo dos recursos de @Injectable ou se ainda falta migrar sua injeção pelo construtor.
Não transforme a migração em uma regra cega
O objetivo do schematic é adotar a API nova onde ela se encaixa. Um projeto Angular 22 pode usar @Service e @Injectable ao mesmo tempo.
Mantenha @Injectable quando a classe precisar de DI pelo construtor, escopo fora do root ou metadados avançados. Use @Service() para serviços raiz que já recebem dependências com inject(). Use @Service({ autoProvided: false }) quando o provider for configurado manualmente.
Também vale revisar o papel de cada serviço. A migração atualiza a declaração da classe, mas não corrige dependências mal posicionadas ou contratos acoplados à infraestrutura. Esse trabalho continua sendo uma decisão de arquitetura.
Conclusão
O comando ng generate @angular/core:service reduz o trabalho repetitivo de modernizar serviços no Angular 22. Ele troca decorators, ajusta imports e preserva o escopo de classes que não eram fornecidas automaticamente.
O maior acerto do schematic está nos casos que ele deixa intactos. Serviços com DI pelo construtor, escopos diferentes de root, herança incompatível ou configurações avançadas continuam com @Injectable para evitar mudanças de comportamento.
Execute a migração por partes, revise o diff e rode build e testes entre as etapas. Assim, a adoção do @Service vira uma atualização previsível, mesmo em projetos antigos.
Referências
Entre na nossa comunidade!
Receba novos posts, novidades do ecossistema Angular e muito mais.
Sobre o autor