O que é Angular?
Angular é um framework de aplicação completo mantido pelo Google. Enquanto o React é uma biblioteca em torno da qual você monta sua stack, o Angular já entrega a stack pronta: componentes, roteamento, formulários, HTTP, injeção de dependência, testes e uma CLI poderosa, tudo projetado para trabalhar em conjunto. Ele é focado em TypeScript e opinativo, que é exatamente o motivo pelo qual grandes equipes o escolhem.
Essa completude é a principal troca. Você aceita uma curva de aprendizado mais acentuada e mais convenções de framework em troca de consistência, ferramentas robustas e menos decisões arquiteturais para tomar do zero. Para um protótipo pequeno, isso pode parecer pesado; para uma base de código empresarial de cinco anos, costuma ser um alívio.
Componentes e templates
Um componente é uma classe TypeScript decorada com @Component, combinada com um template HTML e estilos opcionais.
// user-card.component.ts
import { Component, Input } from "@angular/core";
@Component({
selector: "app-user-card",
standalone: true,
template: `
<article class="card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</article>
`,
})
export class UserCardComponent {
@Input({ required: true }) user!: { name: string; email: string };
}
O template utiliza {{ }} para interpolação e diretivas para a lógica. O Angular moderno utiliza blocos de controle de fluxo nativos em vez das antigas diretivas estruturais.
<!-- list.html -->
@if (users.length > 0) {
<ul>
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
}
</ul>
} @else {
<p>No users yet.</p>
}
A expressão track em @for desempenha o mesmo papel que uma key no React ou Vue: ela informa ao Angular como correlacionar os itens entre as atualizações.
Data binding
Os templates do Angular suportam quatro tipos de binding.
- Interpolation
{{ value }}renderiza um valor como texto. - Property binding
[src]="url"define a propriedade de um elemento a partir de uma expressão. - Event binding
(click)="save()"chama um método quando um evento é disparado. - Two-way binding
[(ngModel)]="name"combina ambos para inputs de formulários.
<!-- bindings.html -->
<img [src]="user.avatar" [alt]="user.name" />
<button (click)="save()">Save</button>
<input [(ngModel)]="query" />
O two-way binding é conveniente para formulários simples, mas para qualquer coisa que exija validação, os reactive forms geralmente são a melhor escolha.
Signals
Signals são a primitiva de reatividade moderna do Angular. Um signal armazena um valor e notifica os consumidores interessados quando ele muda, o que permite que o framework atualize apenas as partes da view que dependem dele.
// cart.component.ts
import { Component, signal, computed } from "@angular/core";
@Component({
selector: "app-cart",
standalone: true,
template: `<p>Total: {{ total() }}</p>`,
})
export class CartComponent {
items = signal([{ price: 10 }, { price: 20 }]);
total = computed(() =>
this.items().reduce((sum, i) => sum + i.price, 0),
);
add(price: number) {
this.items.update((items) => [...items, { price }]);
}
}
computed deriva um valor de outros signals e o armazena em cache até que uma dependência mude. effect executa um side effect quando os signals que ele lê são alterados. Juntos, eles cobrem a maior parte do que os componentes precisam.
RxJS e observables
O Angular possui uma integração profunda com RxJS. Um observable representa um fluxo de valores ao longo do tempo, e os operadores permitem que você transforme e combine esses fluxos.
// search.component.ts
import { Component } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { debounceTime, distinctUntilChanged, switchMap } from "rxjs";
@Component({ /* ... */ })
export class SearchComponent {
private query$ = new Subject<string>();
results = toSignal(
this.query$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((q) => this.api.search(q)),
),
{ initialValue: [] },
);
}
É aqui que o RxJS mostra seu valor: debouncing, cancelamento de requisições obsoletas e a combinação de fluxos são difíceis de expressar usando apenas promises. Para estados síncronos simples, signals são mais simples. O Angular moderno permite que você misture ambos e realize a conversão com toSignal e toObservable.
Serviços e injeção de dependência
Lógicas e dados compartilhados pertencem a serviços, não a componentes. A injeção de dependência os fornece onde quer que sejam necessários.
// users.service.ts
import { Injectable, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
@Injectable({ providedIn: "root" })
export class UsersService {
private http = inject(HttpClient);
getAll() {
return this.http.get<User[]>("/api/users");
}
}
// users.component.ts
export class UsersComponent {
private users = inject(UsersService);
list = toSignal(this.users.getAll(), { initialValue: [] });
}
providedIn: "root" registra um singleton para todo o app. A DI torna a substituição de serviços simples em testes e mantém os componentes focados na apresentação.
Roteamento
O Angular Router mapeia URLs para componentes e suporta rotas aninhadas, guards, resolvers e lazy loading.
// app.routes.ts
import { Routes } from "@angular/router";
export const routes: Routes = [
{ path: "", component: HomeComponent },
{ path: "users/:id", component: UserProfileComponent },
{
path: "admin",
loadChildren: () => import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
},
];
O recurso de lazy loading mantém o bundle inicial pequeno, e os guards permitem bloquear a navegação até que uma condição, como a autenticação, seja atendida.
Formulários e HTTP
O Angular possui duas abordagens para formulários. Reactive forms definem o modelo e os validadores na classe, o que escala bem e é fácil de testar. Template-driven forms utilizam diretivas como ngModel e são mais rápidos para casos simples.
// signup.component.ts
form = new FormGroup({
email: new FormControl("", [Validators.required, Validators.email]),
password: new FormControl("", [Validators.required, Validators.minLength(8)]),
});
submit() {
if (this.form.invalid) return;
this.http.post("/api/signup", this.form.getRawValue()).subscribe();
}
HttpClient retorna observables, portanto as requisições são compostas com operadores RxJS para tentativas de reenvio (retries), cancelamento e tratamento de erros.
Melhores práticas
- Prefira standalone components e signals para códigos novos.
- Mantenha os componentes focados na apresentação e mova a lógica para services.
- Use reactive forms para qualquer coisa que exija validação ou campos dinâmicos.
- Utilize lazy-load em áreas de funcionalidades para manter o bundle inicial pequeno.
- Use a detecção de mudanças
OnPushou signals para limitar verificações desnecessárias. - Rastreie itens em
@forcom um valor único estável. - Faça o unsubscribe de observables de longa duração, ou utilize o async pipe e
toSignal.
Erros comuns
- Colocar a lógica de negócio em componentes em vez de serviços.
- Fazer a inscrição (subscribe) manualmente e esquecer de cancelar a inscrição, causando memory leaks.
- Tentar usar RxJS quando um signal seria mais simples.
- Mutar o valor de um signal diretamente em vez de usar
setouupdate. - Criar um único NgModule enorme em vez de rotas de funcionalidades com lazy-loading.
- Tratar o Angular como se fosse React e lutar contra a estrutura do framework.
Próximos passos
O Angular recompensa a paciência com uma estrutura que se mantém sólida ao longo dos anos. Aprofunde seus conhecimentos em TypeScript, entenda como ele se compara ao React e ao Vue, e aprenda sobre as ferramentas de Node.js que alimentam sua CLI. Em seguida, crie uma funcionalidade com routing, um service e um reactive form para ver como todas as peças se encaixam.