Angular Signals: как обрабатывать запросы к API

Итак, я новичок в сигналах и пытаюсь использовать их все больше и больше в нашем приложении. Часть, которую я до сих пор не могу уяснить, — это связь между rxJS и Signals. Поскольку мы используем HTTP-клиент Angulars, нам приходится иметь дело с наблюдаемыми (что неплохо). Но теперь я пытаюсь сделать свой код более реактивным, обновляя значение сигналов, выполняя вызов API всякий раз, когда изменяется идентификатор объекта с ограниченной областью действия (который сам является сигналом). Вот что я придумал:

 effect(() => {
      this.apiService.hasSomething({
        customerId: mySignalWhoseValueChanges()
      }).subscribe(value => this.mySignal.set(value));
    });

Если я использую mySignal() в своем шаблоне и изменяю mySignalWhoseValueChanges(), он вызовет effect(), потому что effect() прослушивает любые изменения сигналов, которые используются внутри него.

Дело в том, что я на 100% уверен, что есть лучший подход без использования effect(), но я просто не знаю. Я пробовал использовать toSignal(), но это не работает, поскольку на самом деле это не сигнал, на который ссылаются, а только значение на момент его создания, или я что-то упускаю:

this.mySignal = toSignal(this.apiService.hasSomething({
      customerId: mySignalWhoseValueChanges()
    }))

Заранее спасибо!

Я не думаю, что рекомендуется использовать «эффект» в ответах HTTP. Они рекомендуют это только в некоторых ситуациях angular.io/guide/signals#use-cases-for-effects . Так что придерживайтесь традиционного развития, с моей точки зрения.

D A 21.03.2024 19:37
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
3
1
2 887
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете довольно легко объединить сигнал с http-запросом.

Вы можете использовать функцию toObservable для отслеживания изменений в сигнале, а затем запускать HTTP-запрос при изменении идентификатора.

Примечание. Сигналы НЕ предназначены для замены RxJS (по крайней мере, в ближайшее время). Они предназначены для работы в унисон.

Для этого вам следует предпринять следующие шаги:

  1. Создайте сигнал user для наблюдения за текущим пользователем. (это может быть что угодно, я выбрал номер).
  2. Создайте сигнал response для отслеживания ответа http.
  3. Затем создайте способ обновления пользователя (я выбрал ввод формы), который привязывается к сигналу user.
  4. Используйте toObservable и передайте свой сигнал user в качестве параметра.
  5. Когда он изменится, отправьте http-запрос на сервер.
  6. Когда сервер ответит, обновите свой сигнал response данными.

Пример StackBlitz

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

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [HttpClientModule, FormsModule, AsyncPipe, JsonPipe],
  styles: ['div{font-family: monospace}'],
  template: `
    @if ({request: page$ | async}) {
      <div>{{response() | json}}</div>
    }
    <p>Enter a number from 1 to 4.</p>
    <input [(ngModel)] = "user" />
  `,
})
export class App {
  user = signal<number>(0);          // A way to watch users
  response = signal<any>(undefined); // A way to watch for http changes

  constructor(private readonly http: HttpClient) {}

 
  page$ = toObservable<number>(this.user).pipe( // Watch for user changes
    filter((id) => id > 0),                     // Only make http request for users larger than 0
    tap((id) => console.info('user id', id)),    // Just some debugging
    exhaustMap((id) =>                          // Don't execute the http request if one is already in progress
      this.http
        .get<Response>(`https://example.com/user/${id}`) // Make the http request
        .pipe(tap((response) => this.response.set(response)))   // Update the response
    )
  );
}

При вашем подходе запрос будет выполнен только один раз. если бы вы, например, указали example.com/endpoint/customerId в качестве URL-адреса, даже если customerId обновится (что в моем случае является сигналом), запрос не будет выполнен снова, или я упускаю суть?

Martin Hochmair 22.03.2024 08:21

@MartinHochmair Похоже, мой последний комментарий об обновлении ответа не сохранился... Но в любом случае я обновил ответ, который должен учитывать ваш комментарий.

Get Off My Lawn 23.03.2024 13:58

Спасибо, это было то, что я искал! Я надеялся на простой подход, но это определенно лучше, чем эффект.

Martin Hochmair 25.03.2024 09:56

Использование гораздо более чистого подхода к получению запросов на основе идентификатора объекта.

На момент написания предпочтительнее сохранить компоненты службы как операторы rxjs и использовать сигналы для синхронной реактивности. Эффекты в настоящее время используются только для рендеринга изменений в представлении, а не в бизнес-логике (вы можете сделать это, используя untracked()).

Подробнее об этом можно узнать здесь.

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

entityId = input.required<string>({
  alias: 'id',
});

response = toSignal(
  toObservable<string>(entityId).pipe(switchMap((id) => this.apiService.fetchById(id))),
);

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