Angular Signals: Guia Completo
- #Angular
Quando o Angular 17 foi lançado com os signals do Angular, muitos desenvolvedores ficaram empolgados. Eu, particularmente, também estava animado com isso. Eles são reativos por natureza e possuem esse design produtor-consumidor. Eles vêm com uma interface amigável para os desenvolvedores modificarem facilmente os dados no framework. A característica mais empolgante é a capacidade de aprimorar a eficiência na detecção de mudanças e atualização de conteúdo, além de lidar com complexidades que antes eram desafiadoras.
Entendendo o Signals
Com o signals o Angular encontrou uma maneira de informar a outros códigos que algo mudou nos dados.
- No Angular, os signals são um tipo específico de Observables projetado para otimizar a detecção de mudanças em dados assíncronos.
Agora você pode estar tentado a perguntar: Signals e Observables, eles são a mesma coisa?
Quando na verdade o que devemos perguntar é: quais problemas os Signals e Observables se propõem a resolver?
A resposta é direta:
- Realizar tarefas que ocorrem independentemente umas das outras.
- Observables têm emissores que emitem valores, semelhante a torres de sinal transmitindo mensagens.
- Observables funcionam como fluxos dinâmicos de eventos em um aplicativo, encapsulando interações do usuário, dados de APIs ou eventos baseados no tempo.
Como o Signals Funciona?
Bem, O Angular.io descreve os Signals como invólucros. É explicado que os signals envolvem um valor. Para simplificar, é como uma casca de ovo que segura a gema do ovo envolta nela. Os signals do Angular envolvem um valor (mantêm um valor) e, em seguida, notificam o usuário sobre quaisquer alterações. Para ler o valor de um signal, você precisa de uma função especial chamada getter. Existem dois tipos de signals: Sinais Graváveis e Sinais Computados (somente leitura):
import { Component, signal } from ‘@angular/core’;
@Component({
selector: ‘app- signal - example’,
template: `
<div>
<p>Valor atual: {{ mySignalValue() }}</p>
<button (click)=”setNewValue()”>Novo Valor</button>
<button (click)=”updateValue()”>Atualizar Valor</button>
</div>
`,
})
export class SignalExampleComponent {
mySignal = signal({ foo: ‘bar’ });
setNewValue() {
this.mySignal.set({ foo: ‘bar1’ });
}
updateValue() {
const currentValue = this.mySignal();
this.mySignal.set({ …currentValue, foo: currentValue.foo + ‘1’ });
}
}
Signals Computáveis
Os signals computáveis são derivados de outros signals usando uma função de derivação. Eles permitem que você crie valores dinâmicos com base em signals existentes. Por exemplo, você pode definir um sinal computado da seguinte forma:
import { signal, computed } from "@angular/core";
const countSignal = signal(0);
const doubleCount = computed(() => countSignal() * 2);
// O sinal doubleCount depende do countSignal.
Armadilhas ao Criar Signals Computáveis
Ao trabalhar com signals computáveis no Angular, é importante ser cauteloso sobre como as dependências são reconhecidas.
- Angular só identifica uma dependência de signal quando é explicitamente usada no processo de computação de outro signal.
- A armadilha surge quando um signal contribui indiretamente para o valor final de outro signal, mas a dependência não é explicitamente declarada na função de computação. Nesses casos, o Angular pode não conseguir registrá-lo como uma dependência, resultando em atualizações para a dependência “oculta” que não desencadeiam atualizações no signal computado.
- Por exemplo, se `SignalA` contribuir para o cálculo de `ComputedSignalB`, mas a relação não for explicitamente declarada na função de computação de `ComputedSignalB`, quaisquer alterações em `SignalA` podem não se propagar para `ComputedSignalB`. Isso pode levar a um comportamento inesperado e cálculos incorretos.
- Para mitigar esse problema, é importante garantir que todas as dependências relevantes sejam explicitamente declaradas dentro das funções de computação. Ao fazer isso, o Angular pode identificar com precisão os relacionamentos e manter a reatividade esperada em seu aplicativo.
Dependência Em Signals
Em Angular, a dependência do signal refere-se à relação entre dois signals em que um signal (o signal dependente) depende do valor atual de outro signal (o signal de dependência) para calcular seu próprio valor.
Essa dependência permite atualizações automáticas no signal dependente sempre que o signal de dependência muda, simplificando o gerenciamento de dados inter-relacionados dentro de um aplicativo Angular.
Isso permite uma comunicação eficiente e o fluxo de dados entre diferentes partes do seu aplicativo (componentes, serviços, diretivas).
import { Component, signal } de '@angular/core';
// Defina o tipo OrderStatus (você pode ajustar isso com base em seus requisitos reais)
type OrderStatus = ‘placed’ | ‘cooking’ | ‘delivered’;
@Component({
seletor: 'app-computed-example',
modelo: `
<div>
<p>Status do pedido: {{ orderStatusValue() }}</p>
<p>Status da Preparação de Alimentos: {{ prepareFoodValue() }}</p>
</div>
`,
})
export class ComputedExampleComponent {
// Crie um signal de escrita para o status do pedido
orderStatus = signal<OrderStatus>('colocado');
// Crie um signal computado (derivado do status do pedido) para preparação de alimentos
prepareFoodValue = computed(() => {
status de retorno === 'colocado' ? 'preparação' : 'ocioso'
});
construtor() {
// Atualizar o status do pedido aciona o recálculo do prepareFoodValue
this.orderStatus.set('cozinhar');
}
}
Aqui, `prepareFoodSignal` depende do valor de `orderStatusSignal`. Quando `orderStatusSignal` é atualizado, `prepareFoodSignal` é automaticamente recomputado para refletir a mudança.
Adições Dinâmicas e Remoção de Dependência
import { Component, signal } de '@angular/core';
@Component({
seletor: 'app-profile',
modelo: `
<div>
<p>Usuário Logado: {{ userLoggedIn() }}</p>
<p>Mostrar Perfil: {{ showProfileValue() }}</p>
</div>
`,
})
export class ProfileComponent {
// Crie um signal gravável para o estado de login do usuário
userLoggedIn = signal<boolean>(falso);
// Crie um signal calculado (derivado do estado de login do usuário) para mostrar o perfil
showProfileValue = computed(() => {
retornar logado? 'Sim' : 'Não'
});
construtor() {
// Simule o login do usuário (você pode substituir isso pela lógica de login real)
this.userLoggedIn.set(true);
// Quando o usuário fizer login, adicione dependência dos dados do usuário (simulado pelo userDataSignal)
computado(() => {
if (this.userLoggedIn()) {
// Adicionar dependência
} mais {
// Remover dependência }
})
}
}
// Defina o tipo UserData (você pode ajustar isso com base em seus requisitos reais)
tipo UserData = {
id: número;
nome: string;
e-mail: string;
};
- Criamos um componente Angular chamado `ProfileComponent`.
- O signal `userLoggedIn` representa o estado de login do usuário (inicializado com `false`).
- O `showProfileValue` é um signal calculado derivado do estado de login do usuário. Ele exibe “Sim” se o usuário estiver logado e “Não” caso contrário.
- No construtor, simulamos o login do usuário definindo`userLoggedIn.set(true)`.
- O exemplo demonstra como adicionar ou remover dinamicamente dependências com base em condições (embora, neste caso, na verdade não adicionemos ou removamos dependências).
Trabalhando com Signals e Matrizes/Valores de Objeto
// Signal para itens de carrinho de compras (matriz de strings)
const cartItemsSignal = signal<string[]>([]);
// Não mude a matriz diretamente (má prática)
cartItemsSignal()[0] = 'maçãs'; // Evite isso!
// Adicione um novo item usando métodos adequados (boa prática)
cartItemsSignal.update(items => [...items, 'pão']);
Aqui, demonstramos evitar a mutação direta da matriz dentro do signal e, em vez disso, usar o método `update` para adicionar um novo item, mantendo a imutabilidade.
Verificação de Igualdade de Signals de Substituição para Valores Complexos
- Por padrão, os signals (observables RxJS) usam a função Object.is para verificações de igualdade.
- Somente objetos com exatamente a mesma referência acionam a detecção de alterações.
- Modificar propriedades dentro de um objeto existente não será considerado uma alteração, pois a referência do objeto permanece a mesma.
- Para lidar com objetos complexos (estruturas de dados personalizadas), crie uma função de igualdade personalizada.
- Esta função deve realizar uma comparação profunda para detectar mudanças significativas dentro de propriedades ou matrizes aninhadas.
// app.component.ts
import { Component } from '@angular/core';
@Component({
seletor: 'app-root',
modelo: `
<div>
<p>Os objetos são iguais: {{ areObjectsEqual }}</p>
</div>
`,
})
export class AppComponent {
myObject = signal({
name: “Kauê”
}, {
equals: (a, b) => a.name === b.name
})
}
Aqui, veja que substituímos a verificação de igualdade do signal, fornecendo a nossa na propriedade de iguais no objeto passado como um segundo parâmetro para a função de signal.
Nossa função de igualdade personalizada está verificando a igualdade na propriedade de nome do valor myObject.
Detectando Alterações de Signal com a API effect()
A API `effect()` permite que você reaja a alterações em um signal. É necessária uma função de retorno de chamada que é executada sempre que o valor do signal é atualizado.
const productPriceSignal = signal<número>(10);
effect(() => {
console.log('Preço do produto alterado para:', productPriceSignal());
});
productPriceSignal.set(20); // Aciona o retorno de chamada do efeito
Este exemplo registra o preço do novo produto sempre que o `productPriceSignal` é atualizado.
Em Resumo
Nós mergulhamos profundamente nos Signals, um conceito revolucionário que injeta capacidade de resposta em seus aplicativos. Longe vão os dias dos contêineres de dados simples; Os signals atuam como mensageiros inteligentes, notificando eficientemente o Angular sempre que as informações mudam.
Exploramos dois tipos de Signal:
- Signals graváveis: Estes são signals que podem ser atualizados diretamente, ideais para cenários em que você precisa atribuir um valor e prosseguir.
- Signals Computáveis: Estes são signals que dependem de outros signals. Quando uma dependência muda, o signal computado reavalia seu valor, causando um efeito em cascata que atualiza qualquer coisa que dependa dele.
Construir aplicativos reativos não é sem seus desafios. Nós abordamos possíveis armadilhas, como lidar com a lógica condicional e adicionar ou remover dependências dinamicamente. Ao permanecer vigilante, você pode garantir que seus signals fluam sem problemas e evitar comportamentos inesperados.
Signals e matrizes/objetos andam de mãos dadas. Enfatizamos a importância de tratá-los com cuidado. Técnicas como .set() e .update() garantem que você modifique os dados de maneira previsível, mantendo seus signals felizes. Para objetos complexos, até abordamos a personalização da verificação de igualdade para detecção precisa de alterações.
Finalmente, introduzimos a API effect(), uma ferramenta poderosa que permite que você reaja a mudanças de signal. Pense nisso como um detetive constantemente à procura, pronto para entrar em ação sempre que o valor de um signal mudar.
Este guia abrangente equipa você para transformar o desempenho do seu aplicativo. Ao abraçar os Signals, você desbloqueará um mundo de:
- Dependências claras e canais de comunicação bem definidos levam a um código mais limpo.
- Os signals são extremamente rápidos em reagir a mudanças, mantendo sua interface do usuário atualizada e dinâmica.
- As atualizações DOM tornam-se focadas no laser, minimizando re-renderizações desnecessárias e mantendo seu aplicativo suave e eficiente.