Преобразование наблюдаемых в сигналы в Angular: что вам нужно знать

May 01 2023
Angular v16 поставляется с новым пакетом rxjs-interop, в котором представлена ​​функция toSignal, преобразующая наблюдаемое в сигнал. В этой статье мы более подробно рассмотрим эту новую функцию и ее использование.

Angular v16 поставляется с новым пакетом с именем rxjs-interop, в котором представлена toSignal​​функция, преобразующая наблюдаемое в сигнал. В этой статье мы более подробно рассмотрим эту новую функцию и ее использование.

Чтобы начать использовать toSignalфункцию, нам нужно импортировать ее из @angular/core/rxjs-interopмодуля. Вот пример фрагмента кода, демонстрирующий его использование:

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$);
}

Стоит отметить, что, в отличие от asyncпайпа, мы можем сразу считывать значение сигнала в нашем компоненте, который может выдавать undefined.

Более того, toSignalфункция сразу же подписывается на observable , что может привести к нежелательным результатам в некоторых случаях, если есть побочные эффекты.

Если у нас есть код, который использует asyncканал с ngIfдирективой, он подпишется на наблюдаемое только тогда, когда мы визуализируем шаблон.

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

В случае, если мы хотим удалить undefinedтип из нашего результирующего сигнала, у нас есть два варианта. Первый — передать начальное значение, когда у нас есть asyncнаблюдаемое, которое не срабатывает немедленно.

@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 });
}

Когда toSignalфункция вызывается, она сначала проверяет , вызывается ли она в контексте внедрения . Если нет, будет выброшена ошибка. Это означает, что мы можем использовать toSignalфункцию только тогда, когда inject() функция доступна, за исключением случаев, когда мы используем manualCleanupопцию или передаем injectorявно.

Причина этого в том, что Angular будет автоматически отписываться при уничтожении контекста упаковки. Он делает это с помощью нового OnDestroyхука, который он получает при использовании inject()функции или явно предоставленного 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);
    }
  }
}

Подпишитесь на меня в Medium или Twitter , чтобы узнать больше об Angular и JS!