Prefetch de dados por rota no Angular com Standalone Components e Signals
Por John Pires · 16 de março de 2026

Prefetch de dados por rota no Angular com Standalone Components e Signals
Introducao ao problema
O que é prefetch e por que importa
Prefetch significa antecipar a busca de dados antes que o usuário precise deles. Em aplicações SPA, navegar para uma rota muitas vezes dispara requisições HTTP que atrasam a renderização do conteúdo. Para quem está começando, imagine que você vai abrir uma porta e precisa primeiro trazer as chaves de outro cômodo: a porta só vai abrir quando as chaves chegarem. Prefetch busca as chaves antes mesmo de o usuário tentar abrir a porta, reduzindo latência aparente e evitando telas vazias ou carregadores longos.
Problema prático em rotas
Em Angular, navegação para rotas com componentes que precisam de dados costuma resultar em múltiplas abordagens: carregar o componente, o componente faz subscribe ao serviço, exibe um loader, depois renderiza. Isso causa flicker, chamadas repetidas e estado fragmentado quando o usuário volta e avança. Para iniciantes, a dificuldade é coordenar quando buscar dados, onde guardar (component vs serviço) e como garantir que a navegação aguarda os dados quando necessário.
Existem alternativas nativas (resolvers de rota), padrões de carregamento (preloadStrategy para módulos), e padrões manuais (guards, interceptors, serviços com cache). Neste artigo investigamos as alternativas e apresentamos uma solução moderna usando Standalone Components + Signals, mantendo código simples e previsível.
Pre-requisitos e setup
O que você precisa ter instalado
Para seguir o tutorial este artigo assume que você tem:
Node.js >= 18
Angular CLI >= 16 (suporta signals e standalone components)
Conhecimento básico de TypeScript, serviços e rotas no Angular
Criação do projeto e módulos mínimos
Crie um projeto novo para seguir passo a passo, com suporte a standalone components:
ng new prefetch-signals --routing --standalone --skip-testsApós criar, entre na pasta e instale dependências básicas (opcional):
cd prefetch-signals
ng add @angular/localize
# HttpClient já vem, mas confirme:
npm install rxjs
Configure o Router no main.ts usando rotas com componentes standalone. Vamos usar HttpClientModule importado no bootstrap para permitir chamadas HTTP em serviços.
Tutorial guiado passo a passo
Estratégia escolhida: resolver + store com signals
Alternativas:
Resolver que retorna dados brutos (Promise/Observable) - Angular aguarda; simples, mas o componente ainda precisa expor esse dado.
PreloadStrategy para módulos - funciona para módulos lazy, não ideal para dados por rota.
Serviço central com cache - flexível; podemos combiná-lo com resolver para popular o cache antes da navegação.
Escolhemos combinar um ResolveFn (resolver) que popula um PrefetchStore baseado em Signals. Assim, a navegação aguarda o dado inicial e o componente consome o signal para reatividade nativa sem subscribes manuais.
Passo 1: ApiService (simula backend)
Crie um serviço simples que retorna observables do HttpClient (ou simula via of()). Este serviço é a fonte de dados.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private http: HttpClient) {}
// Simulação: em produção você usaria this.http.get(`/api/users/${id}`)
getUser(id: string): Observable<{ id: string; name: string }> {
const fake = { id, name: `User ${id}` };
return of(fake);
}
}
O que faz: expõe getUser que retorna Observable. O componente/resolver consome esse observable. Depois: resolver vai forçar a chamada e populará o store.
Passo 2: PrefetchStore com Signals
O store mantém um cache reativo via signals. O componente acessa o signal e recebe atualizações automáticas.
import { Injectable, signal, computed } from '@angular/core';
import { ApiService } from './api.service';
import { firstValueFrom } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class PrefetchStore {
private cache = signal>({});
constructor(private api: ApiService) {}
// Atualiza cache com a resposta
set(key: string, value: any) {
this.cache.update(c => ({ ...c, [key]: value }));
}
// Retorna um signal computado para leitura reativa
getSignal(key: string) {
return computed(() => this.cache()[key] as T | undefined);
}
// Busca e popula cache. Retorna a resposta como Promise.
async fetchUser(id: string) {
const data = await firstValueFrom(this.api.getUser(id));
this.set(`user:${id}`, data);
return data;
}
}
O que faz: fornece métodos para buscar e expor dados como signals. Observação: signals são funções que devolvem valores reativos; computed retorna um sinal derivado.
Passo 3: Resolver que usa o store
Usamos ResolveFn (função resolvable) para simplificar a criação de resolvers curtos. A função injeta o PrefetchStore e espera o fetch terminar.
import { ResolveFn } from '@angular/router';
import { inject } from '@angular/core';
import { PrefetchStore } from './prefetch-store.service';
export const userResolver: ResolveFn = async (route) => {
const store = inject(PrefetchStore);
const id = route.params['id'];
return store.fetchUser(id);
};
O que faz: antes de completar a navegação, chama fetchUser do store. Como resultado, quando o componente for inicializado, os dados já estarão no cache e expostos por signals.
Passo 4: Rota com componente standalone
Defina a rota para usar o resolver. O componente será standalone e consumirá a signal do PrefetchStore.
import { provideRouter, Routes } from '@angular/router';
import { UserComponent } from './user.component';
import { userResolver } from './user.resolver';
const routes: Routes = [
{ path: 'user/:id', component: UserComponent, resolve: { user: userResolver } },
{ path: '', redirectTo: 'user/1', pathMatch: 'full' }
];
export const appRouter = provideRouter(routes);
O que faz: registra as rotas e associa o resolver. Angular aguarda o resolver antes de completar navegação para /user/:id.
Passo 5: Componente standalone consumindo o signal
Crie UserComponent como standalone. Use inject para obter PrefetchStore e a signal gerada por getSignal.
import { Component, inject } from '@angular/core';
import { PrefetchStore } from './prefetch-store.service';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user',
standalone: true,
template:
{{ user().name }}
ID: {{ user().id }} Carregando user...
,
imports: []
})
export class UserComponent {
private route = inject(ActivatedRoute);
private store = inject(PrefetchStore);
private id = this.route.snapshot.params['id'];
user = this.store.getSignal(`user:${this.id}`);
}
O que faz: o componente lê a signal correspondente ao usuário requisitado e renderiza imediatamente sem subscribes ou async pipe. Se o resolver já populou o cache, user() já tem valor ao inicializar. Caso contrário, o signal será undefined até fetch completar.
Resultado final funcionando
Fluxo de navegação e comportamento observado
Fluxo:
Navegar para /user/:id ativa o resolver.
Resolver chama PrefetchStore.fetchUser(id), que faz a requisição e atualiza o signal interno.
Angular aguarda a Promise do resolver; quando completa, o componente é ativado.
Componente usa getSignal para ler o dado já em cache; renderização imediata.
O ganho: removemos a necessidade de loader no componente e evitamos refetch duplicado. O estado fica centralizado no PrefetchStore, tornando fácil reaproveitar dados em outras rotas ou componentes.
Testando localmente
1) Rode ng serve. 2) Acesse /user/1 e veja que o nome aparece sem flicker (em ambiente real, depende do tempo da requisição). 3) Acesse /user/2; o resolver buscará user:2. 4) Volte para /user/1 — se já estiver no cache, não haverá nova chamada HTTP.
Se estiver usando simulação com of(), as respostas são imediatas — para testar verdadeiramente a espera, simule atraso com delay() do RxJS.
Erros comuns, armadilhas e dicas práticas
Erro: resolver não foi chamado
Verifique se a rota realmente usa a propriedade resolve e se o ResolveFn está exportado/importado corretamente. Em rotas child ou lazy, preste atenção ao nível onde o resolver foi declarado.
Erro: sinal undefined no componente
Se o componente inicializa antes do resolver (por exemplo, navegação imperativa que não espera), ou se você lê a signal usando um id diferente, o valor pode estar undefined. Use nomes de chaves consistentes (ex: `user:${id}`) e prefira route.snapshot.params no componente para derivar a chave correta.
Dicas práticas
Use firstValueFrom/lastValueFrom para converter Observables em Promises dentro do store ou resolver.
Evite chamar a API novamente se o cache já tem dado — verifique cache antes de fetchar.
Prefira chaves únicas (namespace) no cache para evitar colisões de dados.
Para listas grandes, prefetch com paginação e estratégia de invalidação.
Desafio para o leitor praticar
Tarefas propostas
Implemente as seguintes variações no seu projeto:
Adicionar cache validation: se o dado estiver na cache por mais de X minutos, refetch automaticamente.
Implementar um mecanismo para pré-carregar dados de rotas relacionadas (por exemplo, ao carregar user, já prefetch os posts do user).
Transformar PrefetchStore em um provider de rota (route providers) para criar escopos de cache por rota.
Como verificar se você acertou
Monitore as requisições HTTP (Network tab). Em casos de cache corretamente implementado, navegar para rotas já visitadas não dispara novas requisições. Use console.logs no store.fetchUser para ver quando a requisição é feita vs quando o dado é lido do cache.
Conclusao pratica
Resumo técnico
Combinar resolvers com um PrefetchStore baseado em Signals traz dois benefícios claros: (1) a navegação pode aguardar dados essenciais antes de ativar o componente; (2) componentes consomem dados reativamente via signal sem subscribes manuais. Isso reduz complexidade, evita múltiplas chamadas e melhora UX.
Boas práticas finais
Mantenha a lógica de fetch e cache fora do componente. Use padrões previsíveis (chaves namespaced, verificação de cache) e aproveite a composição do Angular moderno (standalone components, ResolveFn, inject). Para iniciantes, a proposta facilita entender fluxo: resolver faz o trabalho pesado; componente apenas consome o resultado.
Se seguir o passo a passo, você terá uma base sólida para evoluir para pré-carregamento de módulos, estratégias de validação e até SSR. Experimente as variantes do desafio para fixar o conceito e ganhar prática com Signals e DI em rotas.
Boa prática: escreva testes unitários para o PrefetchStore e para o ResolveFn, simulando HttpClient com HttpTestingController para garantir que o comportamento de cache e fetch é o esperado.
Este padrão é especialmente útil em aplicações com muitas rotas que dependem de dados externos e em cenários onde a percepção de velocidade pelo usuário é crítica.
Fim do artigo — aplique, teste e adapte para seu domínio.
Assuntos: Angular, signals, standalone-components, routing, rxjs
Leia também
Prefetch inteligente de rotas com Signals e RxJS em Standalone Components
Prefetch inteligente para Angular: combine Signals para estado e RxJS para orquestração de intents (hover, focus, intersection). Exemplo prático com…
Providers com escopo de rota em Angular standalone: signals, ciclo de vida e limpeza
Guia prático para usar providers com escopo de rota em Angular standalone: exemplos com Signals, factories que leem params de rota, limpeza via onCleanup e…
Signals + RxJS em rotas lazy com Standalone Components e Route Providers: modo errado vs modo certo
Padrões práticos para combinar Signals e RxJS em rotas lazy com standalone components e route providers: comparativo, erros comuns e implementação correta…
Wizard multi-step com rotas filhas, Signals e Standalone Components
Tutorial prático e direto para criar um wizard multi-step em Angular: rotas filhas para cada etapa, gerenciamento de estado com Signals e componentes…
Autenticação reativa com Guards, Signals e Standalone Components em Angular (passo a passo)
Tutorial prático para iniciantes: implemente autenticação reativa em Angular usando signals, standalone components e guards. Passo a passo com exemplos…
Migrando BehaviorSubject para Signal em formulários Angular — passo a passo
Guia prático para migrar BehaviorSubject para Signal em formulários Angular: exemplos errados e corretos, patterns com standalone components, DI e routing, e…