---
title: "Estado HTTP sem transformar tudo em Signals"
description: "Um guia técnico para fronteiras de estado HTTP no Angular com HttpClient, RxJS, toSignal e o httpResource agora estável."
deck: "Estado HTTP no Angular fica mais simples quando o time separa valores de UI, derivações, leituras remotas e fluxos assíncronos antes de escolher Signals ou RxJS. [HttpClient](https://angular.dev/guide/http) continua canônico em produção, e [`httpResource`](https://angular.dev/api/common/http/httpResource) agora é [estável desde o Angular 22](https://angular.dev/events/v22), então a pergunta saiu de 'é seguro' para 'onde encaixa.' [`toSignal`](https://angular.dev/api/core/rxjs-interop/toSignal) segue sendo a ponte pra um stream RxJS existente."
author: "André Ramos"
url: "https://andreramos.dev/pt/angular/http-state-without-signals-everywhere/"
lang: "pt-BR"
type: "article"
---

# Estado HTTP sem transformar tudo em Signals

> Estado HTTP no Angular fica mais simples quando o time separa valores de UI, derivações, leituras remotas e fluxos assíncronos antes de escolher Signals ou RxJS. [HttpClient](https://angular.dev/guide/http) continua canônico em produção, e [`httpResource`](https://angular.dev/api/common/http/httpResource) agora é [estável desde o Angular 22](https://angular.dev/events/v22), então a pergunta saiu de 'é seguro' para 'onde encaixa.' [`toSignal`](https://angular.dev/api/core/rxjs-interop/toSignal) segue sendo a ponte pra um stream RxJS existente.

## Classifique o estado antes de escolher a ferramenta

A primeira pergunta de review não é "Signals ou RxJS?" É "que tipo de estado essa feature está carregando?"

Um filtro digitado, um botão derivado, uma leitura HTTP, uma ação de reload, um stream WebSocket e estado de domínio compartilhado são problemas diferentes. Quando a mesma ferramenta tenta resolver todos, o resultado costuma ser uma facade cheia de adaptadores que ninguém quer depurar.

| Formato do estado | Exemplo | Ponto de partida |
|---|---|---|
| Valor local de UI | aba selecionada, linha expandida, filtro digitado | `signal` |
| Derivação síncrona | pode salvar, contagem filtrada, label de exibição | `computed` |
| Leitura remota simples | perfil por id, tela de configurações | `HttpClient` + `AsyncPipe` ou `toSignal` |
| Fluxo assíncrono no tempo | busca, retry, polling, cancelamento | RxJS em uma facade ou store |
| Estado de domínio compartilhado | carrinho, auth, permissões, fluxo em várias telas | Store/facade existente, com Signals nas leituras do componente |

## Mantenha o service de API simples

Preserve primeiro um service de API simples. Ele deve descrever o contrato com o backend, usar `HttpClient` e evitar decidir como todo componente vai consumir estado.

Esse service não fica antigo só porque retorna Observable. Ele é um contrato estável. A modernização normalmente mora no componente ou na facade, onde a UI precisa de valor atual, loading, retry ou cancelamento.

*Service de API simples*

```ts
@Injectable({ providedIn: 'root' })
export class OrdersApi {
  private readonly http = inject(HttpClient);

  listOrders() {
    return this.http.get<Order[]>('/api/orders');
  }
}
```

## Uma facade que mistura ferramentas de propósito

Uma facade de feature pode deixar cada ferramenta cumprir um papel. Um `signal` local registra a intenção de reload. RxJS modela request, loading, erro e replay. `toSignal` entrega um estado atual para o componente ler sem código manual de subscription.

Não esconda o stream RxJS como se ele fosse dívida legada. Ele é o lugar certo para lifecycle de request, fallback e semântica de cache. Signals melhoram o contrato consumido pelo template.

O valor inicial de `refresh` dispara a primeira request, e `reload()` incrementa esse valor depois. O stream interno controla loading para cada request, sem depender de um `startWith` perdido antes do `switchMap`.

Se o produto precisa manter linhas antigas visíveis durante um reload, codifique isso em `OrdersState`; não deixe a UI inferir essa decisão a partir de `shareReplay`.

*Facade de pedidos*

```ts
type OrdersState =
  | { status: 'loading'; data: Order[]; error: null }
  | { status: 'ready'; data: Order[]; error: null }
  | { status: 'empty'; data: Order[]; error: null }
  | { status: 'error'; data: Order[]; error: string };

const loadingState: OrdersState = {
  status: 'loading',
  data: [],
  error: null,
};

@Injectable()
export class OrdersFacade {
  private readonly api = inject(OrdersApi);
  private readonly refresh = signal(0);

  readonly state$ = toObservable(this.refresh).pipe(
    switchMap(() =>
      this.api.listOrders().pipe(
        map((data) => ({
          status: data.length > 0 ? 'ready' : 'empty',
          data,
          error: null,
        }) satisfies OrdersState),
        startWith(loadingState),
        catchError(() => of({
          status: 'error',
          data: [],
          error: 'Failed to load orders',
        } satisfies OrdersState))
      )
    ),
    shareReplay({ bufferSize: 1, refCount: true })
  );

  readonly state = toSignal(this.state$, {
    initialValue: loadingState,
  });

  readonly orders = computed(() => this.state().data);
  readonly isLoading = computed(() => this.state().status === 'loading');
  readonly error = computed(() => this.state().error);

  reload() {
    this.refresh.update((value) => value + 1);
  }
}
```

## O template deve manter todos os estados visíveis

Exemplos fracos pulam justamente os estados que doem em produção: loading inicial, resultado vazio, erro no reload e retry. Alguns branches extras no template custam menos do que uma demo que só funciona no caminho feliz.

O componente não precisa saber se a facade usou RxJS, store ou `toSignal` internamente. Ele precisa de um contrato de estado claro e identidade estável na lista.

O template nomeia o estado atual com `@let`, então cada branch lê de um valor discriminado em vez de repetir chamadas de signal e non-null assertions.

*Contrato de template com estado explícito*

```html
@let state = facade.state();

@switch (state.status) {
  @case ('loading') {
    <app-orders-skeleton />
  }
  @case ('error') {
    <app-inline-error
      [message]="state.error"
      (retry)="facade.reload()"
    />
  }
  @case ('empty') {
    <app-empty-orders />
  }
  @case ('ready') {
    @for (order of state.data; track order.id) {
      <app-order-row [order]="order" />
    }
  }
}
```

## Onde `httpResource` encaixa agora que é estável

`httpResource` encaixa numa leitura estreita porque empacota valor, loading, erro, reload e cancelamento em torno de dependências reativas.

O Angular 22 o tornou estável, então não é mais sobre risco, é sobre encaixe. Ele inicia requests de forma eager quando as dependências permitem, cancela request pendente quando dependências mudam, e é feito pra leituras. Não deveria virar seu default pra mutações como POST ou PUT, que seguem no `HttpClient`.

*Leitura de baixo risco com httpResource*

```ts
export class CustomerPanelComponent {
  readonly customerId = input.required<string>();

  readonly customer = httpResource(() =>
    `/api/customers/${this.customerId()}`
  );

  readonly displayName = computed(() => {
    if (!this.customer.hasValue()) return 'Customer not loaded';
    return this.customer.value().name;
  });
}
```

*Proteja leituras de value antes de renderizar*

```html
@if (customer.hasValue()) {
  <app-customer-card [customer]="customer.value()" />
} @else if (customer.error()) {
  <app-inline-error
    message="Could not load customer"
    (retry)="customer.reload()"
  />
} @else if (customer.isLoading()) {
  <app-customer-skeleton />
}
```

## Regra de produção para apps maduros

Para apps Angular maduros, a regra de produção é firme: mantenha services de API simples, mantenha RxJS onde tempo e cancelamento importam, exponha Signals onde o componente precisa de estado atual e use as APIs de resource agora estáveis nas leituras onde elas merecem o lugar.

Isso entrega Angular moderno ao time sem transformar estado HTTP em outra campanha de reescrita.

## Artefato reaproveitável: Checklist de review para estado HTTP

- Nomeie o formato do estado antes de escolher Signals, RxJS, store ou resource.
- Mantenha services de API focados no contrato com o backend.
- Preserve semântica de loading, vazio, erro, retry e cache.
- Decida se o reload limpa dados antigos ou os mantém visíveis, depois codifique isso no contrato de estado.
- Use `toSignal` nas leituras do componente, não como motivo para apagar todo Observable.
- Use `resource` e `httpResource` em leituras agora que são estáveis no v22; mantenha mutações no `HttpClient`.

## Fontes consultadas

- https://angular.dev/guide/http
- https://angular.dev/api/common/http/httpResource
- https://angular.dev/guide/signals/resource
- https://angular.dev/api/core/rxjs-interop/toSignal
- https://angular.dev/api/core/rxjs-interop/toObservable
- https://angular.dev/guide/signals
- https://angular.dev/roadmap
- https://blog.angular.dev/announcing-angular-v20-b5c9c06cf301

## Leia também

- [Signals sem briga com RxJS](https://andreramos.dev/pt/angular/signals-without-rxjs-war/)
- [NG0950: lendo um input obrigatório antes do Angular preencher](https://andreramos.dev/pt/angular/angular-ng0950-required-input-not-set/)
- [O código que quebra quando Angular fica zoneless](https://andreramos.dev/pt/angular/code-that-breaks-when-angular-goes-zoneless/)
