Experts in Angular

SignalsA Revolução dos Sinais em Angular: Domine o Poder da Reatividade
Domine o Poder da Reatividade

A Revolução dos Sinais em Angular: Domine o Poder da Reatividade

Angular, o titã do desenvolvimento web, sempre buscou a perfeição na reatividade. Em sua busca incansável pela performance e elegância, surge uma nova arma no arsenal do Angular: os Sinais. Como um farol na noite, os Sinais iluminam o caminho para uma nova era da reatividade em Angular, prometendo otimizações e um controle granular sobre o estado da sua aplicação.

Prepare-se para desvendar os segredos dos Sinais e liberar todo o potencial da reatividade em seus projetos Angular. Abrace essa revolução e conquiste a maestria no desenvolvimento web.

O Que São Sinais?

Em sua essência, um Sinal em Angular é um invólucro que envolve um valor e notifica os interessados sempre que esse valor se transforma. Seja um número simples ou uma intrincada estrutura de dados, os Sinais podem conter qualquer valor, adaptando-se às suas necessidades.

A chave para acessar o valor de um Sinal reside em sua função getter. Ao invocá-la, você desvenda o valor contido no Sinal e permite que o Angular rastreie onde esse Sinal é utilizado, abrindo as portas para otimizações de renderização.

Os Sinais podem ser mutáveis, permitindo que você os modifique, ou imutáveis, mantendo seu valor intocado. Essa flexibilidade oferece um controle preciso sobre o comportamento dos seus Sinais, moldando-os de acordo com os requisitos da sua aplicação.

A Ascensão dos Sinais em Angular

Apresentados em Angular 17, os Sinais surgem como um divisor de águas na reatividade do framework.

Sua missão é clara: fornecer aos desenvolvedores uma primitiva reativa intuitiva e poderosa para construir aplicações com elegância e eficiência.

Com uma API simples e direta, os Sinais permitem que você comunique as mudanças de dados ao framework com facilidade, abrindo as portas para otimizações de detecção de mudanças e renderização que antes eram inatingíveis.

Imagine um mundo onde o Angular sabe exatamente quais partes da sua página precisam ser atualizadas e age com precisão cirúrgica, atualizando apenas o necessário e nada mais. Essa é a promessa dos Sinais, um salto quântico em relação à detecção de mudanças tradicional, que muitas vezes varre todos os componentes em busca de alterações, mesmo quando os dados que eles consomem permanecem inalterados.

Os Sinais não apenas impulsionam a performance da sua aplicação ao eliminar o Zone.js, mas também oferecem uma maneira elegante e intuitiva de construir aplicações reativas em geral.

Ao contrário do RxJS, que pode ser complexo e desafiador, os Sinais apresentam uma API mais acessível e fácil de entender, facilitando a propagação de mudanças de dados em toda a sua aplicação.

Os sinais são uma mudança de paradigma e levam a reatividade no Angular a um novo nível!

Prepare-se para a Jornada

Nesta jornada épica, desvendaremos os mistérios da API dos Sinais, exploraremos seu funcionamento e revelaremos os desafios que você pode encontrar pelo caminho. Prepare-se para dominar o poder da reatividade em Angular e elevar suas aplicações a um novo patamar de excelência.

Começando Simples

Para entender melhor os sinais, vamos começar com um exemplo simples que ainda não usa sinais e, em seguida, reescrever o mesmo exemplo usando a API dos Sinais.

Para começar, digamos que temos um componente Angular simples com uma variável de contador:

@Component({
  selector: 'app',
  template: `
    <h1>Valor atual do contador: {{counter}}</h1>
    <button (click)="increment()">Incrementar</button>
  `
})
export class AppComponent {
  counter: number = 0;

  increment() {
    this.counter++;
  }
}

Como você pode ver, este é um componente muito simples.

Ele apenas exibe um valor de contador e tem um botão para incrementar o contador. Este componente usa o mecanismo de detecção de mudanças padrão do Angular.

Isso significa que a expressão {{counter}}, bem como qualquer outra expressão na página, está sendo verificada quanto a mudanças após cada evento, como um clique no botão de incrementar.

Como você pode imaginar, isso pode ser uma maneira potencialmente ineficaz de tentar detectar o que precisa ser atualizado na página.

No caso do nosso componente, essa abordagem é até necessária, porque estamos usando uma variável de membro JavaScript mutável simples como o contador para armazenar nosso estado.

Portanto, quando ocorre um evento, praticamente qualquer coisa na página pode ter afetado esses dados.

Além disso, o clique no botão de incrementar poderia facilmente ter desencadeado mudanças em qualquer outra parte da página também, e não apenas dentro deste componente.

Imagine, por exemplo, que chamássemos um serviço compartilhado que afetasse várias partes da página.

Com a detecção de mudanças padrão, não há como o Angular saber exatamente o que mudou na página, por isso não podemos fazer suposições sobre o que aconteceu e precisamos verificar tudo!

Como não temos garantias sobre o que poderia ou não ter mudado, precisamos escanear toda a árvore de componentes e todas as expressões em cada componente.

Com a detecção de mudanças padrão, não há outra maneira.

Mas é aqui que os sinais vêm para o resgate!

Aqui está o mesmo exemplo simples, mas desta vez reescrito usando a API dos Sinais:

@Component({
  selector: 'app',
  template: `
    <h1>Valor atual do contador: {{counter()}}</h1>
    <button (click)="increment()">Incrementar</button>
  `
})
export class AppComponent {
  counter = signal(0);

  constructor() {
    console.log(`Valor do contador: ${this.counter()}`)
  }

  increment() {
    console.log(`Atualizando contador...`)
    this.counter.set(this.counter() + 1);
  }
}

Como você pode ver, a versão baseada em sinal do nosso componente não parece tão diferente.

A principal diferença é que agora estamos encapsulando nosso valor de contador em um sinal usando a API signal(), em vez de usar uma variável de membro JavaScript simples.

Este sinal representa o valor de um contador, que começa em zero.

Podemos ver que o sinal age como um contêiner para o valor que queremos acompanhar.

Vamos nos aprofundar um pouco mais nas explicações deste exemplo simples, quero que você não apenas aprenda mas se torne um mestre!

Sinais Mutáveis(Writable Signals): Moldando a Reatividade à Sua Vontade

Os Sinais mutáveis, verdadeiros mestres da transformação, concedem o poder de alterar seus valores à vontade. Ao conjurar um Sinal mutável com a função signal, você define seu valor inicial, como um escultor que molda a argila bruta:

const count = signal(0); // O Sinal `count` inicia sua jornada com o valor 0.

Mas a magia dos Sinais mutáveis não para por aí. Para desvendar o valor que eles guardam, basta invocá-los como uma função, sem argumentos, e o véu se erguerá, revelando o segredo:

console.log('O valor do contador é: ' + count()); // A mensagem "O valor do contador é: 0" será exibida.

E quando a necessidade de mudança se impõe, os Sinais mutáveis oferecem duas armas poderosas para moldar seu destino: o método .set() e o método .update().

Com o .set(), você impõe um novo valor ao Sinal, como um decreto real que reescreve a história:

count.set(3); // O Sinal `count` agora carrega o valor 3.

Já o .update(), como um artesão habilidoso, transforma o valor existente, calculando um novo valor a partir do anterior:

count.update(value => value + 1); // O valor do Sinal `count` é incrementado em 1.

Sinais Computados: A Arte da Derivação Reativa

Os Sinais computados, observadores astutos da mudança, derivam seu valor de outros Sinais. Como alquimistas que transformam chumbo em ouro, os Sinais computados utilizam a função computed para forjar um novo valor a partir dos Sinais que os alimentam:

Vamos explorar como isso funciona.

const count = signal(0);
const doubleCount = computed(() => count() * 2);

console.log('O valor de doubleCount é: ' + doubleCount()); // 0
count.set(5);
console.log('O valor atualizado de doubleCount é: ' + doubleCount()); // 10

Neste exemplo, doubleCount é um sinal computado que depende do sinal count. Quando count muda, doubleCount é recalculado automaticamente. Isso permite uma reatividade fluida e eficiente em sua aplicação.

A Sinfonia dos Sinais Computados

Imagine os Sinais como notas musicais que se entrelaçam, criando melodias complexas e harmoniosas. Os Sinais computados, como maestros da orquestra, conduzem essa sinfonia, garantindo que cada nota esteja em perfeita sincronia com as demais.

Em nosso componente AppComponent, desejamos adicionar um Sinal derivado que multiplica o valor do contador por 10. Com a função computed, criamos esse novo Sinal, derivedCounter, que se alimenta do Sinal counter:

derivedCounter = computed(() => {
    return this.counter() * 10;
});

A função computed aceita um ou mais Sinais como entrada e forja um novo Sinal a partir deles. Quando o Sinal de origem (counter) se transforma, o Sinal computado (derivedCounter) é atualizado instantaneamente, como um espelho que reflete a realidade em tempo real.

Assim, ao clicar no botão “Incrementar”, o contador inicia sua jornada em 1, e o Sinal derivado assume o valor 10. A cada clique, o contador avança, e o Sinal derivado o acompanha, sempre dez passos à frente.

Sinais Computados: Maestria na Avaliação Preguiçosa e Memoização

Os Sinais computados, como sábios monges, praticam a arte da avaliação preguiçosa e da memoização. A função de derivação de doubleCount, por exemplo, só é invocada quando o Sinal é lido pela primeira vez, como um pergaminho antigo que só revela seus segredos quando desenrolado. Uma vez calculado, o valor é armazenado em cache, como um tesouro guardado em um cofre. Leituras subsequentes de doubleCount retornam esse valor em cache, sem a necessidade de recalcular, poupando tempo e recursos.

Quando count se transforma, doubleCount é alertado de que seu valor em cache está obsoleto, como um mapa antigo que precisa ser atualizado. O recálculo só ocorre na próxima leitura de doubleCount, como um explorador que redesenha o mapa após descobrir novas terras.

Graças a essa astúcia, você pode realizar cálculos complexos em Sinais computados, como filtrar arrays, sem medo de comprometer o desempenho da sua aplicação.

Sinais Computados: Guardiões da Imortalidade

Sinais computados, como seres imortais, não podem ser alterados diretamente. Tentar atribuir um valor a um Sinal computado, como tentar mudar o curso do destino, resultará em um erro de compilação. Afinal, um Sinal computado não é um WritableSignal, e seu valor é derivado de outros Sinais, não definido por você.

Sinais Computados: Detetives da Dependência Dinâmica

Os Sinais computados são como detetives perspicazes, rastreando apenas os Sinais realmente lidos durante a derivação. Em um Sinal computado com lógica condicional, como o exemplo abaixo, o Sinal count só é lido se showCount for verdadeiro:

const showCount = signal(false);
const count = signal(0);
const conditionalCount = computed(() => {
  if (showCount()) {
    return `O contador está em ${count()}.`;
  } else {
    return 'Nada para ver aqui!';
  }
});

Se showCount for falso, a mensagem “Nada para ver aqui!” é retornada sem consultar o Sinal count. Mudanças em count não desencadeiam um novo cálculo, como um enigma que só é decifrado quando a chave correta é encontrada.

Se showCount se tornar verdadeiro e conditionalCount for lido novamente, a derivação é reexecutada e o valor de count é exibido. A partir desse momento, mudanças em count invalidam o valor em cache de conditionalCount, como um mistério que se aprofunda a cada nova pista.

Sinais Computados: A Teia da Dependência

A beleza dos Sinais computados reside em sua capacidade de rastrear automaticamente as dependências entre os Sinais. No exemplo do derivedCounter, não precisamos explicitamente assinar o Sinal counter. Basta invocar counter() dentro da função computed para estabelecer o vínculo entre os dois Sinais.

Quando o Sinal de origem (counter) se transforma, o Sinal computado (derivedCounter) é atualizado automaticamente, como um eco que se propaga pela floresta. Angular, como um guardião da floresta, conhece toda a árvore de dependências dos Sinais e sabe como a mudança em um Sinal afeta todos os outros.

Sinais Computados: A Arte da Leitura Discreta

Em raras ocasiões, você pode precisar ler o valor de um Sinal de outro Sinal computado sem criar uma dependência entre eles. Para isso, o Angular oferece a função untracked, que permite acessar o valor do Sinal sem que ele seja considerado uma dependência:

derivedCounter = computed(() => {
    return untracked(this.counter) * 10;
});

Essa técnica, como um ninja que se move nas sombras, deve ser usada com moderação. Se você se encontrar utilizando untracked com frequência, é sinal de que algo pode estar errado em sua abordagem.

Sinais Computados: A Armadilha da Lógica Condicional

Ao definir Sinais computados, é crucial ter cuidado com a lógica condicional. Se um Sinal derivado depende de um Sinal de origem, você deve garantir que o Sinal de origem seja invocado em todas as chamadas da função computed. Caso contrário, a dependência entre os dois Sinais será quebrada, como um elo de uma corrente que se rompe.

No exemplo abaixo, a lógica condicional impede que a dependência entre counter e derivedCounter seja estabelecida:

derivedCounter = computed(() => {
    if (this.multiplier < 10) {
        return 0;
    } else {
        return this.counter() * this.multiplier;
    }
});

Inicialmente, o valor de multiplier é menor que 10, então a função computed retorna 0 sem invocar counter(). Como resultado, Angular não detecta a dependência entre os dois Sinais. Ao clicar no botão “Incrementar”, o contador é atualizado, mas o derivedCounter permanece em 0, como um relógio quebrado que não acompanha o tempo.

Para corrigir esse problema, você deve garantir que counter() seja invocado em todas as chamadas da função computed:

derivedCounter = computed(() => {
    if (this.counter() == 0) {
        return 0;
    } else {
        return this.counter() * this.multiplier;
    }
});

Lembre-se de que as dependências de um Sinal computado são dinâmicas e reavaliadas a cada novo cálculo. Seja cauteloso ao usar lógica condicional e garanta que todas as dependências sejam rastreadas corretamente para evitar surpresas desagradáveis.

Sinais em Componentes OnPush: A Dança da Mudança Sincronizada

Ao ler um Sinal dentro do template de um componente OnPush, o Angular, como um coreógrafo atento, acompanha os movimentos do Sinal e o marca como dependência do componente. Quando o valor do Sinal se transforma, o Angular, com a graça de um bailarino, marca o componente para ser atualizado na próxima execução da detecção de mudanças.

Sinais e Componentes OnPush: Uma União Perfeita

Componentes OnPush são conhecidos por sua seletividade. Eles só se atualizam quando suas propriedades de entrada mudam ou quando Observables, aos quais se inscreveram com o async pipe, emitem novos valores. Mas agora, os Sinais se juntam a essa dança, tornando os componentes OnPush ainda mais responsivos.

Quando Sinais são utilizados em um componente, o Angular o marca como dependente do Sinal. Ao menor tremor do Sinal, o componente é re-renderizado, como uma folha que dança ao vento.

Vejamos um exemplo de um componente CounterComponent que utiliza um Sinal para controlar um contador:

@Component({
  selector: "counter",
  template: `
    <h1>Counter</h1>
    <p>Count: {{ count() }}</p>
    <button (click)="increment()">Increment</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CounterComponent {
  count = signal(0);

  increment() {
    this.count.update((value) => value + 1);
  }
}

Ao clicar no botão “Increment”, o componente é re-renderizado, provando a integração perfeita entre Sinais e OnPush. A necessidade de injetar ChangeDetectorRef e invocar markForCheck para atualizar o componente se torna obsoleta, como um ritual antigo substituído por uma nova tecnologia.

A combinação de sinais com a estratégia de detecção de mudanças OnPush permite que apenas as partes necessárias da UI sejam atualizadas, garantindo um desempenho superior.

Comparando com um exemplo sem Sinais, onde um intervalo atualiza um valor a cada segundo:

@Component({
  selector: "app",
  standalone: true,
  template: ` Number: {{ num }} `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class ExampleComponent {
  num = 1;

  private cdr = inject(ChangeDetectorRef);

  ngOnInit() {
    setInterval(() => {
      this.num = this.num + 1;
      this.cdr.markForCheck();
    }, 1000);
  }
}

Percebemos a simplicidade e elegância da versão com Sinais. A complexidade de injetar ChangeDetectorRef e usar markForCheck desaparece, como um nó desatado por mãos habilidosas.

Efeitos: A Magia da Ação em Resposta à Mudança

Os Sinais, como sentinelas vigilantes, alertam seus súditos sobre cada transformação. Um efeito, por sua vez, é uma operação que se desencadeia sempre que um ou mais valores de Sinais se alteram. Com a função effect, você dá vida a esses efeitos:

effect(() => {
  console.log(`O valor atual do contador é: ${count()}`);
});

Os efeitos, como um ritual ancestral, sempre são executados pelo menos uma vez. Ao despertar, o efeito rastreia as leituras dos valores dos Sinais, como um xamã que decifra os sinais da natureza. Sempre que um desses valores se transforma, o efeito é invocado novamente, como uma dança ritualística em resposta aos ciclos da lua.

Efeitos: Aplicações Práticas e Precauções

Em sua maioria, os efeitos são raramente necessários no código de uma aplicação. No entanto, em situações específicas, eles podem ser valiosos aliados. Imagine um cenário onde você precisa registrar os dados exibidos e suas mudanças, seja para fins de análise ou como uma ferramenta de depuração. Os efeitos podem ser a resposta para esse desafio.

Outras aplicações incluem manter os dados sincronizados com o window.localStorage, adicionar comportamentos personalizados ao DOM que não podem ser expressos com a sintaxe do template, realizar renderizações personalizadas em um <canvas>, biblioteca de gráficos ou outras bibliotecas de interface do usuário de terceiros.

Efeitos: A Armadilha da Propagação de Estado

Evite usar efeitos para propagar mudanças de estado, pois isso pode desencadear erros ExpressionChangedAfterItHasBeenChecked, atualizações circulares infinitas ou ciclos desnecessários de detecção de mudanças.

Devido a esses riscos, o Angular, por padrão, impede que você defina Sinais dentro de efeitos. Essa restrição pode ser contornada, se absolutamente necessário, definindo a flag allowSignalWrites ao criar um efeito. No entanto, é recomendável usar Sinais computados para modelar estados que dependem de outros estados, evitando assim armadilhas perigosas.

Efeitos: O Contexto da Injeção

Por padrão, você só pode criar um effect() dentro de um contexto de injeção, onde você tem acesso à função inject. A maneira mais simples de satisfazer esse requisito é chamar effect dentro do construtor de um componente, diretiva ou serviço:

@Component({...})
export class EffectiveCounterComponent {
  readonly count = signal(0);
  constructor() {
    effect(() => {
      console.log(`O contador está em: ${this.count()}`);
    });
  }
}

Alternativamente, você pode atribuir o efeito a um campo, o que também permite dar um nome descritivo a ele:

@Component({...})
export class EffectiveCounterComponent {
  readonly count = signal(0);
  private loggingEffect = effect(() => {
    console.log(`O contador está em: ${this.count()}`);
  });
}

Para criar um efeito fora do construtor, você pode passar um Injector para effect através de suas opções:

@Component({...})
export class EffectiveCounterComponent {
  readonly count = signal(0);
  constructor(private injector: Injector) {}
  initializeLogging(): void {
    effect(() => {
      console.log(`O contador está em: ${this.count()}`);
    }, {injector: this.injector});
  }
}

Efeitos: A Destruição e o Legado

Quando você cria um efeito, ele é automaticamente destruído quando o contexto que o envolve é destruído. Isso significa que efeitos criados dentro de componentes são destruídos quando o componente é destruído, e o mesmo se aplica a diretivas, serviços, etc.

Os efeitos retornam um EffectRef que você pode usar para destruí-los manualmente, invocando o método .destroy(). Combinando isso com a opção manualCleanup, você pode criar um efeito que persiste até ser destruído manualmente. No entanto, tenha cuidado para limpar esses efeitos quando eles não forem mais necessários, como um jardineiro que poda as plantas para que o jardim floresça.

Tópicos Avançados: Desvendando os Segredos dos Sinais

Sinais e Funções de Igualdade: A Busca pela Verdadeira Mudança

Ao criar um Sinal, você pode, como um mestre da lógica, fornecer uma função de igualdade. Essa função, como um juiz imparcial, será usada para determinar se o novo valor é realmente diferente do anterior, evitando atualizações desnecessárias:

import _ from 'lodash';
const data = signal(['test'], {equal: _.isEqual});

// Mesmo sendo uma nova instância de array, a função de igualdade profunda considerará os valores iguais.
data.set(['test']); // Nenhuma atualização será disparada.

Essa função de igualdade, como um oráculo, pode ser fornecida tanto para Sinais mutáveis quanto para Sinais computados, garantindo que apenas as mudanças relevantes sejam detectadas.

Lendo Sinais em Segredo: A Arte da Discrição

Em raras ocasiões, você pode precisar executar código que lê Sinais dentro de uma função reativa, como computed ou effect, sem criar uma dependência. Essa técnica, como um espião que se infiltra em território inimigo, permite observar os Sinais sem deixar rastros.

Imagine um cenário onde você deseja registrar o valor de um contador sempre que o usuário atual (currentUser) muda. Você poderia criar um efeito que lê ambos os Sinais:

effect(() => {
  console.log(`Usuário definido como ${currentUser()} e o contador está em ${counter()}`);
});

No entanto, esse efeito seria executado sempre que currentUser ou counter mudassem. Para evitar isso, você pode usar a função untracked para ler o valor de counter sem criar uma dependência:

effect(() => {
  console.log(`Usuário definido como ${currentUser()} e o contador está em ${untracked(counter)}`);
});

Agora, o efeito só será executado quando currentUser mudar, como um detetive que só age quando a pista certa aparece.

Efeitos e Funções de Limpeza: A Arte da Finalização Elegante

Os efeitos, como artistas performáticos, podem iniciar operações de longa duração. Para garantir uma saída de cena elegante, você pode fornecer uma função onCleanup ao criar o efeito. Essa função, como um assistente de palco, será invocada antes da próxima execução do efeito ou quando o efeito for destruído, permitindo que você cancele operações em andamento e libere recursos:

effect((onCleanup) => {
  const user = currentUser();
  const timer = setTimeout(() => {
    console.log(`1 segundo atrás, o usuário se tornou ${user}`);
  }, 1000);
  onCleanup(() => {
    clearTimeout(timer);
  });
});

Com essa função de limpeza, seus efeitos podem encerrar suas atividades de forma organizada, como um ator que se despede do público após uma performance memorável.

Sinais Compartilhados: A Arte da Colaboração

Quando um Sinal é utilizado por múltiplos componentes, surge a necessidade de gerenciá-lo de forma eficiente e segura. Em cenários simples, criar um Sinal global e importá-lo nos componentes pode ser suficiente, como um idioma universal que permite a comunicação entre diferentes culturas.

No entanto, em aplicações mais complexas, essa abordagem pode levar a problemas de controle e manutenção, como um sistema político sem leis claras. Para evitar o caos, podemos encapsular o Sinal em um serviço de dados, como um cofre que protege um tesouro valioso.

Serviços de Dados Reativos Baseados em Sinais: O Cofre da Reatividade

Um serviço de dados baseado em Sinais oferece um mecanismo seguro e controlado para compartilhar um Sinal mutável entre vários componentes. O Sinal mutável é mantido em segredo, como um ingrediente secreto de uma receita, enquanto um Sinal imutável derivado dele é disponibilizado publicamente.

Para modificar o valor do Sinal, os componentes devem utilizar um método público do serviço, como um ritual que só pode ser realizado por sacerdotes autorizados.

Esse padrão, como um sistema de governo eficiente, garante que as regras de negócio sejam aplicadas de forma consistente e que o estado da aplicação seja mantido sob controle.

Sinais: Além das Fronteiras

A beleza dos Sinais reside em sua versatilidade. Eles podem ser criados em qualquer lugar da sua aplicação, como sementes que germinam em diferentes solos. No entanto, em muitos casos, é recomendável encapsulá-los em um serviço para garantir um controle mais preciso e evitar efeitos colaterais indesejados.

Sinais vs. RxJS: Uma Nova Perspectiva na Reatividade

Embora não sejam um substituto direto para o RxJS, os Sinais oferecem uma alternativa mais simples e intuitiva em situações onde o RxJS seria comumente utilizado. Por exemplo, os Sinais podem substituir os Behavior Subjects do RxJS na propagação de mudanças de dados para múltiplas partes da aplicação.

Ao explorar o mundo dos Sinais, você descobrirá novas maneiras de construir aplicações reativas em Angular, com mais simplicidade, elegância e controle.

Como Criar Serviços de Dados Reativos Baseados em Sinais

O padrão mais simples para compartilhar um sinal gravável em vários componentes é encapsular o sinal em um serviço de dados, assim:

@Injectable({
  providedIn: "root",
})
export class CounterService {
  // este é o sinal gravável privado
  private counterSignal = signal(0);

  // este é o sinal somente leitura público
  readonly counter = this.counterSignal.asReadonly();

  constructor() {
    // injete quaisquer dependências necessárias aqui
  }

  // qualquer um que precisar modificar o sinal
  // precisa fazer isso de maneira controlada
  incrementCounter() {
    this.counterSignal.update((val) => val + 1);
  }
}

Este padrão é muito semelhante ao uso de Serviços de Dados Observáveis com RxJs e um BehaviorSubject, se você estiver familiarizado com esse padrão. A diferença é que este serviço é muito mais fácil de entender, e há menos conceitos avançados em jogo aqui.

Podemos ver que o sinal gravável counterSignal é mantido privado do serviço. Qualquer um que precisar do valor do sinal pode obtê-lo através de sua contraparte somente leitura pública, a variável counter. E qualquer um que precisar modificar o valor do contador só pode fazê-lo de maneira controlada, através do método público incrementCounter.

Dessa forma, quaisquer validações ou lógica de tratamento de erros podem ser adicionadas ao método, e ninguém pode ignorá-las. Imagine que há uma regra que diz que o contador não pode ultrapassar 100. Com esse padrão, podemos facilmente implementar isso no método incrementCounter em um único lugar, em vez de repetir essa lógica em toda a aplicação.

Também podemos refatorar e manter a aplicação melhor. Se quisermos descobrir todas as partes da aplicação que estão incrementando o contador, só precisamos encontrar o uso do método incrementCounter usando nosso IDE. Esse tipo de análise de código não seria possível se déssemos acesso direto ao sinal.

Além disso, se o sinal precisar acessar quaisquer dependências para funcionar corretamente, essas podem ser recebidas no construtor, como em qualquer outro serviço. Um dos princípios em jogo aqui que torna essa solução mais sustentável é o princípio da encapsulação. Não queremos que qualquer parte da aplicação possa emitir novos valores para o sinal livremente; queremos permitir isso apenas de maneira controlada.

Epílogo: A Revolução dos Sinais no Angular

Ao mergulharmos nos meandros dos Sinais do Angular, descobrimos um novo paradigma reativo que promete transformar a forma como construímos aplicações. Os sinais não apenas proporcionam uma maneira elegante e eficiente de gerenciar o estado, mas também nos oferecem um controle granular sobre as atualizações de UI, algo que antes parecia impossível.

Imagine a fluidez de uma interface que responde instantaneamente às mudanças, atualizando apenas o necessário e liberando recursos de forma inteligente. Com os Sinais, podemos criar aplicações mais rápidas, mais reativas e, acima de tudo, mais fáceis de manter.

Mas além da técnica, há a filosofia. Assim como um maestro conduz uma orquestra, sincronizando cada instrumento para criar uma sinfonia harmoniosa, os Sinais nos permitem orquestrar cada parte de nossa aplicação, garantindo que tudo funcione em perfeita harmonia.

A jornada com Sinais é uma jornada de empoderamento. É sobre equipar desenvolvedores com ferramentas que simplificam o complexo, que tornam o impossível, possível. E ao dominarmos esses conceitos, não estamos apenas construindo melhores aplicações; estamos nos tornando melhores desenvolvedores.

Então, convido você a explorar, experimentar e abraçar os Sinais do Angular. Deixe-se emocionar pela elegância da simplicidade e pelo poder da reatividade. Porque, no final, são esses pequenos detalhes que fazem toda a diferença e que levam nossas aplicações ao próximo nível.

Agora é a sua vez de brilhar
Agora é a sua vez de brilhar

Agora é a sua vez de brilhar! Compartilhe suas experiências, dúvidas e ideias sobre os Sinais nos comentários abaixo. Juntos, podemos desvendar todos os segredos dessa poderosa ferramenta e elevar a reatividade em Angular a um novo patamar.