Ir para o conteúdo principal
WBC - GovBR-DS
Copiar página como Markdown para IA

Angular – @govbr-ds/webcomponents-angular

Este é um wrapper Angular que encapsula os Web Components GovBR-DS, habilita NG_VALUE_ACCESSORS e permite a vinculação de eventos de entrada diretamente a um value accessor, proporcionando uma integração perfeita no fluxo de dados bidirecional do Angular.

Compatibilidade​

  • Faixa declarada: @angular/common, @angular/core e @angular/forms conforme as peerDependencies do pacote, sem limite superior.
  • Núcleo de Web Components: @govbr-ds/webcomponents na mesma versão deste wrapper.
  • Navegadores: A mesma política Baseline Widely Available dos Web Components. Internet Explorer não é suportado.

[!NOTE] O peer do Angular permanece sem limite superior para preservar compatibilidade com consumidores existentes. Isso não é uma garantia formal de suporte a cada versão nova do Angular. O subpath standalone e a entrada principal com NgModule usam o mesmo contrato publicado. O wrapper não faz requisições HTTP, autenticação, cache ou transporte de dados — essas responsabilidades permanecem na aplicação consumidora.

Versões novas do Angular​

O pacote pode ser consumido em versões do Angular mais novas que as validadas pelo Quickstart, mas a faixa aberta do peer não representa uma matriz de suporte formal para cada versão nova. Antes de atualizar Angular, valide o projeto com a versão exata do Angular CLI e mantenha @govbr-ds/webcomponents-angular e @govbr-ds/webcomponents na mesma versão.

Como o wrapper é produzido​

Os componentes Angular são gerados pelo @stencil/angular-output-target, que produz proxies com inputs, métodos, outputs e value accessors. Há duas entradas: @govbr-ds/webcomponents-angular (NgModule) e @govbr-ds/webcomponents-angular/standalone (standalone).

Os arquivos em src/stencil-generated/ e standalone/src/stencil-generated/ são artefatos gerados. Não os edite diretamente: eles podem ser substituídos no próximo build. Quando o núcleo mudar, gere primeiro os artefatos do Web Components e depois o wrapper:

pnpm exec nx build webcomponents
pnpm exec nx build angular

Se o wrapper for compilado sem regenerar esses arquivos, a aplicação pode usar um componente antigo, não reconhecer uma propriedade nova (por exemplo, legibility-target-id) ou apresentar tipos diferentes do runtime instalado.

Configuração standalone (Angular 19+)​

Importe cada proxy usado no componente e registre os custom elements uma única vez:

// app.config.ts
import { ApplicationConfig, importProvidersFrom } from '@angular/core'
import { WebcomponentsAngularModule } from '@govbr-ds/webcomponents-angular'

export const appConfig: ApplicationConfig = {
providers: [importProvidersFrom(WebcomponentsAngularModule.forRoot())],
}
// app.component.ts
import { Component } from '@angular/core'
import { BrScrim } from '@govbr-ds/webcomponents-angular/standalone'

@Component({
selector: 'app-root',
standalone: true,
imports: [BrScrim],
template: `
<img id="banner" src="/assets/banner.jpg" alt="Banner" />
<br-scrim variant="legibility" legibility-target-id="#banner">
<p>Texto sobre o banner</p>
</br-scrim>
`,
})
export class AppComponent {}

Atenção: se br-scrim não estiver em imports, Angular emitirá NG8001/NG8002. Não corrija isso apenas adicionando CUSTOM_ELEMENTS_SCHEMA: essa opção silencia erros de template, mas não cria proxies, métodos, outputs ou value accessors.

Inputs, outputs e Angular Forms​

Inputs podem ser usados em bindings ([isOpen], [legibilitySize]) ou como atributos HTML (is-open, legibility-size). Eventos customizados dos Web Components são expostos pelos proxies como outputs Angular e carregam o payload em $event.detail:

<br-scrim variant="legibility" (brScrimOpen)="onOpen($event)"></br-scrim>
onOpen(event: CustomEvent<void>) {
console.log(event.detail)
}

br-scrim não é um controle de formulário e não deve receber formControlName ou [(ngModel)]. Os value accessors gerados são destinados a componentes que possuem valor, como br-input, br-select, checkbox, radio e upload. Para esses componentes, importe ReactiveFormsModule ou FormsModule.

Eventos nativos de formulário, como (input) e (change), não usam detail: leia value, checked, files, selected ou rangeValue em $event.currentTarget. Em geral o value accessor já faz essa leitura; adicione um listener somente quando a aplicação também precisar reagir à interação.

Particularidades do variant="legibility"​

Essa variante fica sempre ativa, não bloqueia a interação e não fecha com ESC. Sem legibility-target-id, ela usa o elemento pai direto como referência. Para evitar que um container expansível determine uma largura maior que a imagem, informe um seletor:

<img id="imagem-produto" src="/assets/produto.jpg" alt="Produto" />
<br-scrim
variant="legibility"
legibility-target-id="#imagem-produto"
legibility-anchor="bottom"
legibility-size="25%"
>
<span>Descrição do produto</span>
</br-scrim>

O seletor é resolvido com document.querySelector. Se for inválido ou não encontrar elemento, o pai direto será usado como fallback. O alvo precisa existir no momento da inicialização; para conteúdo criado depois, atualize a propriedade após a criação e aguarde a renderização.

Problemas frequentes em Angular 19+​

SintomaCausa provávelCorreção
NG8001/NG8002Proxy não importado no standalone ou móduloAdicione o proxy em imports ou WebcomponentsAngularModule.forRoot() no NgModule
Propriedade desconhecidaProxy gerado desatualizado ou nome incorretoRegenere webcomponents e angular; use kebab-case no HTML ou binding Angular
Elemento sem comportamentodefineCustomElements() não executadoRegistre WebcomponentsAngularModule.forRoot() uma única vez
Evento não dispara ou valor vazioListener nativo errado ou leitura de $event em vez de $event.detailUse o output do proxy e leia detail
formControlName não atualizaComponente sem value accessor ou FormsModule ausenteUse componente compatível e importe ReactiveFormsModule/FormsModule
Falha em AOT/produçãoArtefatos, Angular ou bundler incompatíveisExecute ng build, alinhe versões e consulte a issue #317
Erro de window/document no SSRDOM acessado no servidorInicialize no browser e use afterNextRender()/isPlatformBrowser

O output target gera código para o contrato disponível na publicação; ele não transforma automaticamente qualquer Web Component em um controle Angular. Em Angular 19+, valide standalone, AOT, SSR/hidratação e Angular Forms antes de promover a atualização para produção.

Por que usar este wrapper? 🤔​

  • Desacoplamento da detecção de mudanças dos elementos Web.
  • Conversão de eventos para observáveis RxJS, alinhado ao @Output().
  • Control Value Accessors para integração com Reactive Forms e ngModel.

[!TIP] Para mais detalhes, consulte a documentação oficial do Stencil.

Instalação 📦​

Instale o wrapper e suas dependências:

npm install @govbr-ds/webcomponents-angular
# ou
pnpm add @govbr-ds/webcomponents-angular
# ou
yarn add @govbr-ds/webcomponents-angular

peerDependencies​

peerDependencies são pacotes que este wrapper não instala automaticamente — o seu projeto precisa tê-los instalados.

Observe que algumas peerDependencies podem ter suas próprias peerDependencies que também precisam ser atendidas. Consulte a documentação de cada pacote para garantir que todas as dependências necessárias estejam presentes.

Por que existem: Garantem que o Angular CLI e o wrapper compartilhem a mesma instância do Angular e dos Web Components. Versões duplicadas podem causar falhas na compilação ou erros em tempo de execução.

O que isso implica: Se as peers não estiverem instaladas ou forem de versão incompatível, o npm emitirá avisos e o pacote pode não funcionar corretamente.

As peers declaradas neste pacote são:

  • @angular/core
  • @angular/common
  • @angular/forms
  • @govbr-ds/webcomponents

As versões aceitas estão em peerDependencies no package.json publicado deste pacote (também exibidas na página do npm e na política de compatibilidade). O peer @govbr-ds/webcomponents exige exatamente a mesma versão deste wrapper.

Nota importante: pnpm e tree-shaking​

Se ao consumir estes pacotes você notar que o bundler não está removendo código não utilizado (tree‑shaking), pode haver uma incompatibilidade com o layout padrão do pnpm.

Solução rápida (opcional, somente se precisar): crie um arquivo .npmrc na raiz do seu projeto com:

node-linker=hoisted

Por que isso ajuda: por padrão, o pnpm organiza as dependências em pastas isoladas com symlinks. Alguns bundlers/otimizadores se baseiam na estrutura de node_modules e no campo sideEffects para decidir o que pode ser eliminado. O layout hoisted aproxima o formato “achatado” (similar ao npm/yarn), facilitando essa análise e, em muitos casos, restaurando o tree‑shaking.

Observações:

  • Use apenas se o tree‑shaking realmente não estiver funcionando.
  • Pode aumentar o uso de disco e alterar a resolução de dependências do seu projeto.

Quickstart Angular​

Use o quickstart Angular como referência para configuração de projeto:

  • Repositório: govbr-ds-wbc-quickstart-angular
  • Servidor local: pnpm start
  • Formulários: Reactive Forms, Template-driven Forms e estado com Signals
  • Testes headless: pnpm test:e2e
  • Porta padrão: http://localhost:4200/

Uso 📚​

Angular com módulos​

// app.module.ts
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms'
import { WebcomponentsAngularModule } from '@govbr-ds/webcomponents-angular'
import { AppComponent } from './app.component'

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, FormsModule, WebcomponentsAngularModule.forRoot()],
bootstrap: [AppComponent],
})
export class AppModule {}

Angular standalone​

// app.component.ts
import { Component } from '@angular/core'
import { FormsModule } from '@angular/forms'
import {
BrButton,
BrInput,
TextValueAccessor,
} from '@govbr-ds/webcomponents-angular/standalone'

@Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule, BrButton, BrInput, TextValueAccessor],
template: `
<br-input name="nome" label="Nome" [(ngModel)]="nome"></br-input>
<br-button type="button" (click)="nome = ''">Limpar</br-button>
`,
})
export class AppComponent {
nome = ''
}

Em standalone, o proxy visual e seu value accessor são imports independentes. Use a tabela para incluir somente os contratos presentes no template:

ComponentesPropriedade/eventoValor no modeloValue accessor
input textual e textareavalue / inputstringTextValueAccessor
datas simplesvalue / inputDate | nullTextValueAccessor
input numérico e slider simplesvalue / inputnumber | nullNumericValueAccessor
select e radio groupvalue / changeseleçãoSelectValueAccessor
radio isoladochecked ou value / changeseleção ou booleanoRadioValueAccessor
checkbox e switchchecked / changebooleanBooleanValueAccessor
uploadfiles / changeFileList | nullFileValueAccessor
tag selecionávelselected / changeseleçãoSelectableTagValueAccessor
slider ou data em intervalorangeValue / inputobjeto de intervaloRangeValueAccessor

Adicione também FormValidityValidator quando a validade nativa do componente precisar participar de control.valid. A entrada com NgModule declara os accessors e o validador; não misture WebcomponentsAngularModule com proxies standalone no mesmo componente.

Datas simples permanecem como Date | null no FormControl ou no ngModel, apesar do nome histórico TextValueAccessor. FormData serializa a data somente no envio; ao restaurar JSON, converta e valide a string antes de atribuí-la ao controle.

Uso com NgModel e binding​

Para habilitar ngModel e binding bidirecional, adicione FormsModule. Em NgModule, os value accessors vêm de WebcomponentsAngularModule.forRoot(); em standalone, importe o accessor da tabela. Não crie diretivas no aplicativo, não use ngDefaultControl e não desative AOT para ocultar NG01203: esse erro indica que o accessor não foi importado.

<br-checkbox
name="userTermsConditions"
label="Concordo com os Termos e Condições"
[(ngModel)]="termsConditions"
(change)="onTermsConditionsChange($event)"
></br-checkbox>
// app.component.ts
import { Component } from '@angular/core'

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
termsConditions = true
onTermsConditionsChange(event: Event) {
console.log('O valor mudou!', (event.currentTarget as HTMLBrCheckboxElement).checked)
}
}

Validação e Acessibilidade (Reactive Forms)​

Ao utilizar o ReactiveFormsModule, combine os validadores Angular com a Constraint Validation dos componentes. O accessor sincroniza o valor, mas a aplicação continua responsável por apresentar state="danger", mensagem textual e associação acessível quando o controle estiver inválido.

<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<br-input
label="Usuário"
formControlName="username"
required
[state]="loginForm.controls.username.invalid && loginForm.controls.username.touched ? 'danger' : undefined">
</br-input>

<br-message
*ngIf="loginForm.controls.username.invalid && loginForm.controls.username.touched"
state="danger" show-icon>
O nome de usuário é obrigatório.
</br-message>

<br-button type="submit" [disabled]="loginForm.invalid">Entrar</br-button>
</form>

Para regras de domínio, como CPF ou confirmação de senha, chame setCustomValidity() no host e limpe a mensagem com '' quando o valor for corrigido. Para limpar um formulário, use um botão type="reset", trate (reset) para restaurar o FormGroup ou modelo e deixe o reset nativo limpar controles como br-upload.

Dependências de Build 🛠️​

Este pacote é um wrapper gerado automaticamente e depende dos artefatos produzidos pelo núcleo de Web Components.

PacoteDependência de BuildMotivo
@govbr-ds/webcomponents-angularwebcomponents:buildNecessita dos proxies gerados em src/stencil-generated e standalone/src/stencil-generated.

Desenvolvimento 👨‍💻​

Estrutura do projeto​

├── 📁 src
│ ├── 📁 stencil-generated
│ ├── 📄 angular-webcomponents.module.ts
│ └── 📄 index.ts
├── 📁 standalone
│ └── 📁 src
│ ├── 📁 stencil-generated
│ └── 📄 index.ts
└── 📁 scripts

[!WARNING] Tudo dentro de stencil-generated é sobrescrito ao gerar o build de Web Components.

Scripts/Build​

Gere os Web Components antes de compilar o wrapper:

nx build webcomponents
nx build angular

Formatos do build 📦​

A tarefa nx build angular compila o wrapper com ng-packagr e gera a saída em dist/angular/. Abaixo estão os artefatos produzidos e quando utilizá-los.

Estrutura do dist/angular/​

dist/angular/
├── esm2022/ ← Módulos ESM (Angular Ivy)
│ ├── index.js
│ └── stencil-generated/
├── fesm2022/ ← Flat ESM bundle (otimizado para bundlers)
│ └── govbr-ds-webcomponents-angular.mjs
├── standalone/ ← Componentes standalone (sem NgModule)
│ ├── esm2022/
│ └── fesm2022/
├── index.d.ts ← Tipos TypeScript (entrada principal)
├── package.json ← Campos exports/main/module para resolução
└── README.md

Quando usar cada formato​

ArtefatoQuando usarObservações
fesm2022/Aplicações Angular 14+ (padrão)Resolvido automaticamente pelo Angular CLI via campo exports
esm2022/Toolchains que necessitam de módulos individuaisPermite tree-shaking granular por arquivo
standalone/Aplicações Angular standalone (sem NgModule)Importação via @govbr-ds/webcomponents-angular/standalone
index.d.tsAutocomplete e tipagem TypeScriptTipos para diretivas, value accessors e módulo

NgModule vs Standalone​

O pacote disponibiliza duas variantes de uso:

  • NgModule (entrada principal): use WebcomponentsAngularModule.forRoot() no imports do módulo.
  • Standalone (subpath /standalone): importe componentes individuais diretamente.
// NgModule
import { WebcomponentsAngularModule } from '@govbr-ds/webcomponents-angular'

@NgModule({
imports: [WebcomponentsAngularModule.forRoot()],
})
export class AppModule {}

// Standalone
import { BrButton } from '@govbr-ds/webcomponents-angular/standalone'

@Component({
standalone: true,
imports: [BrButton],
})
export class AppComponent {}

[!NOTE] Ambas as variantes dependem de @govbr-ds/webcomponents e @govbr-ds/core instalados.

Documentações Complementares 📖​

SSR (Server-Side Rendering)​

Web Components dependem de APIs do navegador (DOM, window, customElements). Para projetos Angular Universal / SSR:

  1. Proteja imports com verificação isPlatformBrowser:
import { isPlatformBrowser, PLATFORM_ID } from '@angular/common'
import { inject } from '@angular/core'

const isBrowser = isPlatformBrowser(inject(PLATFORM_ID))
  1. Use afterNextRender() (Angular 16+) para lógica que depende do DOM:
afterNextRender(() => {
// Código que acessa o DOM
})
  1. Em angular.json, importe os estilos CSS condicionalmente para evitar warnings no servidor.

Referências Complementares 📖​

Contribuindo 🤝​

Reportar Bugs/Problemas 🐛​

Abra uma issue: gitlab.com/.../issues/new

Commits 📝​

Padrões de branches e commits: gov.br/ds/wiki

Precisa de ajuda? 🆘​

Créditos 🎉​

Desenvolvido pelo SERPRO com a comunidade.

Comece aquiComponentesQuickstartsSobre o projetoRepositório GitLabVersõesDiscordGovBR-DSFAQWikiRedefinir cookies