Как создать вычисленный сигнал из сигнала и наблюдаемого?

Представьте себе стандартную ситуацию в Angular: вам нужно получить данные с сервера для некоторого идентификатора объекта. Идентификатор объекта — это сигнал, и вы хотите, чтобы полученные данные тоже были сигналом.

Что-то вроде этого кажется естественным:

      
@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule],
  template: `
  <code>{{roles()}}</code>
  `,
})
export class App {
  userId: Signal<string> = signal('userA');
  roles = computed(() => toSignal(getRoles(this.userId())));
}

//this could be api service sending  http request
const getRoles = (userId: string) => {
  return userId === 'userA' ? of([1, 2, 3]) : of([1]);
};

но в консоли браузера возникает ошибка выполнения:

      Error: NG0203: toSignal() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`. Find more at https://angular.io/errors/NG0203

Демо-версия Stackblitz здесь

ОБНОВЛЕНИЕ: я также пытался предоставить Injector вtoSignal:

       constructor(private injector: Injector) {}
  userId: Signal<string> = signal('userA');
  roles = computed(() =>
    toSignal(getRoles(this.userId()), { injector: this.injector })()
  );

но затем еще одна ошибка времени выполнения:

      Error: NG0600: Writing to signals is not allowed in a `computed` or an `effect` by default. Use `allowSignalWrites` in the `CreateEffectOptions` to enable this inside effects.

3 ответа

Нет необходимости в эффекте , поскольку то, что вы хотите, можно выполнить с помощью функций взаимодействия rxjs.

Для сигнала роли userId должен быть преобразован в наблюдаемый объект с помощью toObservable . Затем значение этой новой наблюдаемой передается оператору switchMap для получения значения роли из службы. Наконец, внутренний наблюдаемый поток преобразуется обратно в сигнал, передавая его в toSignal .

      export class App {
  readonly userId = signal('userA');

  readonly roles = toSignal(toObservable(this.userId).pipe(
    switchMap(userId => getRoles(userId)),
  ), { initialValue: [] });
}

В приведенном выше примере указано начальное значение. Вы можете опустить это, но это создаст начальную неопределенную эмиссию.

Как насчет чего-то еще такого:

      export class App {
  userId = signal('userA');
  roles = signal<number[]>([]);

  roleEffect = effect(() =>
    this.getRoles(this.userId()).subscribe(
       r = this.roles.set(r)
    )
  );
}

Я бы сказал, что вам нужно обрабатывать сигнал как любое асинхронное значение:

      roles = computed(() =>
  getRoles(this.userId())
);

И вызовите асинхронный канал:

      {{roles() | async }

Что вы думаете об этом ?


Изменить: Что касается второго сообщения об ошибке, вы пишете сигнал, создавая его с помощьюtoSignal()который также подписывается и устанавливает новые значения. (См. исходный код )

Другие вопросы по тегам