Dados remotos e carregamento
Componentes visuais não fazem chamadas HTTP. A aplicação escuta os eventos de interação, controla debounce, autenticação, cache e cancelamento e devolve os dados por propriedades, slots ou conteúdo projetado.
Busca em br-select
Use filterable e search-mode="remote". O evento brSelectSearch contém a
consulta atual em event.detail.query, inclusive quando ela é limpa.
let requestId = 0;
let controller;
select.addEventListener('brSelectSearch', async ({ detail: { query } }) => {
controller?.abort();
const currentRequest = ++requestId;
controller = new AbortController();
select.loading = true;
try {
const url = new URL('/api/options', location.origin);
url.searchParams.set('q', query.trim());
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error('Não foi possível carregar as opções.');
const result = await response.json();
if (currentRequest === requestId) await select.setOptions(result.items);
} catch (error) {
if (!(error instanceof DOMException && error.name === 'AbortError')) {
// Mostre uma mensagem role="alert" fora do componente.
}
} finally {
if (currentRequest === requestId) select.loading = false;
}
});
Defina o limite mínimo de caracteres na aplicação. Por exemplo, antes de
consultar o serviço, limpe as opções quando a consulta tiver menos de dois
caracteres. O consumidor também pode mapear a resposta para o formato
{ label, value } antes de chamar setOptions().
O componente preserva a seleção atual quando o item selecionado não aparece na janela de resultados seguinte. O consumidor deve mapear e validar o payload da API antes de passá-lo ao componente.
Angular standalone com FormControl
[!IMPORTANT] O pacote 2.x declara Angular
>=14, mas a matriz automatizada atual verifica até o Angular 18.2.14. Use o procedimento abaixo como teste-piloto no Angular 21 e valide compilação AOT, formulário e navegador antes de adotá-lo em produção.
Instale o wrapper e o Web Component na mesma versão. Este exemplo fixa a release 2.1.3 e instala as dependências de runtime declaradas pelo pacote:
npm install \
@govbr-ds/core@3.7.0 \
@govbr-ds/webcomponents@2.1.3 \
@govbr-ds/webcomponents-angular@2.1.3 \
@floating-ui/dom@^1.7.6 \
date-fns@^4.1.0 \
iconify-icon@3.0.2
Importe os estilos globais uma vez em src/styles.css:
@import '@govbr-ds/core/dist/core.min.css';
Registre o HttpClient no app.config.ts:
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient()],
};
O componente standalone precisa importar três peças distintas:
BrSelect, que registra e expõe o Web Component;SelectValueAccessor, que conectavalue,change, estado desabilitado e toque aoFormControl;FormValidityValidator, que encaminha a validade nativa do Web Component ao Angular Forms.
import { HttpClient } from '@angular/common/http';
import { Component, DestroyRef, ViewChild, inject, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Subject, catchError, debounceTime, distinctUntilChanged, finalize, map, of, switchMap } from 'rxjs';
import type { SelectOptionData, SelectSearchDetail } from '@govbr-ds/webcomponents';
import { BrSelect, FormValidityValidator, SelectValueAccessor } from '@govbr-ds/webcomponents-angular/standalone';
type OrgaoApi = {
id: string;
nome: string;
};
@Component({
selector: 'app-orgao-select',
standalone: true,
imports: [ReactiveFormsModule, BrSelect, SelectValueAccessor, FormValidityValidator],
template: `
<br-select
[formControl]="orgaoId"
label="Órgão"
placeholder="Digite ao menos dois caracteres"
searchMode="remote"
[filterable]="true"
[loading]="loading()"
required
(brSelectSearch)="onSearch($event)"
></br-select>
@if (errorMessage()) {
<p role="alert">{{ errorMessage() }}</p>
}
`,
})
export class OrgaoSelectComponent {
private readonly http = inject(HttpClient);
private readonly destroyRef = inject(DestroyRef);
private readonly queries = new Subject<string>();
@ViewChild(BrSelect) private select?: BrSelect;
readonly orgaoId = new FormControl('', {
nonNullable: true,
validators: [Validators.required],
});
readonly loading = signal(false);
readonly errorMessage = signal('');
constructor() {
this.queries
.pipe(
map((query) => query.trim()),
debounceTime(300),
distinctUntilChanged(),
switchMap((query) => {
this.errorMessage.set('');
if (query.length < 2) return of([] as SelectOptionData[]);
this.loading.set(true);
return this.http.get<OrgaoApi[]>('/api/orgaos', { params: { q: query } }).pipe(
map((items) =>
items.map((item) => ({
label: item.nome,
value: item.id,
})),
),
catchError(() => {
this.errorMessage.set('Não foi possível consultar os órgãos. Tente novamente.');
return of([] as SelectOptionData[]);
}),
finalize(() => this.loading.set(false)),
);
}),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((options) => void this.select?.setOptions(options));
}
onSearch(event: CustomEvent<SelectSearchDetail>): void {
this.queries.next(event.detail.query);
}
}
O exemplo usa um FormControl isolado. Se o controle estiver dentro de um
FormGroup, substitua [formControl] por formControlName="orgaoId" e declare
o grupo no elemento <form [formGroup]="form">.
O switchMap cancela a inscrição da requisição anterior do HttpClient quando
chega uma consulta nova. setOptions() atualiza a janela de resultados sem
apagar uma seleção remota que esteja fora da resposta atual. A escolha do
usuário chega a orgaoId.valueChanges; orgaoId.setValue(...), reset(),
disable() e enable() percorrem o caminho inverso pelo value accessor.
No modo múltiplo, adicione [multiple]="true" e use um
FormControl<string[]> não anulável; o value accessor entrega a lista de
valores selecionados.
Não instancie core.BRSelect, não use ngDefaultControl, não registre
CUSTOM_ELEMENTS_SCHEMA e não chame defineCustomElements() manualmente neste
fluxo. Também não misture WebcomponentsAngularModule.forRoot() com os proxies
standalone no mesmo componente.
Se aparecer NG01203: No value accessor, confirme que
SelectValueAccessor veio do subpath
@govbr-ds/webcomponents-angular/standalone e está no array imports. Confirme
também que wrapper e Web Components possuem a mesma versão:
npm ls @govbr-ds/webcomponents @govbr-ds/webcomponents-angular
br-pagination e br-table
br-pagination já emite brPaginationPageChange e
brPaginationPerPageChange. Defina loading enquanto busca a página; os
controles ficam bloqueados e aria-busy="true" é aplicado.
Use controlled quando a página visível só puder mudar após uma resposta
bem-sucedida. Nesse modo, os eventos comunicam page ou perPage, mas a
aplicação confirma a mudança atribuindo current ou perPage.
br-table recebe loading enquanto suas linhas são atualizadas. As linhas
anteriores permanecem visíveis para evitar mudança de layout; o novo conteúdo
continua sendo fornecido pelos slots header e body.
Outros campos
Validação remota em br-input e br-textarea
Para validar disponibilidade, unicidade ou outra regra de domínio no servidor,
atribua uma função assíncrona a validator. A função recebe o valor textual e
retorna null quando válido ou uma mensagem quando inválido. O componente
expõe aria-busy="true", o slot validation-loading e um evento de início e
fim da validação. A validação ocorre no change ou quando a aplicação chama
validate(); não ocorre a cada tecla.
input.validator = async (value) => {
const response = await fetch(`/api/usuarios/${encodeURIComponent(value)}`);
if (!response.ok) throw new Error('Falha ao consultar o usuário.');
return (await response.json()).available ? null : 'Valor já utilizado.';
};
const valid = await input.validate();
Use AbortController e um identificador de requisição na própria função
quando a política da aplicação exigir cancelamento. O componente ignora o
resultado de uma validação antiga, mas não cancela automaticamente o fetch.
Consulte também as seções de validação do input e do
textarea.
Upload assíncrono em br-upload
br-upload aceita uploadHandler como propriedade JavaScript. O callback
recebe file, signal e onProgress; endpoint, autenticação e formato da
resposta continuam externos. Use brUploadStart, brUploadProgress,
brUploadSuccess e brUploadError para atualizar a aplicação. Sem
uploadHandler, o componente mantém apenas a seleção e a validação dos
arquivos.
upload.uploadHandler = async ({ file, signal, onProgress }) => {
const body = new FormData();
body.append('arquivo', file);
// fetch não expõe progresso de upload; use um cliente que aceite onProgress
// quando a interface precisar exibir percentual real.
const response = await fetch('/api/arquivos', { method: 'POST', body, signal });
if (!response.ok) throw new Error('Falha no envio.');
};
Quando o cliente de transporte oferecer progresso, encaminhe-o por
onProgress. cancelUpload() aborta o envio atual pelo AbortSignal.
br-list e br-dropdown são componentes de composição e também não assumem
transporte ou estado de dados.