Angular 19.2: Efetuando requisições HTTP com httpResource

A versão 19.2 do Angular introduziu um novo recurso experimental chamado httpResource, que visa simplificar a forma como os desenvolvedores lidam com requisições HTTP em aplicações Angular. Veja como o httpResource se diferencia do HttpClient

Angular 19.2: Efetuando requisições HTTP com httpResource
Angular 19.2: efetuando requisições http com httpResource

Lançado na versão 19.2 do Angular, o httpResource, ainda em fase experimental, introduz uma nova maneira de lidar com requisições HTTP, oferecendo uma abordagem mais integrada com o Angular Signals em comparação ao tradicional HttpClient.

De certa forma, o httpResource já era esperado, seguindo a tendência de adaptação dos recursos do Angular Signals. Da mesma forma que o @Input evoluiu para input@Output para output e @ViewChild para viewChild.

💡
Para aqueles que não estão familiarizados com signals, são um sistema de reatividade que rastreia as dependências de valores e notifica automaticamente quando esses valores mudam.
Introdução ao Angular Signals
Ainda em developer preview, Angular Signals é um novo recurso que pode mudar o atual sistema de detecção de mudanças do Angular. Criando “pacote” ao redor de um valor e pode notificar consumidores interessados nas mudanças do valor. Conheça os motivadores e veja alguns exemplos neste post

httpResource permite que você defina requisições HTTP de forma declarativa, integrando-as diretamente ao fluxo de dados reativo da sua aplicação. Isso simplifica o gerenciamento de estados de requisição, como carregamento, erro e sucesso, e facilita a atualização da interface do usuário com base nos resultados das requisições. O httpResource, se parece bastante com o resource, que foi já foi citado por aqui:

Utilizando o Resource API do Angular 19 em um pequeno buscador
A API Resource, lançada no Angular 19, integra-se aos Signals, permitindo que componentes reajam automaticamente a mudanças nos dados. Isso elimina a necessidade de gerenciar assinaturas de observables ou usar outras soluções complexas. Veja neste texto um exemplo prático aplicado a um buscador.

Exemplo do httpResource

Para testar esse novo recurso, primeiro criei o projeto Angular na versão correta conforme o script abaixo:

# Criando o projeto com Angular 19.2
# Versão do Node.js: v22.14.0
npx @angular/[email protected] new teste-http-resource

✔ Which stylesheet format would you like to use? CSS             [ https://developer.mozilla.org/docs/Web/CSS                     ]
✔ Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? No

Criando o projeto

Após a criação do projeto incluí o provideHttpClient() no appConfig:

import {
  ApplicationConfig,
  provideZoneChangeDetection
} from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
  ],
};

app.config.ts

Então modifiquei o AppComponent para consumir uma API local com dois endpoints:

  • GET /languages – Retorna uma lista paginada de linguagens de programação.
  • POST /languages – Adiciona uma nova linguagem à lista.

A seguir, a implementação do teste com httpResource, realizando requisições GET e POST.

import {
  HttpErrorResponse,
  HttpHeaders,
  httpResource,
  HttpResourceRef,
} from '@angular/common/http';
import {
  Component,
  computed,
  effect,
  signal,
} from '@angular/core';
import { RouterOutlet } from '@angular/router';

// Tipos para resposta da API de linguagens
type LanguagesResponse = {
  page: number;
  limit: number;
  total: number;
  totalPages: number;
  data: string[];
};

// Tipo para requisição de criação de linguagem:
// POST /languages
type CreateLanguageRequest = {
  name: string;
};

// Valor padrão para resposta GET /languages
const DEFAULT_LANGUAGES_RESPONSE: LanguagesResponse = {
  total: 0,
  limit: 5,
  page: 1,
  totalPages: 0,
  data: [],
};

@Component({
  selector: 'app-root',
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css',
  standalone: true,
})
export class AppComponent {
  readonly currentPage = signal<number>(1);
  readonly limit = signal<number>(5);
  readonly customHeader = signal<string>('teste');
  readonly newLanguage =
    signal<CreateLanguageRequest | null>(null);

  // Recurso HTTP para buscar linguagens
  readonly languagesResource =
    httpResource<LanguagesResponse>(
      () => ({
        url: `http://localhost:3000/languages`,
        method: 'GET',
        // Query strings
        params: {
          page: this.currentPage(),
          limit: this.limit(),
        },
        // Header customizado para testes:
        headers: new HttpHeaders({
          'x-cabecalho': this.customHeader(),
        }),
      }),
      {
        defaultValue: DEFAULT_LANGUAGES_RESPONSE,
      }
    );

  // Recurso HTTP para criar nova linguagem
  readonly createNewLanguage: HttpResourceRef<
    string | undefined
  > = httpResource(() => {
    if (!this.newLanguage()) {
      return;
    }

    return {
      url: `http://localhost:3000/languages`,
      method: 'POST',
      // Assim que `newLanguage` recebe um novo valor,
      // é efetuada a requisição POST /languages
      body: this.newLanguage(),
      // Header customizado para testes:
      headers: new HttpHeaders({
        'x-cabecalho': this.customHeader(),
      }),
    };
  });

  readonly isLoading = computed(
    () =>
      this.languagesResource.isLoading() ||
      this.createNewLanguage.isLoading()
  );

  readonly languages = computed(() =>
    this.languagesResource.value()
  );

  readonly createHttpStatusResponse = computed(
    () => this.createNewLanguage.statusCode() ?? 0
  );

  readonly createHttpError = computed(() => {
    const error = this.createNewLanguage.error();
    if (error instanceof HttpErrorResponse) {
      return JSON.stringify(error);
    }

    return null;
  });

  constructor() {
    // Recarrega a lista após criação de um novo registro
    effect(() => {
      if (this.createHttpStatusResponse() === 201) {
        this.languagesResource.reload();
        this.newLanguage.set(null);
      }
    });
  }

  goToPage(page: number): void {
    this.currentPage.set(page);
  }

  createNewRecord(language: string): void {
    this.newLanguage.set({ name: language });
  }

  setLimit(limit: number): void {
    this.limit.set(limit);
    this.currentPage.set(1);
  }
}

app.component.ts

@if (isLoading()) {
<div>Carregando...</div>
}

<!-- LISTA DE LINGUAGENS -->
<div>
  <ul>
    @let languagesArray = languages().data;
    @for (language of languagesArray; track $index) {
    <li>{{ language }}</li>
    }
  </ul>
</div>

<!-- PÁGINAÇÃO -->
@let totalPages = languages().totalPages;
@for (page of [].constructor(totalPages); track $index) {
<button
  [disabled]="currentPage() === $index + 1 || isLoading()"
  (click)="goToPage($index + 1)">
  {{ $index + 1 }}
</button>
}

<div>Total de registros: {{ languages().total }}</div>

<br /><br /><br />

<input #inputLimit type="number" [value]="limit()" />
<button
  [disabled]="isLoading()"
  (click)="setLimit(+inputLimit.value)">
  Set
</button>

<!-- FORM PARA CRIAR UM NOVO REGISTRO -->
<br /><br /><br />
<input #input type="text" />
<button
  [disabled]="isLoading()"
  (click)="createNewRecord(input.value)">
  Create
</button>

<br /><br /><br />
@if (createHttpError()) {
<div style="color: #ff0000">
  {{createHttpError()}}
</div>
}

<router-outlet />

app.component.html

Resultado da execução:

Navegador mostrando a listagens das linguagens de programação, a paginação e a criação de um novo registro.
Exemplo do código em execução

Simulando um erro devido a tentativa de criar um registro duplicado:

Navegador mostrando um erro capturado pelo httpResource.
Exemplo de erro capturado pelo httpResource

Considerações

Neste pequeno exemplo é possível notar que o httpResource reage a mudanças de valores nos signals, tanto para consultas utilizando o método GET quanto para alterações com o método POST, diferente do HttpClient tradicional, que exige chamadas manuais para cada requisição, o httpResource atualiza automaticamente os dados em resposta a mudanças nos signals.

httpResource oferece outros benefícios, como a simplificação do tratamento de erros e a integração com interceptores. No entanto, devido ao seu status experimental, é importante usá-lo com cautela e estar ciente de possíveis mudanças na API.