Angular Signals

Reatividade Simples e Prática

William Grasel

renaissance

Nova logo!

New Built-in control flow

import {
  bootstrapApplication
} from '@angular/platform-browser';

@Component({ 
  selector: 'hello-world', 
  standalone: true,
  template: `Hello {{ world }}`
})
export class HelloWorld {
  world = "World";
}

bootstrapApplication(HelloWorld);

StandAlone Component

Deferrable views

Entre várias outras novidades!

Mas ainda o mesmo framework desde a v2.0

O que é Reatividade?

RxJS

Reactive Extensions Library for JavaScript

RxJS é um canivete suíço, para lidar com todo tipo de fluxo de dados, síncrono ou não, de maneira avançada!

Mas no dia a dia, normalmente queremos algo simples, como uma planilha de excel

Queremos algo fácil de ser aprendido, escrito e de dar manutenção por qualquer desenvolvedor!

Foi com esse foco que Signals foi criado!

Apenas 3 primitivas simples:

Signals são as "células" reativas que retém os valores e notificam os interessados em mudanças!

Computes reagem as mudanças de outras "células", para criar novos valores derivados através de formulas!

Effects reagem as mudanças de cada célula sem criar novos valores, para executar algum efeito colareral!

import { Component, signal, computed, effect } from '@angular/core';

@Component({ 
  selector: 'planilha',
  template: `
    <table>
      <tr>
        <th>First</th>
        <th>Last</th>
        <th>Full Name</th>
      </tr>
      <tr>
        <td>{{ firstName() }}</td>
        <td>{{ lastName() }}</td>
        <td>{{ fullName() }}</td>
      </tr>
    </table>`
})
export class Planilha {
  firstName = signal("William");
  lastName = signal("Grasel");

  fullName = computed(() =>
    this.firstName() + " " + this.lastName()
  );

  lastNameChange = effect(() => console.log(
    "Last name changed: " +  this.lastName()
  ));
}

Juntando tudo!

Posso já começar a usar Signals agora??

Não só pode como DEVE!

A API entrou como Developer Preview na v16

Na v17 foi graduada para a API pública sem mudanças!

Signals também podem ser usados em Services, Diretivas, Pipes e etc.

Chegou a hora de se acostumar com esse novo padrão reativo!

Signals ❤️ RxJS

A integração do framework com Signals deve ser cada vez maior

Observables continuam sendo uma parte importante do framework

Sendo a principal opção para os casos mais complexos

E vamos ter de aprender a trabalhar com os dois juntos em muitos casos

De Observable para Singals

import { HttpClient, HttpClientModule } from '@angular/common/http';
import { Component, inject } from '@angular/core';
import { User } from './user.interface';
import { toSignal } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    HttpClientModule,
  ],
  template: `
    @if( user(); as data ) {
      <h1>{{ data.title }}</h1>
    }
  `,
})
export class App {
  http = inject(HttpClient);
  user$ = this.http.get<User>("/user");
  user = toSignal(this.user$); // Signal<User | undefined>
}

Fornecendo um valor inicial

import { Component } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';

@Component({
  selector: 'app-root',
  template: `{{ counter() }}`,
})
export class App {
  counterObservable$ = interval(1000);
  
  // Signal<number>
  counter = toSignal(this.counterObservable$, {
    initialValue: 0
  });
}

De Signal para Observable

import { Component, Signal, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FilterService } from './filter.service';
import { toObservable } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-root',
  template: `{{ results$ | async }}`,
})
export class App {
  http = inject(HttpClient);
  
  query = inject(FilterService).query; // Signal<string>
  query$ = toObservable(this.query);
  
  results$ = this.query$.pipe(
    switchMap(query => this.http.get('/search?q=' + query ))
  );
}

O que o futuro da API de Signals nos trará?

Hoje todo o processo de detecção de mudanças é baseado no ZoneJS

Que basicamente intercepta toda chamada assíncrona do browser

E sai varrendo

cada componente

para verificar se houve mudanças na interface

Mas e se desse para saber, de maneira granular e cirúrgica, quando realmente houve mudanças relevantes?

Responder de maneira reativa e precisa não é justamente o objetivo da nova API de Signals?

Signal-based components

Precisamos que todo o ciclo de vida de cada componente, com seus inputs e outputs sejam baseados em Signals

Input / Output como Signals

@Component({
  signals: true,
  selector: 'user-profile',
  template: `
    <p>Name: {{ firstName() }} {{ lastName() }}</p>
    <p>Disabled: {{ disable() }}</p>
  `,
})
export class UserProfile {
  // readonly Signal<string|undefined>
  firstName = input<string>();

  // readonly Signal<string>
  lastName = input('Smith');
  
  // readonly Signal<string>
  nickName = input.required<string>();

  // EventEmitter<number>
  saved = output<number>();

  // Cria um modelo, um *writable* signal que suporta two-way binding.
  disable = model(false); // WritableSignal<boolean>
}

Queries como Signals

@Component({
  template: `
    <div #el>element to query</div>
  `
})
export class App {
  // returns Signal<ElementRef<HTMLDivElement> | undefined>
  divEl = viewChild<ElementRef<HTMLDivElement>>('el');

  // returns Signal<ElementRef<HTMLDivElement>>
  divElReq = viewChild.required<ElementRef<HTMLDivElement>>('el');

  // returns Signal<readonly ElementRef<HTMLDivElement>[]>
  divEls = viewChildren('el');
}

Mas e a estratégia OnPush?

Não resolve o mesmo problema?

OnPush é MUITO mais performático que a detecção de mudança padrão

Mas vem com muitos desafios e complexidade de implementação

Além de não permitir o mesmo nível de precisão que Signal based

Como será que vai ficar esse benchmark com Signal-based components?

Ta, mas quantos anos vamos ter de esperar por isso??

Pode ser mais rápido que você imagina!

Boa era da renascença para sua Web App!

Referências

Obrigado! =)