Identificação
- Duração: 2 horas
- Tipo: teoria aplicada e laboratório
- Entrega: serviço de tarefas compartilhado
- Laboratório:
../exemplos/aula-4.4/index.html
Introdução
O que é um serviço?
Serviço é uma classe criada para reunir código que não pertence exclusivamente à apresentação de um componente. Ele pode guardar estado compartilhado, aplicar regras de negócio, acessar uma API, registrar eventos ou coordenar outras dependências.
Componente: apresenta a interface e recebe interações
Serviço: mantém dados e executa operações reutilizáveis
Na Knowledge AI, TaskList não precisa conhecer todos os detalhes de armazenamento.
Ela pede tarefas ao TaskStore e solicita operações como alternar status.
Serviço é uma API ou servidor?
Não necessariamente. Nesta aula, o serviço é uma classe TypeScript executada no front-end, dentro da aplicação Angular no navegador. Mais adiante, esse serviço usará o cliente HTTP para conversar com a API NestJS.
agora: componente → serviço Angular → estado em memória
mais adiante: componente → serviço Angular → HTTP → API NestJS → MySQL
O serviço Angular não se conecta diretamente ao MySQL e não guarda credenciais do banco no navegador.
Serviço é igual a função utilitária?
Não. Uma função pura, como formatDate(value), pode ser apenas uma função exportada.
Um serviço é útil quando há estado, dependências, ciclo de vida, substituição de
implementação ou uso compartilhado pelo sistema de injeção.
O que é uma dependência?
Dependência é algo de que uma classe precisa para realizar seu trabalho. Se
TaskList usa TaskStore, então TaskStore é uma dependência de TaskList.
export class TaskList {
protected readonly taskStore = inject(TaskStore);
}
A classe declara do que precisa, sem decidir como criar a instância.
O que é injeção de dependência?
Injeção de dependência, ou DI, é o mecanismo que entrega uma dependência pronta a quem a solicita. O Angular mantém injetores que procuram um provider para o token pedido e devolvem a instância correspondente.
componente pede TaskStore
↓
injector procura provider para TaskStore
↓
provider entrega a instância
O que é um injector?
Injector é o objeto do Angular responsável por resolver pedidos. Podemos imaginá-lo como um catálogo: a chave é um token e o valor descreve o que deve ser entregue.
token TaskStore → instância de TaskStore
O token pode ser uma classe ou um InjectionToken. Nesta aula, a própria classe
TaskStore será o token.
O que é um provider?
Provider é a configuração que ensina ao injector como obter uma dependência. Para um serviço comum, o provider pode ser registrado automaticamente na raiz. Em casos específicos, ele pode ser registrado manualmente em uma aplicação, rota ou componente.
Por que não usar new TaskStore()?
Com new, o componente escolhe e cria sua dependência. Isso pode gerar uma instância
diferente em cada lugar, dificulta substituições em testes e ignora o ciclo de vida
controlado pelo Angular.
// Evite dentro do componente
const store = new TaskStore();
// Solicite ao Angular
const store = inject(TaskStore);
Não é que new seja proibido em JavaScript. Ele apenas não é a forma correta de
obter uma dependência gerenciada pelo injector.
O que são @Service() e @Injectable()?
Na documentação do Angular 22, @Service() é a forma moderna e concisa de declarar
um serviço singleton de raiz:
@Service()
export class TaskStore {}
Em Angular 21 ou anterior, e em muitos projetos atuais, você verá:
@Injectable({ providedIn: "root" })
export class TaskStore {}
As duas formas permitem inject(). @Injectable continua necessário para
configurações avançadas e compatibilidade. Este curso mostrará a API atual sem
esconder o padrão encontrado em projetos existentes.
O que significa providedIn: "root"?
Significa que o serviço é fornecido pelo injector raiz da aplicação. Normalmente, isso resulta em uma instância compartilhada por todas as partes que usam aquele provider. O serviço também pode ser removido da compilação se não for usado.
Singleton significa uma única instância para sempre?
Somente dentro do escopo daquele provider. Um provider raiz costuma entregar uma instância para a aplicação. Se o mesmo serviço for fornecido novamente em um componente, aquela parte da árvore recebe outra instância, vinculada ao ciclo de vida do componente.
Como isso entra na Knowledge AI?
Moveremos o array de tarefas e suas operações para TaskStore. Dois componentes
que injetarem o mesmo serviço raiz observarão o mesmo estado. A interface fica focada
em apresentar dados e encaminhar intenções.
Objetivos
Ao final da aula, o aluno deverá conseguir:
- explicar serviço, dependência, injector, provider e token;
- diferenciar serviço Angular, servidor e função utilitária;
- declarar um serviço atual com
@Service(); - reconhecer
@Injectable({ providedIn: "root" }); - solicitar uma dependência com
inject(); - explicar por que evitar
newpara serviços gerenciados; - mover estado e operações de um componente para um serviço;
- expor estado somente para leitura;
- diferenciar provider raiz e provider de componente;
- reconhecer erros comuns de resolução de dependências.
Pré-requisitos
- Aulas 4.1 a 4.3 concluídas;
- classes TypeScript e modificadores de acesso;
- componentes, signals e estado derivado;
- atualização imutável de arrays;
- composição com inputs e outputs.
Pergunta orientadora
Como compartilhar regras e estado sem acoplar cada componente à criação das suas dependências?
Roteiro sugerido
| Etapa | Duração |
|---|---|
| Serviço e responsabilidade | 20 min |
| Dependência, provider e injector | 30 min |
@Service, @Injectable e inject |
25 min |
| Escopos e estado compartilhado | 25 min |
| Laboratório | 15 min |
| Revisão | 5 min |
1. Problema: componente com responsabilidades demais
export class TaskList {
readonly tasks = signal<Task[]>(INITIAL_TASKS);
toggleTask(id: number): void {
// regra de atualização misturada à interface
}
}
Isso funciona em uma tela pequena. Quando dashboard, lista e detalhe precisam das mesmas tarefas, duplicar o estado cria divergências.
2. Extraindo o estado
import { computed, Service, signal } from "@angular/core";
@Service()
export class TaskStore {
private readonly taskState = signal<readonly Task[]>(INITIAL_TASKS);
readonly tasks = this.taskState.asReadonly();
readonly openCount = computed(() =>
this.tasks().filter((task) => task.status !== "done").length,
);
}
O signal gravável fica privado. Consumidores recebem apenas a leitura e operações nomeadas.
3. Serviço não precisa terminar em Service
O nome deve descrever o papel. TaskStore comunica que a classe mantém estado de
tarefas. TaskApi, TaskRepository ou TaskLogger teriam responsabilidades
diferentes. Evite um genérico TaskService que acumule tudo.
4. Criando pelo Angular CLI
ng generate service tasks/task-store
O CLI cria um arquivo próprio. A convenção atual usa nomes concisos; projetos
anteriores podem gerar task-store.service.ts.
5. @Service() no Angular atual
import { Service } from "@angular/core";
@Service()
export class TaskStore {}
No Angular 22, essa forma registra automaticamente um singleton no injector raiz e
suporta dependências obtidas com inject().
6. Forma compatível com versões anteriores
import { Injectable } from "@angular/core";
@Injectable({ providedIn: "root" })
export class TaskStore {}
Use esta forma se a versão instalada ainda não oferece @Service ou se o projeto
mantém essa convenção. Não troque toda uma base apenas por estética.
7. Quando manter @Injectable
@Injectable permanece adequado quando há:
- injeção por parâmetros do construtor;
- escopo diferente de raiz, como
platform; - configurações avançadas de provider;
- necessidade de compatibilidade com Angular anterior;
- convenção consolidada no projeto.
8. Solicitando com inject()
import { Component, inject } from "@angular/core";
import { TaskStore } from "./task-store";
@Component({ /* ... */ })
export class TaskList {
protected readonly taskStore = inject(TaskStore);
}
O guia de estilo atual prefere inject() pela legibilidade e inferência de tipos.
9. O que inject(TaskStore) faz?
- usa
TaskStorecomo token; - começa a busca no contexto de injeção atual;
- procura um provider na hierarquia;
- cria a instância quando necessário;
- reutiliza a instância dentro do mesmo escopo;
- devolve o objeto tipado.
10. Contexto de injeção
inject() não pode ser chamado em qualquer momento e lugar. Ele funciona em um
contexto onde o Angular conhece o injector atual, como inicializadores de campos de
componentes e serviços ou factories de providers.
export class TaskList {
private readonly store = inject(TaskStore); // válido
}
Chamá-lo mais tarde em um clique comum pode gerar erro de contexto.
11. Injeção por construtor
constructor(private readonly taskStore: TaskStore) {}
Essa forma continua suportada com @Injectable. É frequente em projetos anteriores.
Para código novo, seguiremos inject() conforme o guia de estilo atual.
12. Encapsulando escrita
private readonly taskState = signal<readonly Task[]>(INITIAL_TASKS);
readonly tasks = this.taskState.asReadonly();
Se o signal gravável fosse público, qualquer componente poderia chamar set com um
estado inválido. O serviço deve proteger suas invariantes.
13. Operações de domínio
toggle(id: number): void {
this.taskState.update((tasks) =>
tasks.map((task) =>
task.id === id
? { ...task, status: task.status === "done" ? "todo" : "done" }
: task,
),
);
}
O componente diz o que deseja; o serviço controla como o estado muda.
14. Estado derivado no serviço
readonly openCount = computed(() =>
this.tasks().filter((task) => task.status !== "done").length,
);
Como a regra interessa a diferentes telas, centralizá-la evita resultados divergentes.
15. Consumindo no template
<p>{{ taskStore.openCount() }} tarefas abertas</p>
@for (task of taskStore.tasks(); track task.id) {
<app-task-card
[task]="task"
(toggleRequested)="taskStore.toggle($event)"
/>
}
Para uma aplicação maior, o componente pode expor métodos próprios para evitar acoplar todo o template ao serviço. Aqui deixamos a ligação visível para estudo.
16. Raiz compartilhada
Root EnvironmentInjector
└── TaskStore · instância A
├── Dashboard recebe A
└── TaskList recebe A
Quando o dashboard altera a instância A, a lista lê o mesmo estado reativo.
17. Provider no componente
Com um serviço não fornecido automaticamente, ou sobrescrevendo o provider raiz:
@Component({
providers: [TaskStore],
})
export class IsolatedTaskPanel {
private readonly store = inject(TaskStore);
}
Cada instância do componente cria seu próprio escopo e pode receber um TaskStore
isolado.
18. Hierarquia de injetores
componente atual
↓ se não encontrou
componente ancestral
↓ se não encontrou
EnvironmentInjector da aplicação
↓ se não encontrou
erro de provider ausente
O Angular usa o primeiro provider encontrado para aquele token.
19. Token e implementação
Na forma simples, a classe cumpre os dois papéis:
providers: [TaskStore]
É um atalho para:
providers: [{ provide: TaskStore, useClass: TaskStore }]
O token é a chave; useClass descreve a implementação.
20. Interfaces não existem em ambiente de execução
Uma interface TypeScript desaparece depois da compilação e não pode ser usada diretamente como token:
interface TaskGateway {}
// inject(TaskGateway) // não funciona
Para contratos sem classe, usamos InjectionToken<TaskGateway>. Esse recurso será
aprofundado quando houver implementações reais e simuladas.
21. Provider raiz ou local?
| Necessidade | Escopo sugerido |
|---|---|
| estado compartilhado pela aplicação | raiz |
| instância isolada por editor | componente |
| dependência específica de uma área | rota |
| configuração global | aplicação ou token raiz |
Escolha conscientemente. “Singleton” não deve ser uma consequência acidental.
22. Ciclo de vida
Um serviço raiz costuma viver durante a aplicação. Um serviço fornecido pelo componente é destruído com aquela instância do componente. Isso importa para estado, subscriptions e recursos externos.
23. Serviço não é depósito de qualquer código
Evite uma classe gigante com autenticação, tarefas, notificações e formatação. Separe por responsabilidade e área de domínio. A injeção ajuda a compor serviços menores.
24. Serviço pode depender de outro serviço
@Service()
export class TaskAudit {
private readonly clock = inject(Clock);
}
O injector resolve a árvore de dependências. Dependências circulares indicam uma fronteira arquitetural que precisa ser revista.
25. Testabilidade
Quando o componente solicita um token, um teste pode fornecer outra implementação. Isso permite isolar a interface sem acessar rede ou banco. Não significa criar mocks para tudo; substitua fronteiras externas ou comportamentos difíceis de controlar.
26. Laboratório guiado
Abra o mapa de serviço e injetores.
Etapa 1 — Use o provider raiz
Altere uma tarefa no painel A. Observe o painel B lendo a mesma instância.
Etapa 2 — Observe a resolução
O registro mostra o pedido, a busca do token e a instância entregue.
Etapa 3 — Ative instâncias locais
Compare dois componentes com providers próprios. Uma alteração deixa de aparecer no outro painel.
Etapa 4 — Examine a fonte
Alterne entre serviço, componente e escopo do provider.
27. Sobre o laboratório estático
O laboratório reproduz o comportamento para abrir sem instalar Angular. A pasta
src/app contém a implementação Angular equivalente com @Service(), inject() e
signals. A simulação serve para observar identidade e escopo; não implementa o
injector real do framework.
28. Erros comuns
Esquecer o provider
Se o injector não encontra o token na hierarquia, ocorre erro de resolução.
Usar new no componente
O Angular perde controle sobre criação, compartilhamento e substituição.
Tornar o signal gravável público
Qualquer consumidor pode quebrar as regras do estado.
Achar que todo serviço é global
O escopo depende de onde o provider foi registrado.
Usar inject() fora de contexto
A função precisa de um contexto de injeção ativo.
Colocar todo código em serviços
Interação visual pertence ao componente; uma transformação pura pode ser uma função.
Confundir serviço Angular com back-end
O código continua no navegador e não deve receber segredos de infraestrutura.
29. Boas práticas
- escolha nomes que expressem responsabilidade;
- mantenha um conceito principal por arquivo;
- prefira
inject()em código Angular novo; - preserve
@Injectablequando a versão ou configuração exigir; - mantenha estado gravável privado;
- exponha leitura e operações explícitas;
- registre o provider no menor escopo correto;
- evite dependências circulares;
- mantenha componentes focados na interface;
- nunca conecte o front-end diretamente ao MySQL.
30. Exercícios
Exercício 1 — Extração
Mova uma lista de usuários de um componente para UserStore.
Exercício 2 — Leitura protegida
Exponha um signal somente para leitura e uma operação add.
Exercício 3 — Injeção
Use inject(UserStore) em dois componentes.
Exercício 4 — Escopo
Explique a diferença entre provider raiz e providers: [UserStore] no componente.
Exercício 5 — Decisão
Classifique formatação de moeda, estado de sessão e clique de expansão como função, serviço ou responsabilidade do componente.
31. Desafio
Crie um serviço de tarefas que:
- seja fornecido na raiz;
- mantenha o signal gravável privado;
- exponha tarefas somente para leitura;
- calcule totais com
computed; - possua operações de adicionar, alternar e remover;
- seja injetado com
inject(); - seja consumido por dois componentes;
- demonstre que ambos recebem a mesma instância;
- não contenha código visual;
- não acesse MySQL diretamente.
32. Lista de verificação de conclusão
- Explico o que é um serviço Angular.
- Diferencio serviço front-end e servidor.
- Identifico uma dependência.
- Explico injector, provider e token.
- Reconheço
@Service()e@Injectable(). - Uso
inject()em contexto válido. - Evito criar serviços gerenciados com
new. - Encapsulo a escrita do estado.
- Diferencio provider raiz e local.
- Sei que interfaces não servem diretamente como tokens.
33. Critérios de avaliação
| Critério | Pontos |
|---|---|
| Separação de responsabilidades | 20 |
| Declaração e injeção do serviço | 25 |
| Encapsulamento do estado | 20 |
| Entendimento de provider e escopo | 20 |
| Clareza, tipos e acessibilidade | 15 |
| Total | 100 |
Resumo
Nesta aula, aprendemos que:
- serviço reúne estado ou comportamento reutilizável fora da apresentação;
- dependência é algo de que uma classe precisa;
- provider associa um token ao valor entregue;
- injector resolve pedidos pela hierarquia;
@Service()é o atalho moderno para singleton raiz no Angular 22;@Injectable({ providedIn: "root" })continua válido e compatível;inject()é a forma preferida em código novo;- provider raiz compartilha uma instância dentro daquele escopo;
- provider de componente pode criar instâncias isoladas;
- escrita privada e operações nomeadas protegem o estado.
Próxima aula
Na Aula 4.5, criaremos rotas, layouts e navegação para transformar os componentes em uma aplicação com múltiplas páginas.