Angular 20 em 2026: Resource API, httpResource e perguntas de entrevista

O Angular 20 introduz o httpResource e estabiliza a Resource API para a busca de dados baseada em signals. Um tutorial prático sobre resource(), rxResource(), httpResource(), a validação com Zod e as perguntas de entrevista mais comuns.

Tutorial do Angular 20 Resource API e httpResource para a busca reativa de dados com signals

O Angular 20 coloca a Resource API e o httpResource no centro da busca de dados baseada em signals. Essas APIs experimentais substituem os verbosos padrões de subscrição do HttpClient por primitivas reativas que acompanham automaticamente os estados de carregamento, erro e resolução.

O que muda no Angular 20

A Resource API renomeia request para params e loader para stream (no caso do rxResource). Os valores de status agora são literais de string ('idle', 'loading', 'resolved', 'error', 'reloading', 'local') em vez de enumerações numéricas. O httpResource é construído sobre o HttpClient e oferece suporte nativo a interceptors e à validação com Zod.

As três variantes de Resource no Angular 20

O Angular 20 oferece três maneiras de carregar dados assíncronos como signals. Cada uma atende a um caso de uso diferente, mas todas compartilham o mesmo modelo reativo: declarar dependências, definir um loader e consumir o resultado por meio de signals.

  • resource() funciona com Promises. Ideal ao usar fetch() ou qualquer API baseada em promessas.
  • rxResource() funciona com Observables. A escolha certa quando operadores RxJS como debounceTime, retry ou switchMap são necessários.
  • httpResource() encapsula diretamente o HttpClient do Angular. Interceptors, utilitários de teste e validação de schema funcionam sem configuração adicional.

A diferença essencial entre o httpResource e os outros dois: o httpResource usa o HttpClient por baixo dos panos, o que significa que os interceptors existentes continuam funcionando. A API resource() original ignorava completamente o HttpClient, um ponto de atrito importante no Angular 19.

Construindo um perfil de usuário com resource()

A função resource() aceita uma função de cálculo params e uma função loader. Quando os signals dentro de params mudam, o loader é reexecutado automaticamente.

user-profile.component.tstypescript
import { Component, signal, resource } from '@angular/core';

interface User {
  id: number;
  name: string;
  email: string;
}

@Component({
  selector: 'app-user-profile',
  template: `
    @if (userResource.hasValue()) {
      <h2>{{ userResource.value().name }}</h2>
      <p>{{ userResource.value().email }}</p>
    } @else if (userResource.isLoading()) {
      <p>Loading profile...</p>
    } @else if (userResource.error()) {
      <p>Failed to load user</p>
    }
  `,
})
export class UserProfileComponent {
  userId = signal(1);

  // params produces the reactive dependency
  // loader receives it and returns a Promise
  userResource = resource<User, number>({
    params: () => this.userId(),
    loader: async ({ params: id, abortSignal }) => {
      const res = await fetch(`/api/users/${id}`, { signal: abortSignal });
      return res.json();
    },
  });

  loadUser(id: number) {
    this.userId.set(id); // triggers automatic refetch
  }
}

O parâmetro abortSignal permite que o Angular cancele requisições em andamento quando userId muda antes de a requisição anterior terminar. Isso evita condições de corrida sem gerenciamento manual de subscrições.

Busca reativa de dados com httpResource

O httpResource elimina o código repetitivo ao combinar a declaração da URL e a execução HTTP em uma única chamada. Ele retorna um HttpResourceRef que expõe value, isLoading, error, status e headers como signals.

product-list.component.tstypescript
import { Component, signal, computed } from '@angular/core';
import { httpResource } from '@angular/common/http';

interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
}

@Component({
  selector: 'app-product-list',
  template: `
    @if (products.hasValue()) {
      @for (product of products.value(); track product.id) {
        <div class="product-card">
          <h3>{{ product.name }}</h3>
          <span>{{ product.price | currency }}</span>
        </div>
      }
    } @else if (products.isLoading()) {
      <p>Loading products...</p>
    }
  `,
})
export class ProductListComponent {
  category = signal('electronics');

  // httpResource re-fetches whenever category() changes
  products = httpResource<Product[]>(() => ({
    url: '/api/products',
    params: { category: this.category() },
  }));

  filterByCategory(cat: string) {
    this.category.set(cat); // pending request is cancelled, new one starts
  }
}

Vários detalhes importam aqui. A função passada ao httpResource retorna um objeto de configuração de requisição. O Angular acompanha as leituras de signals dentro dessa função, então alterar category dispara uma nova requisição GET. Se já houver uma requisição em andamento, o Angular a cancela antes de iniciar a nova.

httpResource é apenas para leituras

O httpResource foi projetado para a busca de dados (requisições GET). Usá-lo em operações POST, PUT ou DELETE é inseguro, porque o cancelamento da requisição poderia abortar uma mutação no meio do caminho. Para operações de escrita, usar o HttpClient diretamente ou encapsular as mutações em um método de serviço.

Validação de schema com Zod e httpResource

As respostas de serviços externos podem divergir do formato esperado. A opção parse do httpResource integra bibliotecas de validação de schema como o Zod para detectar incompatibilidades em tempo de execução, em vez de propagar silenciosamente dados corrompidos.

order.component.tstypescript
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
import { z } from 'zod';

// Define the expected shape with Zod
const OrderSchema = z.object({
  id: z.number(),
  status: z.enum(['pending', 'shipped', 'delivered', 'cancelled']),
  total: z.number().positive(),
  items: z.array(z.object({
    productId: z.number(),
    quantity: z.number().int().positive(),
    unitPrice: z.number().positive(),
  })),
  createdAt: z.string().datetime(),
});

type Order = z.infer<typeof OrderSchema>;

@Component({
  selector: 'app-order',
  template: `
    @if (order.hasValue()) {
      <h2>Order #{{ order.value().id }}</h2>
      <p>Status: {{ order.value().status }}</p>
      <p>Total: {{ order.value().total | currency }}</p>
    } @else if (order.error()) {
      <p>Invalid order data received</p>
    }
  `,
})
export class OrderComponent {
  orderId = signal(42);

  // parse validates the response before exposing it as a signal
  order = httpResource<Order>(
    () => `/api/orders/${this.orderId()}`,
    { parse: OrderSchema.parse }
  );
}

Quando a API retorna dados que não correspondem ao OrderSchema, a resource passa ao status 'error'. O tipo de retorno da função parse também determina o tipo TypeScript de value(), então as definições de schema cumprem um papel duplo: validadores em tempo de execução e geradores de tipos.

rxResource com stream e params no Angular 20

O Angular 20 renomeia loader para stream e request para params no rxResource. Essa mudança alinha a nomenclatura à semântica de streaming que o rxResource oferece. A função stream recebe um contexto de Observable e deve retornar um Observable.

search.component.tstypescript
import { Component, signal } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { debounceTime, switchMap } from 'rxjs';

interface SearchResult {
  id: number;
  title: string;
  excerpt: string;
}

@Component({
  selector: 'app-search',
  template: `
    <input (input)="query.set($any($event.target).value)" placeholder="Search..." />
    @if (results.isLoading()) {
      <p>Searching...</p>
    }
    @if (results.hasValue()) {
      @for (item of results.value(); track item.id) {
        <div>{{ item.title }}</div>
      }
    }
  `,
})
export class SearchComponent {
  private http = inject(HttpClient);
  query = signal('');

  // params (was "request") provides the reactive input
  // stream (was "loader") returns an Observable
  results = rxResource<SearchResult[], string>({
    params: () => this.query(),
    stream: ({ params: q }) =>
      this.http.get<SearchResult[]>('/api/search', {
        params: { q },
      }),
  });
}

Ao contrário do httpResource, o rxResource oferece controle total sobre o pipeline de Observable. Operadores como debounceTime ou retry podem ser encadeados dentro de stream. No entanto, o httpResource resolve o caso mais comum (uma única requisição GET) com menos código.

Pronto para mandar bem nas entrevistas de Angular?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Acompanhamento de status: literais de string substituem as enumerações

O Angular 20 transforma o ResourceStatus de uma enumeração numérica em um tipo de união de strings. Os seis valores possíveis oferecem uma visão detalhada do ciclo de vida da resource:

| Status | Significado | |---|---| | 'idle' | params retornou undefined — nenhuma requisição emitida | | 'loading' | Primeira requisição em andamento | | 'reloading' | Requisição subsequente após um sucesso anterior | | 'resolved' | Dados disponíveis em value() | | 'error' | A requisição falhou — error() contém o erro | | 'local' | Valor definido localmente via .set() ou .update() |

status-demo.component.tstypescript
import { Component, signal, resource } from '@angular/core';

@Component({
  selector: 'app-status-demo',
  template: `
    <p>Status: {{ data.status() }}</p>
    @switch (data.status()) {
      @case ('loading') { <spinner /> }
      @case ('reloading') { <subtle-spinner /> }
      @case ('resolved') { <data-table [rows]="data.value()" /> }
      @case ('error') { <error-banner [error]="data.error()" /> }
      @case ('idle') { <p>Select a filter to load data</p> }
    }
  `,
})
export class StatusDemoComponent {
  filter = signal<string | undefined>(undefined);

  data = resource({
    params: () => this.filter(),
    loader: async ({ params: f, abortSignal }) => {
      const res = await fetch(`/api/data?filter=${f}`, { signal: abortSignal });
      return res.json();
    },
  });
}

Retornar undefined a partir de params define o status como 'idle' e impede que o loader seja executado. Esse padrão funciona bem para a busca condicional de dados: exibir um aviso até que o usuário forneça uma entrada e, então, carregar os dados.

Acessar value() no estado de erro

Desde o Angular 20, chamar value() em uma resource com status 'error' lança uma exceção em tempo de execução. Sempre proteger as leituras com hasValue() ou verificar status() antes de acessar value(). Trata-se de uma mudança incompatível em relação ao Angular 19, em que value() retornava undefined em caso de erro.

Perguntas de entrevista sobre httpResource no Angular 20

A Resource API está se tornando um tema padrão das perguntas de entrevista de Angular. Estas perguntas avaliam uma compreensão real, além da familiaridade superficial com a API.

P: Qual problema o httpResource resolve que o resource() não resolve?

O resource() usa fetch() ou qualquer loader baseado em promessas, ignorando o HttpClient do Angular. Isso significa que os interceptors (para tokens de autenticação, logging, tratamento de erros) não se aplicam. O httpResource usa o HttpClient internamente, então interceptors, utilitários de teste (HttpTestingController) e recursos como withFetch() funcionam sem configuração adicional.

P: Quando o rxResource deve ser preferido em relação ao httpResource?

O rxResource oferece controle total sobre o Observable por meio da sua função stream. Vale escolhê-lo quando o pipeline de dados exige operadores RxJS: aplicar debounce à entrada de busca, repetir requisições falhas com backoff exponencial ou combinar vários fluxos com combineLatest. Para requisições GET simples, o httpResource exige menos código.

P: Como o Angular lida com requisições concorrentes quando um signal muda rapidamente?

As três variantes de resource cancelam as requisições em andamento quando params produz um novo valor. Para o httpResource e o resource(), o AbortSignal cancela o fetch subjacente. Para o rxResource, o Angular cancela a subscrição do Observable anterior. Isso impede que respostas obsoletas sobrescrevam dados atualizados.

P: Qual é o propósito do status 'local'?

Chamar .set() ou .update() em uma resource altera seu valor localmente sem disparar o loader. O status passa a 'local', indicando que o valor atual não veio do servidor. Isso facilita as atualizações otimistas da interface: a UI reflete a mudança imediatamente enquanto uma requisição de mutação separada é executada.

P: Como funciona a integração do Zod com o httpResource?

A opção parse aceita qualquer função com a assinatura (data: unknown) => T. Quando a resposta HTTP chega, o httpResource passa o JSON analisado por parse antes de definir value(). Se parse lançar uma exceção (por exemplo, ZodError), a resource passa ao status 'error'. O tipo de retorno de parse determina o tipo TypeScript de value(), oferecendo segurança em tempo de execução e tipos em tempo de compilação a partir de uma única definição de schema.

Para aprofundar nos signals do Angular e em como eles se integram ao framework de modo mais amplo, o módulo de signals cobre os computed signals, os effects e o modelo de reatividade.

Migrando de subscrições HttpClient para httpResource

As aplicações Angular existentes geralmente buscam dados com subscrições de HttpClient dentro de ngOnInit, ou usam o AsyncPipe com Observables. A migração para o httpResource segue um padrão previsível:

typescript
// BEFORE: manual subscription in ngOnInit
@Component({ /* ... */ })
export class BeforeComponent implements OnInit, OnDestroy {
  private http = inject(HttpClient);
  private destroy$ = new Subject<void>();
  users: User[] = [];
  loading = false;
  error: string | null = null;

  ngOnInit() {
    this.loading = true;
    this.http.get<User[]>('/api/users')
      .pipe(takeUntil(this.destroy$))
      .subscribe({
        next: (data) => { this.users = data; this.loading = false; },
        error: (err) => { this.error = err.message; this.loading = false; },
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

// AFTER: httpResource handles lifecycle automatically
@Component({ /* ... */ })
export class AfterComponent {
  users = httpResource<User[]>(() => '/api/users');
  // No ngOnInit, no Subject, no manual unsubscribe
  // Template uses users.value(), users.isLoading(), users.error()
}

A migração elimina o código repetitivo de gerenciamento do ciclo de vida. A limpeza das subscrições ocorre automaticamente quando o componente é destruído. Os estados de carregamento e erro estão integrados à resource: não são necessárias flags booleanas separadas.

Para aplicações que já usam componentes standalone, a migração é direta: substituir a injeção do HttpClient e a lógica de subscrição por uma única declaração httpResource.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Conclusão

  • O httpResource substitui as subscrições manuais ao HttpClient por uma única declaração reativa que gerencia automaticamente o carregamento, o erro e o cancelamento
  • Usar resource() para APIs baseadas em promessas, rxResource() para pipelines de Observable com operadores RxJS e httpResource() para chamadas HTTP padrão com suporte a interceptors
  • O Angular 20 renomeia request para params e loader para stream no rxResource — atualize o código existente de acordo
  • Os valores de status agora são literais de string ('idle', 'loading', 'resolved', 'error', 'reloading', 'local'), substituindo as enumerações numéricas do Angular 19
  • A opção parse do httpResource integra Zod ou Valibot para uma validação de schema em tempo de execução que também conduz os tipos TypeScript
  • Chamar value() em uma resource no estado de erro lança uma exceção no Angular 20 — sempre proteja com hasValue() ou verifique status() primeiro
  • Todas as variantes de resource cancelam automaticamente as requisições em andamento quando as dependências mudam, evitando condições de corrida sem lógica de cancelamento manual

Tags

#angular
#angular-20
#resource-api
#httpresource
#signals
#typescript
#tutorial

Compartilhar

Artigos relacionados