As informações apresentadas nesta página são complementares às diretrizes de desenvolvimento de software do Ministério da Saúde.
Pode haver diferença nas informações contidas nesta página quando tratar-se de implementação de código multiplataforma. As especificidades devem ser tratadas em conjunto com a equipe da COATIC.
Documento atualizado em: 29/06/2026.
^21.0.0Para Angular 21.0.x, 21.1.x e 21.2.x, a documentação oficial indica:
| Dependência | Versões suportadas |
|---|---|
| Node.js | ^20.19.0, ^22.12.0 ou ^24.0.0 |
| TypeScript | >=5.9.0 <6.0.0 |
| RxJS | ^6.5.3 ou ^7.4.0 |
Antes de atualizar um projeto, validar também a matriz em Version compatibility.
Orientações para acesso aos repositórios Git encontram-se na página inicial.
Este documento descreve melhores práticas e padrões recomendados para projetos Angular 21 LTS usando a arquitetura standalone. O objetivo é manter aplicações consistentes, escaláveis, testáveis e alinhadas com a documentação atual do Angular.
A abordagem standalone permite desenvolver aplicações sem depender de NgModule para declarar componentes, diretivas, pipes e rotas. Em versões atuais do Angular, componentes são standalone por padrão; projetos antigos podem conter standalone: false e uso de NgModule, mas essa não deve ser a estratégia preferencial para novos projetos.
NgModule.loadComponent e loadChildren.computed, effect e model.provideRouter, provideHttpClient e demais providers funcionais.input() e input.required().model() para cenários de two-way binding.output() para eventos customizados.inject() para injeção de dependência.app.routes.ts e providers em app.config.ts.A arquitetura standalone organiza o projeto em componentes, rotas e serviços autônomos. Cada componente declara explicitamente o que usa no array imports, e a aplicação é inicializada com bootstrapApplication.
Exemplo de bootstrap:
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig).catch((error) => console.error(error));
Exemplo de configuração:
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './core/interceptors/auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor])),
],
};
A documentação oficial recomenda organizar o código por áreas funcionais e manter o código de UI dentro de src. Para projetos institucionais, a estrutura abaixo mantém a separação por domínio e evita pastas globais excessivamente genéricas.
my-app/
├── src/
│ ├── main.ts
│ ├── index.html
│ ├── styles.css
│ └── app/
│ ├── app.component.ts
│ ├── app.config.ts
│ ├── app.routes.ts
│ ├── core/
│ │ ├── auth/
│ │ ├── guards/
│ │ ├── interceptors/
│ │ ├── layouts/
│ │ └── services/
│ ├── shared/
│ │ ├── components/
│ │ ├── directives/
│ │ ├── pipes/
│ │ └── ui/
│ └── features/
│ ├── atendimento/
│ │ ├── atendimento.routes.ts
│ │ ├── pages/
│ │ ├── components/
│ │ ├── services/
│ │ └── models/
│ └── usuarios/
│ ├── usuarios.routes.ts
│ ├── pages/
│ ├── components/
│ ├── services/
│ └── models/
├── angular.json
├── package.json
├── tsconfig.json
└── tsconfig.app.json
kebab-case.utils.ts, helpers.ts e common.ts quando houver contexto de domínio mais específico.Exemplo:
user-profile.ts
user-profile.html
user-profile.css
user-profile.spec.ts
PascalCase com sufixo Component.PascalCase com sufixo Service.camelCase para funções ou PascalCase para classes legadas.Exemplos:
export interface Usuario {
id: number;
nome: string;
cpf: string;
}
export class UsuarioService {}
camelCase.salvarUsuario() a handleClick().Componentes devem representar uma unidade clara de interface. Devem conter lógica diretamente relacionada à apresentação e delegar regras de negócio, acesso a dados e orquestrações complexas para services ou stores de estado.
Exemplo de componente standalone:
import { Component, computed, input, output } from '@angular/core';
@Component({
selector: 'app-user-card',
imports: [],
templateUrl: './user-card.html',
styleUrl: './user-card.css',
})
export class UserCardComponent {
readonly name = input.required<string>();
readonly active = input(false);
readonly selected = output<string>();
protected readonly statusLabel = computed(() => (this.active() ? 'Ativo' : 'Inativo'));
protected selectUser(): void {
this.selected.emit(this.name());
}
}
Boas práticas:
imports.input(), input.required(), output() e model() em novos componentes.protected para membros acessados apenas pelo template.readonly para inputs, outputs, models, queries e dependências injetadas.computed() ou métodos nomeados.Para novos projetos, preferir APIs baseadas em Signals.
readonly id = input.required<number>();
readonly label = input('Sem descrição');
readonly disabled = input(false);
readonly saved = output<number>();
protected save(): void {
this.saved.emit(this.id());
}
Usar model() quando o componente precisa alterar o valor recebido pelo pai, como em controles de formulário customizados.
readonly value = model('');
protected clear(): void {
this.value.set('');
}
Uso no componente pai:
<app-search-box [(value)]="searchTerm" />
Signals são a API reativa recomendada para estado local e derivado em componentes e serviços. Eles permitem rastreamento granular de dependências e ajudam o Angular a otimizar atualizações de renderização.
Exemplo:
import { computed, signal } from '@angular/core';
const quantidade = signal(1);
const precoUnitario = signal(10);
const total = computed(() => quantidade() * precoUnitario());
quantidade.update((valor) => valor + 1);
Recomendações:
signal() para estado local mutável.computed() para valores derivados.effect() apenas para efeitos colaterais necessários, como integração com APIs não reativas.Exemplo em service:
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class SessionStateService {
private readonly usuarioAtualState = signal<string | null>(null);
readonly usuarioAtual = this.usuarioAtualState.asReadonly();
definirUsuario(nome: string): void {
this.usuarioAtualState.set(nome);
}
limpar(): void {
this.usuarioAtualState.set(null);
}
}
Preferir inject() em novos códigos, especialmente quando a classe possui várias dependências ou quando propriedades inicializadas dependem de services.
import { Component, inject } from '@angular/core';
import { UsuarioService } from './usuario.service';
@Component({
selector: 'app-usuarios-page',
templateUrl: './usuarios-page.html',
})
export class UsuariosPageComponent {
private readonly usuarioService = inject(UsuarioService);
protected carregarUsuarios(): void {
this.usuarioService.listar().subscribe();
}
}
Rotas devem ficar em arquivos com routes no nome, como app.routes.ts e usuarios.routes.ts.
Exemplo de rota principal:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
loadComponent: () =>
import('./core/pages/home-page/home-page.component').then((m) => m.HomePageComponent),
title: 'Início',
},
{
path: 'usuarios',
loadChildren: () => import('./features/usuarios/usuarios.routes').then((m) => m.USUARIOS_ROUTES),
},
{
path: '**',
loadComponent: () =>
import('./core/pages/not-found-page/not-found-page.component').then((m) => m.NotFoundPageComponent),
title: 'Página não encontrada',
},
];
Exemplo de rotas de uma feature:
import { Routes } from '@angular/router';
export const USUARIOS_ROUTES: Routes = [
{
path: '',
loadComponent: () => import('./pages/usuarios-list-page.component').then((m) => m.UsuariosListPageComponent),
title: 'Usuários',
},
{
path: ':id',
loadComponent: () => import('./pages/usuario-detail-page.component').then((m) => m.UsuarioDetailPageComponent),
title: 'Detalhe do usuário',
},
];
Boas práticas:
loadComponent para páginas standalone.loadChildren para agrupar rotas de features.title nas rotas para acessibilidade.** como última rota.Usar provideHttpClient() em app.config.ts.
import { provideHttpClient, withInterceptors } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([authInterceptor])),
],
};
Exemplo de interceptor funcional:
import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (request, next) => {
const token = sessionStorage.getItem('access_token');
if (!token) {
return next(request);
}
return next(
request.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
}),
);
};
Angular permite combinar:
Criar novo projeto com SSR:
ng new nome-app --ssr
Adicionar SSR a projeto existente:
ng add @angular/ssr
Exemplo de rotas de servidor:
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: '',
renderMode: RenderMode.Client,
},
{
path: 'institucional',
renderMode: RenderMode.Prerender,
},
{
path: 'perfil',
renderMode: RenderMode.Server,
},
{
path: '**',
renderMode: RenderMode.Server,
},
];
Cuidados para SSR:
window, document, navigator ou location.afterNextRender() para inicializações exclusivas do navegador.Para projetos estáveis em produção, usar Reactive Forms como padrão institucional.
import { Component, inject } from '@angular/core';
import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-user-form',
imports: [ReactiveFormsModule],
templateUrl: './user-form.html',
})
export class UserFormComponent {
private readonly formBuilder = inject(NonNullableFormBuilder);
protected readonly form = this.formBuilder.group({
nome: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
});
protected salvar(): void {
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
const value = this.form.getRawValue();
console.log(value);
}
}
Signal Forms existem na linha Angular 21 como recurso experimental. Não devem ser adotados como padrão corporativo sem avaliação da equipe técnica, pois APIs experimentais podem mudar em versões patch ou minor.
Usar o novo control flow em templates:
@if (usuario(); as usuarioAtual) {
<app-user-card [name]="usuarioAtual.nome" />
} @else {
<p>Nenhum usuário selecionado.</p>
}
@for (item of itens(); track item.id) {
<app-item-row [item]="item" />
} @empty {
<p>Nenhum item encontrado.</p>
}
Recomendações:
@if, @for e @switch em novos templates.track em listas.[class] e [style] ao uso de ngClass e ngStyle.computed().Preferir AsyncPipe quando o valor é usado somente no template.
Para subscrições imperativas, usar takeUntilDestroyed().
import { Component, DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.html',
})
export class DashboardComponent {
private readonly destroyRef = inject(DestroyRef);
private readonly service = inject(DashboardService);
protected carregar(): void {
this.service
.listar()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
}
}
Boas práticas:
subscribe() sem estratégia de encerramento.take(1) para leituras únicas.switchMap, concatMap, mergeMap e exhaustMap conforme o comportamento esperado.Manter testes próximos ao arquivo testado, com sufixo .spec.ts.
usuario-card.component.ts
usuario-card.component.html
usuario-card.component.css
usuario-card.component.spec.ts
Exemplo básico:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserCardComponent } from './user-card.component';
describe(UserCardComponent.name, () => {
let fixture: ComponentFixture<UserCardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserCardComponent],
}).compileComponents();
fixture = TestBed.createComponent(UserCardComponent);
fixture.componentRef.setInput('name', 'Maria');
fixture.detectChanges();
});
it('deve criar o componente', () => {
expect(fixture.componentInstance).toBeTruthy();
});
});
Comandos comuns:
ng new nome-app --standalone
ng new nome-app --ssr
ng generate component features/usuarios/pages/usuarios-list-page
ng generate service features/usuarios/services/usuario
ng serve
ng test
ng build
ng update
Atualização entre versões:
ng update @angular/core@21 @angular/cli@21
Para saltos de múltiplas versões, atualizar uma versão major por vez e consultar o Angular Update Guide.
Recomendações mínimas:
title nas rotas.Boas práticas:
bypassSecurityTrustHtml, bypassSecurityTrustUrl e APIs similares sem revisão técnica.Boas práticas:
OnPush quando aplicável e coerente com o padrão do projeto.track em @for.Recomendações:
any; quando necessário, justificar e restringir o escopo.const para valores que não mudam.21.x.bootstrapApplication.app.config.ts.app.routes.ts.NgModule para novo código.provideHttpClient..spec.ts.A abordagem Angular 21 LTS standalone deve ser o padrão para novos projetos Angular institucionais que precisam de estabilidade, suporte prolongado e arquitetura moderna.
O uso de componentes standalone, Signals, providers funcionais, lazy loading por rotas e renderização híbrida permite criar aplicações mais enxutas, previsíveis e preparadas para evolução. APIs experimentais, como Signal Forms, devem ser avaliadas caso a caso antes de adoção em produção.