Angular Signals e Computed em 2026: Reatividade Granular e Perguntas de Entrevista
Dominar Signals do Angular, computed, effect e linkedSignal no Angular 20+. Padrões de reatividade fina e preparação para entrevistas técnicas.

Os Signals do Angular representam a mudança mais significativa no modelo de reatividade do framework desde sua criação. Introduzidos no Angular 17 e agora a abordagem padrão no Angular 20+, os signals fornecem reatividade granular que elimina ciclos desnecessários de detecção de mudanças e integra-se perfeitamente com aplicações zoneless.
Os signals são primitivas reativas síncronas e livres de glitches. Um signal computed recalcula apenas quando suas dependências mudam e apenas uma vez por ciclo de mudança, independentemente de quantas vezes seja lido.
Entendendo as Primitivas Signal no Angular 20
O sistema de signals do Angular consiste em três primitivas principais: signal(), computed() e effect(). Cada uma serve a um propósito distinto no grafo reativo.
Um signal gravável contém um valor que pode ser atualizado com .set() ou .update(). A leitura do signal retorna o valor atual, e quaisquer signals computed ou effects que dependam dele são notificados sobre as mudanças.
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Count: {{ count() }}</p>
<p>Double: {{ doubleCount() }}</p>
<button (click)="increment()">+1</button>
`
})
export class CounterComponent {
// Writable signal with initial value
count = signal(0);
// Computed signal derives from count
// Only recalculates when count changes
doubleCount = computed(() => this.count() * 2);
increment(): void {
// .update() receives current value as argument
this.count.update(c => c + 1);
}
}A diferença principal do BehaviorSubject do RxJS: signals são síncronos e livres de glitches. Quando count muda, doubleCount recalcula exatamente uma vez, mesmo se lido múltiplas vezes no mesmo bloco síncrono.
Signals Computed: Avaliação Preguiçosa e Memoização
Signals computed armazenam em cache seu resultado até que uma dependência mude. Essa memoização é automática, diferentemente do RxJS onde shareReplay ou distinctUntilChanged devem ser adicionados manualmente.
import { Component, signal, computed } from '@angular/core';
interface User {
id: number;
name: string;
purchases: number[];
}
@Component({
selector: 'app-user-stats',
standalone: true,
template: `
<div>
<p>User: {{ user().name }}</p>
<p>Total spent: {{ totalSpent() | currency }}</p>
<p>Average purchase: {{ averagePurchase() | currency }}</p>
</div>
`
})
export class UserStatsComponent {
user = signal<User>({
id: 1,
name: 'Alice',
purchases: [99.99, 149.50, 29.99]
});
// Computed tracks only the properties it reads
totalSpent = computed(() => {
const purchases = this.user().purchases;
return purchases.reduce((sum, p) => sum + p, 0);
});
// Depends on another computed signal
averagePurchase = computed(() => {
const total = this.totalSpent();
const count = this.user().purchases.length;
return count > 0 ? total / count : 0;
});
}Um signal computed é avaliado de forma preguiçosa: a função de cálculo executa apenas quando o signal é lido pela primeira vez, não quando declarado. Leituras subsequentes retornam o valor em cache até que uma dependência o invalide.
Effect: Efeitos Colaterais no Grafo Reativo
Effects executam código em resposta a mudanças de signals. Eles executam de forma assíncrona durante o ciclo de detecção de mudanças, após todas as atualizações síncronas serem completadas.
import { Component, signal, effect, inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
@Component({
selector: 'app-theme-sync',
standalone: true,
template: `
<button (click)="toggleTheme()">Toggle Theme</button>
`
})
export class ThemeSyncComponent {
private document = inject(DOCUMENT);
isDarkMode = signal(false);
constructor() {
// Effect syncs signal state to DOM attribute
effect(() => {
const theme = this.isDarkMode() ? 'dark' : 'light';
this.document.documentElement.setAttribute('data-theme', theme);
});
// Effect for localStorage persistence
effect(() => {
localStorage.setItem('theme', this.isDarkMode() ? 'dark' : 'light');
});
}
toggleTheme(): void {
this.isDarkMode.update(dark => !dark);
}
}Effects rastreiam dependências dinamicamente. Se um branch condicional não é tomado, signals lidos apenas naquele branch não são rastreados até que a condição mude.
Usar effect para copiar dados de um signal para outro indica um problema de design. É preferível usar computed para estado derivado ou linkedSignal quando o valor derivado precisa ser modificável.
linkedSignal: Estado Derivado Modificável
Introduzido no Angular 19 e estável no Angular 20, linkedSignal cria um signal modificável que reinicia quando sua fonte muda. Ele resolve cenários onde computed é somente leitura, mas o valor derivado precisa de modificação local.
import { Component, signal, linkedSignal, computed } from '@angular/core';
@Component({
selector: 'app-pagination',
standalone: true,
template: `
<select (change)="pageSize.set(+$any($event.target).value)">
<option [value]="10">10 per page</option>
<option [value]="25">25 per page</option>
<option [value]="50">50 per page</option>
</select>
<p>Page {{ currentPage() }} of {{ totalPages() }}</p>
<button (click)="prevPage()" [disabled]="currentPage() === 1">Prev</button>
<button (click)="nextPage()" [disabled]="currentPage() === totalPages()">Next</button>
`
})
export class PaginationComponent {
totalItems = signal(243);
pageSize = signal(10);
totalPages = computed(() =>
Math.ceil(this.totalItems() / this.pageSize())
);
// linkedSignal resets to 1 when pageSize or totalItems changes
// but allows manual navigation via prevPage/nextPage
currentPage = linkedSignal(() => 1);
prevPage(): void {
this.currentPage.update(p => Math.max(1, p - 1));
}
nextPage(): void {
this.currentPage.update(p => Math.min(this.totalPages(), p + 1));
}
}Sem linkedSignal, esse padrão exigia um effect escrevendo para outro signal (anti-padrão) ou orquestração RxJS complexa.
Pronto para mandar bem nas entrevistas de Angular?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
APIs de Componentes Baseadas em Signals: input, model, viewChild
Angular 20 promove as APIs de componentes baseadas em signals para status estável. Estas substituem decoradores por alternativas reativas que integram-se ao grafo de signals.
import {
Component,
input,
model,
output,
computed,
viewChild,
ElementRef
} from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<div #card class="card" [class.selected]="selected()">
<h3>{{ fullName() }}</h3>
<button (click)="toggle()">{{ selected() ? 'Deselect' : 'Select' }}</button>
</div>
`
})
export class UserCardComponent {
// Signal input replaces @Input()
firstName = input.required<string>();
lastName = input<string>('');
// model() creates two-way bindable signal
// Parent uses [(selected)]="parentSignal"
selected = model(false);
// Output emitter for events
cardClicked = output<void>();
// viewChild returns Signal<ElementRef | undefined>
cardElement = viewChild<ElementRef>('card');
// Computed derives from signal inputs
fullName = computed(() =>
`${this.firstName()} ${this.lastName()}`.trim()
);
toggle(): void {
this.selected.update(s => !s);
this.cardClicked.emit();
}
}Inputs baseados em signals fornecem melhor segurança de tipos: input.required() retorna InputSignal<T> enquanto input() retorna InputSignal<T | undefined> a menos que um valor padrão seja fornecido.
Signals vs RxJS: Performance Sob Carga
Benchmarks no Angular 21+ demonstram que signals reduzem a sobrecarga de detecção de mudanças em cenários intensivos de UI. Sob carga pesada com atualizações frequentes, signals mostraram perfis de memória mais limpos e menor uso de CPU comparado a bindings orientados por RxJS.
A separação de responsabilidades é clara:
| Caso de Uso | Abordagem Recomendada |
|---|---|
| Estado local do componente | signal() |
| Valores UI derivados | computed() |
| Sincronização com APIs externas | effect() |
| Requisições HTTP | Observable RxJS + toSignal() |
| Streams WebSocket | RxJS com operadores |
| Input de usuário com debounce | RxJS debounceTime, depois toSignal() |
| Orquestração assíncrona complexa | Pipelines RxJS |
RxJS continua sendo a ferramenta para streams assíncronas, cancelamento e contrapressão. Signals gerenciam estado síncrono.
Interoperabilidade RxJS: toSignal e toObservable
Angular fornece utilitários em @angular/core/rxjs-interop para fazer a ponte entre signals e observables.
import { Component, signal, inject } from '@angular/core';
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { debounceTime, switchMap, distinctUntilChanged } from 'rxjs';
interface SearchResult {
id: number;
title: string;
}
@Component({
selector: 'app-search',
standalone: true,
template: `
<input
[value]="query()"
(input)="query.set($any($event.target).value)"
placeholder="Search..."
/>
@if (results(); as items) {
<ul>
@for (item of items; track item.id) {
<li>{{ item.title }}</li>
}
</ul>
}
`
})
export class SearchComponent {
private http = inject(HttpClient);
query = signal('');
// Convert signal to observable for RxJS operators
private query$ = toObservable(this.query);
// RxJS pipeline with debounce, then back to signal
results = toSignal(
this.query$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(q => q.length > 2
? this.http.get<SearchResult[]>(`/api/search?q=${q}`)
: []
)
),
{ initialValue: [] as SearchResult[] }
);
}toSignal se inscreve quando criado e cancela a inscrição quando o componente é destruído. A opção initialValue elimina o tipo undefined do signal.
Perguntas de Entrevista: Angular Signals
Entrevistas técnicas em 2026 frequentemente testam o entendimento de signals. Estes são os padrões que distinguem candidatos seniores.
P: O que acontece se um signal computed lança um erro?
O erro se propaga para qualquer effect ou computed que o leia. Angular não armazena erros em cache, o cálculo tenta novamente na próxima leitura. Código de produção deve tratar erros dentro da função computed.
P: É possível escrever em um signal dentro de um computed?
Não. Tentar chamar .set() ou .update() dentro de um computed lança um erro. Funções computed devem ser puras. Deve-se usar linkedSignal se for necessário um valor derivado que também seja modificável.
P: Como effects tratam leituras de signals em condicionais?
// Dynamic dependency tracking example
const showDetails = signal(false);
const userDetails = signal({ name: 'Alice', email: 'alice@example.com' });
effect(() => {
if (showDetails()) {
// userDetails is only tracked when showDetails is true
console.log('Details:', userDetails().email);
}
});Effects rastreiam dependências baseados na execução mais recente. Se showDetails é false, mudanças em userDetails não disparam o effect.
P: Como funciona a igualdade em signals?
Por padrão, signals usam Object.is para igualdade. Para objetos, isso significa igualdade de referência. Igualdade personalizada pode ser fornecida:
const user = signal(
{ id: 1, name: 'Alice' },
{ equal: (a, b) => a.id === b.id }
);Candidatos que mencionam que signals computed são memoizados e avaliados de forma preguiçosa demonstram entendimento além do uso básico da API. Discutir quando usar linkedSignal vs computed mostra familiaridade com os padrões do Angular 19+.
Detecção de Mudanças: Signals e Angular Zoneless
Angular 22 usa por padrão detecção de mudanças zoneless para novos projetos. Signals são centrais para isso: eles notificam o Angular precisamente quando o estado muda, eliminando a necessidade do Zone.js de interceptar APIs assíncronas.
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-zoneless-counter',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<p>Count: {{ count() }}</p>
<button (click)="increment()">+1</button>
`
})
export class ZonelessCounterComponent {
count = signal(0);
increment(): void {
// Signal update triggers change detection
// No Zone.js required
this.count.update(c => c + 1);
}
}Com signals, Angular rastreia exatamente quais componentes dependem de qual estado. Uma atualização de signal agenda detecção de mudanças apenas para componentes afetados, não a árvore inteira.
API Resource: Dados Assíncronos como Signals
A API Resource do Angular 20 (resource() e rxResource()) carrega dados assíncronos de forma declarativa em signals, substituindo padrões de subscrição manual.
import { Component, signal, computed } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
standalone: true,
template: `
@if (userResource.isLoading()) {
<p>Loading...</p>
}
@if (userResource.error()) {
<p>Error: {{ userResource.error() }}</p>
}
@if (userResource.value(); as user) {
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
}
`
})
export class UserProfileComponent {
private http = inject(HttpClient);
userId = signal(1);
// Declarative async resource
// Refetches when userId changes
userResource = rxResource({
request: () => this.userId(),
loader: ({ request: id }) =>
this.http.get<User>(`/api/users/${id}`)
});
loadUser(id: number): void {
this.userId.set(id);
}
}O resource rastreia automaticamente estado de carregamento, erros e o valor resolvido. Ele recarrega quando o signal de requisição muda.
Melhores Práticas para Arquitetura Signal
Estruturar signals em aplicações maiores requer disciplina. Estes padrões escalam de componentes a serviços.
Colocalizar signals relacionados: Agrupar signals que mudam juntos. Um componente de formulário mantém seus signals de campos juntos, não espalhados em serviços.
Elevar estado compartilhado para serviços: Quando múltiplos componentes precisam do mesmo estado, mover o signal para um serviço injetável:
import { Injectable, signal, computed } from '@angular/core';
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
@Injectable({ providedIn: 'root' })
export class CartService {
private items = signal<CartItem[]>([]);
// Expose read-only signal to consumers
readonly cartItems = this.items.asReadonly();
readonly totalPrice = computed(() =>
this.items().reduce((sum, item) => sum + item.price * item.quantity, 0)
);
readonly itemCount = computed(() =>
this.items().reduce((sum, item) => sum + item.quantity, 0)
);
addItem(item: Omit<CartItem, 'quantity'>): void {
this.items.update(items => {
const existing = items.find(i => i.id === item.id);
if (existing) {
return items.map(i =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
);
}
return [...items, { ...item, quantity: 1 }];
});
}
}Preferir computed a effects: Se um valor pode ser derivado, usar computed. Effects são para efeitos colaterais que saem do grafo reativo: manipulação do DOM, localStorage, analytics, requisições de rede.
Migrando de RxJS para Signals
Codebases Angular legadas podem migrar incrementalmente. Os utilitários de interoperabilidade RxJS permitem que signals e observables coexistam.
// Before: RxJS-based
@Component({ ... })
export class LegacyComponent {
private userSubject = new BehaviorSubject<User | null>(null);
user$ = this.userSubject.asObservable();
userName$ = this.user$.pipe(
map(u => u?.name ?? 'Guest')
);
}
// After: Signal-based
@Component({ ... })
export class ModernComponent {
user = signal<User | null>(null);
userName = computed(() => this.user()?.name ?? 'Guest');
}Para operações assíncronas, manter RxJS para o pipeline e converter na fronteira:
// Hybrid approach
private search$ = toObservable(this.searchQuery).pipe(
debounceTime(300),
switchMap(q => this.api.search(q))
);
results = toSignal(this.search$, { initialValue: [] });Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Preparação para Perguntas de Entrevista sobre Angular Signals
- Signals são primitivas síncronas; RxJS trata streams assíncronas
- Signals computed são preguiçosos e memoizados, recalculando apenas quando dependências mudam
- Effects executam assincronamente durante detecção de mudanças, não imediatamente
- linkedSignal resolve o problema de estado derivado modificável sem effects
- Inputs signal (
input(),model()) substituem decoradores por alternativas reativas toSignaletoObservablefazem ponte entre signals e RxJS para migração incremental- Angular zoneless depende de signals para detecção de mudanças granular
- A API Resource (
rxResource) gerencia estado assíncrono de forma declarativa - O guia oficial de signals do Angular continua sendo a referência autoritativa para detalhes da API e melhores práticas
Você saberia encontrar o bug em Angular?
Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Escrito por
Anthony Fillion-MailletFundador da SharpSkill
Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.
Atualizado em 17 de setembro de 2026
Tags
Compartilhar
Artigos relacionados

Perguntas de entrevista Angular 19: Signals, SSR e conceitos essenciais
As perguntas de entrevista Angular 19 mais comuns: Signals, hidratação incremental, detecção de mudanças sem Zone.js e novas APIs reativas com exemplos de código e respostas esperadas.

Injeção de Dependências Avançada no Angular 2026: Providers, Tokens e Perguntas de Entrevista
Dominar a injeção de dependências no Angular com InjectionToken, injetores hierárquicos, modificadores de resolução e multi-providers. Inclui perguntas de entrevista e padrões de produção.

Sintaxe de controle de fluxo no Angular 2026: @if, @for, @switch e perguntas de entrevista
Guia completo sobre a sintaxe de controle de fluxo do Angular com @if, @for e @switch. Aprenda a substituir as diretivas estruturais por blocos de controle modernos e prepare-se para entrevistas técnicas.