Convertendo observáveis ​​em sinais em Angular: o que você precisa saber

May 01 2023
Angular v16 vem com um novo pacote chamado rxjs-interop, que apresenta a função toSignal que converte um observável em um sinal. Neste artigo, veremos mais de perto esse novo recurso e seu uso.

Angular v16 vem com um novo pacote chamado rxjs-interop, que apresenta a toSignalfunção que converte um observável em um sinal. Neste artigo, veremos mais de perto esse novo recurso e seu uso.

Para começar a usar a toSignalfunção, precisamos importá-la do @angular/core/rxjs-interopmódulo. Aqui está um trecho de código de exemplo que demonstra seu uso:

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

@Component({
  standalone: true,
  template:`{{ counter() }}`,
})
export class FooComponent {
  counter$ = interval(1000);
  counter = toSignal(this.counter$);
}

Vale a pena notar que, ao contrário do asyncpipe, podemos ler o valor do sinal imediatamente em nosso componente, o que pode produzir undefined.

Além disso, a toSignalfunção se inscreve no observável imediatamente , o que pode causar resultados indesejados em alguns casos se houver efeitos colaterais.

Se tivermos um código que usa o asyncpipe com uma ngIfdiretiva, ele se inscreverá no observável apenas quando renderizarmos o modelo.

@Component({
  standalone: true,
  template:`<div *ngIf="someCondition">
     <div *ngFor="let item of source$ | async"></div>
  </div>`,
})
export class FooComponent {
  source$ = inject(Service).someMethod();
}

Caso desejemos remover o undefinedtipo de nosso sinal resultante, temos duas opções. A primeira é passar um valor inicial quando temos um asyncobservável que não dispara imediatamente.

@Component({
  standalone: true,
  template:`{{ counter() }}`,
})
export class FooComponent {
  counter$ = interval(1000);
  counter = toSignal(this.counter$, { initialValue: 0 });
}

@Component({
  standalone: true,
  template:`{{ counter() }}`,
})
export class FooComponent {
  counter$ = interval(1000).pipe(startWith(0));
  counter = toSignal(this.counter$, { requireSync: true });
}

Quando a toSignalfunção é chamada, ela primeiro verifica se está sendo chamada em um contexto de injeção . Caso contrário, um erro será lançado. Isso significa que podemos usar a toSignalfunção somente quando a inject() função estiver disponível, exceto nos casos em que usamos a manualCleanupopção ou passamos injectorexplicitamente.

A razão para isso é que o Angular cancela automaticamente a assinatura quando o contexto de empacotamento é destruído. Ele faz isso usando o novo OnDestroygancho que obtém usando a inject()função ou o fornecido explicitamente injector:

@Component({
  selector: 'foo',
  standalone: true,
  template:`{{ counter() }}`,
})
export class FooComponent {
  counter$ = interval(1000);
  counter: Signal<number | undefined>; 
  private injector = inject(Injector);

  ngOnInit() {
    this.counter = toSignal(this.counter$, { injector: this.injector } );
  }
}

@Component({
  standalone: true,
  template:`{{ counter() }}`,
})
export class FooComponent {
  counter$ = interval(1000).pipe(take(3));
  counter = toSignal(this.counter$, { manualCleanup: true });
}

@Component({
  standalone: true,
  template:`{{ counter() }}`,
})
export class FooComponent {
  counter$ = interval(1000);
  counter = toSignal(this.counter$, { initialValue: 0 });

  ngOnInit() {
    try {
      this.counter();
    } catch (e) {
      console.log(e);
    }
  }
}

Siga-me no Medium ou Twitter para ler mais sobre Angular e JS!